这篇文章主要解析spring定时任务的源码,如果需要看使用方式请看我上篇文章
文章目录
前言
该篇文章介绍Scheduled注解到怎么扫描并注册任务,执行定时任务,各个类的源码解析
定时任务管理各个功能解析
核心类摘要
-
ScheduledAnnotationBeanPostProcessor(任务注解解析器)
-
ScheduledTaskRegistrar(任务注册器)
-
TaskScheduler (任务调度器 默认ThreadPoolTaskScheduler )
-
ReschedulingRunnable(可重复调度的可运行任务)
中间涉及到的比较重要的类
-
CronTask ( 封装的定时任务类)
-
ScheduledThreadPoolExecutor (jdk提供的定时定时线程池类)
-
SchedulingConfiguration 接口
源码是在 org.springframework.scheduling包下面的
- 注册任务解析器
首先 查看@EnableScheduling 注解
具体的import
import详解
EnableScheduling注释
/**
*启用Spring的计划任务执行功能,类似于
*Spring的{@code<task:*>}XML命名空间中的功能。待使用
*在@{@link Configuration}类上,如下所示:
*<pre class=“code”>
* @Configuration
* @EnableScheduling
*公共类AppConfig{
*// various @;Bean definitions
*}</pre>
*这允许在任何Spring管理的数据库上检测@{@link Scheduled}注释
*容器中的注解。例如,给定一个类{@code MyTask}
* package com.myco.tasks;
*public class MyTask {
* @Scheduled(fixedRate=1000)
*public void work() {
*//任务执行逻辑
* }
*}</pre>
*以下配置将确保调用{@code MyTask.work()}
*每1000毫秒一次
* @Configuration
* @EnableScheduling
* public class AppConfig {
* @Bean
* public MyTask task() {
* return new MyTask();
* }
* }</pre>
*或者,如果用{@code@Component}注释了{@code@MyTask},则
*以下配置将确保其{@code@Scheduled}方法
*按所需间隔调用:
*<pre class=“code”>
* @Configuration
* @ComponentScan(basePackages="com.myco.tasks")
* public class AppConfig {
* }</pre>
*用{@code@Scheduled}注释的方法甚至可以直接在
*{@code@Configuration}类:
* <pre class="code">
* @Configuration
* @EnableScheduling
* public class AppConfig {
* @Scheduled(fixedRate=1000)
* public void work() {
* // task execution logic
* }
* }</pre>
*在上述所有场景中,都使用默认的单线程任务执行器。
*当需要更多的控制时,{@code@Configuration}类可以实现
*{@link SchedulingConfigurer}。这允许访问底层
*{@link scheduledtaskregistar}实例。例如,以下示例
*演示如何自定义用于执行调度的{@link Executor}
*任务:
*<pre class=“code”>
* @Configuration
* @EnableScheduling
* public class AppConfig implements SchedulingConfigurer {
* @Override
* public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
* taskRegistrar.setScheduler(taskExecutor());
* }
*
* @Bean(destroyMethod="shutdown")
* public Executor taskExecutor() {
* return Executors.newScheduledThreadPool(100);
* }
* }</pre>
*
*注意上面的例子中{@code@Bean(destroyMethod=“shutdown”)}的用法。这个
*确保任务执行器在Spring应用程序启动时正确关闭
*上下文本身是封闭的。
*实现{@code SchedulingConfigurer}还允许细粒度的
*通过{@code scheduledtaskregistar}控制任务注册。
*例如,下面配置特定bean的执行
*每个自定义{@code Trigger}实现的方法:
*<pre class=“code”>
@Configuration
*@EnableScheduling
* public class AppConfig implements SchedulingConfigurer {
* @Override
* public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
* taskRegistrar.setScheduler(taskScheduler());
* taskRegistrar.addTriggerTask(
* new Runnable() {
* public void run() {
* myTask().work();
* }
* },
* new CustomTrigger()
* );
* }
* @Bean(destroyMethod="shutdown")
* public Executor taskScheduler() {
* return Executors.newScheduledThreadPool(42);
* }
* @Bean
* public MyTask myTask() {
* return new MyTask();
* }
* }</pre>
*作为参考,上面的示例可以与下面的Spring XML进行比较
* 配置:
*<pre class=“code”>
* {@code
* <beans>
* <task:annotation-driven scheduler="taskScheduler"/>
* <task:scheduler id="taskScheduler" pool-size="42"/>
* <task:scheduled ref="myTask" method="work" fixed-rate="1000"/>
* <bean id="myTask" class="com.foo.MyTask"/>
* </beans>
* }</pre>
*除了在XML中使用固定利率时段外,这些示例是等效的
*而不是定制的<em>{@code Trigger}</em>实现;这是因为
*{@code task:}命名空间{@code scheduled}无法轻松公开此类支持。这是
*但是有一个例子说明了基于代码的方法如何允许最大的可配置性
*通过直接访问实际组件。<p>
* @author Chris Beams
* @since 3.1
* @see Scheduled
* @see SchedulingConfiguration
* @see SchedulingConfigurer
* @see ScheduledTaskRegistrar
* @see Trigger
* @see ScheduledAnnotationBeanPostProcessor
*/
并直接在写了EnableScheduling 的demo
介绍任务的Spring的计划任务执行功能
任务注解解析器(ScheduledAnnotationBeanPostProcessor)
全篇注释
/**
*注册用@{@link Scheduled}注释的方法的Bean后处理器
*由{@link org.springframework.scheduling.TaskScheduler}根据
*通过注释提供的“fixedRate”、“fixedDelay”或“cron”表达式。
*<p>这个后处理器由Spring的
*{@代码<task:annotation-driven>}XML元素,以及
*@{@link EnableScheduling}注释。
*<p>自动检测容器中的任何{@link SchedulingConfigurer}实例,
*允许定制要使用的调度程序或进行细粒度的
*对任务注册的控制(例如{@link Trigger}任务的注册)。
*有关完整的用法详细信息,请参阅@{@link EnableScheduling}javadocs。
- @author Mark Fisher
- @author Juergen Hoeller
- @author Chris Beams
- @since 3.0
- @see Scheduled
- @see EnableScheduling
- @see SchedulingConfigurer
- @see org.springframework.scheduling.TaskScheduler
- @see org.springframework.scheduling.config.ScheduledTaskRegistrar
*/
- 管理任务注册器
自动创建 ScheduledTaskRegistrar,并持有
/**
* Create a default {@code ScheduledAnnotationBeanPostProcessor}.
*/
public ScheduledAnnotationBeanPostProcessor() {
this.registrar = new ScheduledTaskRegistrar();
}
- 管理所有任务:bean销毁时,任务自动销毁。
利用DisposableBean 接口,交给ioc框架 自动销毁任务
@Override
public void destroy() {
synchronized (this.scheduledTasks) {
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
for (Set<ScheduledTask> tasks : allTasks) {
for (ScheduledTask task : tasks) {
task.cancel();
}
}
this.scheduledTasks.clear();
}
this.registrar.destroy();
}
-
解析@Scheduled注解,封装成任务添加到任务注册器中。
实现 beanpostprocesse接口,容器所有bean实例化之后会调用该postProcessAfterInitialization 方法,然后 解析注解并封装成任务(例如 CronTask ),并将任务注册ScheduledTaskRegistrar
任务执行顺序 在任务注册器中执行顺序,先执行cron,之后再执行fixedRate
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
else {
// Non-empty set of methods
annotatedMethods.forEach((method, scheduledAnnotations) ->
scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
- 方法调用栈
- 动态生成定时任务调度器
ScheduledAnnotationBeanPostProcessor实现了ApplicationListener接口 (该接口的作用,当Spring容器将初始化完成之后 调用该方法) 调用 onApplicationEvent () 方法
- 加载 实现SchedulingConfigurer 接口的类
if (this.beanFactory instanceof ListableBeanFactory) {
Map<String, SchedulingConfigurer> beans =
((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(configurers);
for (SchedulingConfigurer configurer : configurers) {
configurer.configureTasks(this.registrar);
}
}
- 创建 TaskScheduler (任务调度器 默认ThreadPoolTaskScheduler)
通过NamedBeanHolder动态生成 (这里可以设置执行线程大小等)
try {
// Search for TaskScheduler bean...
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
- 最后调用任务注册器的方法,执行任务 this.registrar.afterPropertiesSet();
this.registrar.afterPropertiesSet();
- 方法调用栈
- 其他一些小功能
存储没有注解的类,降低重复扫描
private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
提供直接从application 中读取数据, 而不用 @Value
使用EmbeddedValueResolverAware解析配置文件
定时任务注册器(ScheduledTaskRegistrar)
- ScheduledTaskRegistrar全篇注释
/**
*用于向{@link TaskScheduler}注册任务的Helper bean,通常使用cron
*表达式。
*<p>从spring3.1开始,{@code scheduledtaskregistar}有一个更突出的面向用户的
*与@{@链接一起使用时的角色
*org.springframework.scheduling.annotation.EnableAsync EnableAsync}注释及其应用
*{@link org.springframework.scheduling.annotation.SchedulingConfigurer
*SchedulingConfigurer}回调接口。
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0
* @see org.springframework.scheduling.annotation.EnableAsync
* @see org.springframework.scheduling.annotation.SchedulingConfigurer
*/
- 管理所有的任务 :任务注册方法;bean销毁时,任务自动销毁。 包括各个任务的注册
- 也是实现了DisposableBean接口 实现任务的清除
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks) {
task.cancel();
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
}
}
- 管理任务调度器 :管理调度器并执行任务,设置调度器 。
当调度器为空的情况会自动创建一个单线程的线程池
(newSingleThreadScheduledExecutor只会有一个线程,不管你提交多少任务,这些任务会顺序执行,如果发生异常会取消下面的任务,线程池也不会关闭)
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
- 调用方法栈
任务调度器(TaskScheduler)
- 注释
/**
*Spring的{@link TaskScheduler}接口的实现,包装
*本地{@link java.util.concurrent.ScheduledThreadPoolExecutor}。
* @author Juergen Hoeller
* @author Mark Fisher
* @since 3.0
* @see #setPoolSize
* @see #setRemoveOnCancelPolicy
* @see #setThreadFactory
* @see #setErrorHandler
*/
- 管理定时任务类 ,初始化设置 线程数大小等
初始化线程池管理定时类 (默认 ScheduledThreadPoolExecutor ,并且单线程)
调用方法栈
通过 设置setPoolSize方法 设置核心线程数
public void setPoolSize(int poolSize) {
Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher");
this.poolSize = poolSize;
if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {
((ScheduledThreadPoolExecutor) this.scheduledExecutor).setCorePoolSize(poolSize);
}
}
- 执行具体的定时任务 创建 可重复调度的运行任务类
最终将task和trigger都封装到了ReschedulingRunnable中 定时任务。
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
- 延时任务,直接执行executor.scheduleAtFixedRate。
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
ScheduledExecutorService executor = getScheduledExecutor();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
- 其他小功能
包括提供活跃数获取等
由返回值的线程
- 可重复调度的运行任务类(ReschedulingRunnable),主要包含的是下面功能。
ReschedulingRunnable schedule方法加了同步锁,只能有一个线程拿到下次执行时间并加入执行器的调度。
/**
*内部适配器,根据
*到给定的{@link Trigger}建议的下一个执行时间。
*<p>因为本机{@link ScheduledExecutorService}支持
*仅限延迟驱动执行。{@link Trigger}接口的灵活性
*将转换为下一次执行时间的延迟(重复)。
* @author Juergen Hoeller
* @author Mark Fisher
* @since 3.0
*/
不同的ReschedulingRunnable对象之间在线程池够用的情况下是不会相互影响的,也就是说满足线程池的条件下,TaskScheduler的schedule方法的多次调用是可以交叉执行的。
@Nullable
public ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) {
return null;
}
long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
}
- 执行具体的定时任务
(java.util.concurrent .ScheduledThreadPoolExecutor中重复进行调用)
下面这部分是jdk中的线程池执行定时任务的类
ScheduledFutureTask 工作原理如下图所示
1、ScheduledFutureTask会放入优先阻塞队列:ScheduledThreadPoolExecutor.DelayedWorkQueue(二叉最小堆实现)
2、上图中的Thread对象即ThreadPoolExecutor.Worker,实现了Runnable接口
3、 Worker中维护了Thread对象,Thread对象的Runnable实例即Worker自身
2、ThreadPoolExecutor#addWorker方法中会创建Worker对象,然后拿到Worker中的thread实例并start,这样就创建了线程池中的一个线程实例
3、Worker的run方法会调用ThreadPoolExecutor#runWorker方法,这才是任务最终被执行的地方,该方法示意如下
(1)首先取传入的task执行,如果task是null,只要该线程池处于运行状态,就会通过getTask方法从workQueue中取任务。ThreadPoolExecutor的execute方法会在无法产生core线程的时候向 workQueue队列中offer任务。
getTask方法从队列中取task的时候会根据相关配置决定是否阻塞和阻塞多久。如果getTask方法结束,返回的是null,runWorker循环结束,执行processWorkerExit方法。
至此,该线程结束自己的使命,从线程池中“消失”。
(2)在开始执行任务之前,会调用Worker的lock方法,目的是阻止task正在被执行的时候被interrupt,通过调用clearInterruptsForTaskRun方法来保证的(后面可以看一下这个方法),该线程没有自己的interrupt set了。
(3)beforeExecute和afterExecute方法用于在执行任务前后执行一些自定义的操作,这两个方法是空的,留给继承类去填充功能。
我们可以在beforeExecute方法中抛出异常,这样task不会被执行,而且在跳出该循环的时候completedAbruptly的值是true,表示the worker died due to user exception,会用decrementWorkerCount调整wc。
(4)因为Runnable的run方法不能抛出Throwables异常,所以这里重新包装异常然后抛出,抛出的异常会使当当前线程死掉,可以在afterExecute中对异常做一些处理。
(5)afterExecute方法也可能抛出异常,也可能使当前线程死掉。
总结:spring 的定时任务并不难,仔细看你会发发现其实就是核心类,并且利用spring框架的特性 ,扩展性强;当你理解过后,我们可以自定义注解器,包括自定义执行线程数等等,多种方式。
注:import详解 :https://zhuanlan.zhihu.com/p/147025312
beanpostprocess实现原理 :https://www.cnblogs.com/youzhibing/p/10559330.html
applicationevent原理 :https://blog.csdn.net/liyantianmin/article/details/81017960
是做了个超链接,我自己还没来得及分析,先引用别人的解析。
以及ScheduledFutureTask 工作原理图画。