SchedulingConfigurer源码初识:理解定时任务抛异常终止本次调度,但不会影响下一次执行调度
@EnableScheduling
也就是我们直接使用的@Scheduled注解配置cron表达式
ScheduledAnnotationBeanPostProcessor
这个ScheduledAnnotationBeanPostProcessor
不仅可以注册我们用@Scheduled注释的方法,它也会检测到我们自定义的定时任务调度配置ScheduledConfigurer实例
当我们开启debug模式,进入ScheduledAnnotationBeanPostProcessor
里面,程序会先执行无参的ScheduledAnnotationBeanPostProcessor()
方法
创建了一个ScheduledTaskRegistrar
对象,并将其赋值给类的registrar成员变量。ScheduledTaskRegistrar用于注册定时任务
设置beanName
设置beanFactory
用于将一个BeanFactory对象设置为当前对象的属性。BeanFactory是Spring框架中用于管理Bean的工厂类,它可以在运行时自动检测和创建Bean实例。通过将BeanFactory对象设置为当前对象的属性,当前对象就可以访问和管理BeanFactory中的所有Bean实例。在注释中提到,设置BeanFactory是可选的,如果不设置,则SchedulingConfigurer类型的Bean将不会被自动检测到,需要显式配置一个schedule
设置applicationContext(上下文),让bean与Spring应用上下文关联,决定bean何时开始活动。当上下文准备好(即所有bean都创建好)时,bean就会被激活。如果没有applicationContext,bean会在所有单例bean实例化后激活(即:如果不设置,初始化将在afterSingletonsInstantiated回调方法中发生,这意味着bean的激活会稍晚一些,但仍然在容器完成单例bean实例化之后)。同时,它也用applicationContext来替代可能缺失的BeanFactory。
进入afterSingletonsInstantiated
后,再进入onApplicationEvent
afterSingletonsInstantiated
:这个方法在Spring IoC容器初始化并创建了所有单例bean后被调用。此时,所有bean的实例已经创建,但可能还没有全部配置完成。在这个阶段,缓存中的单例类不再需要,所以被清除。如果当前环境不是在ApplicationContext中(比如简单的BeanFactory),那么会立即执行finishRegistration来完成一些早期的任务注册。
onApplicationEvent(ContextRefreshedEvent event)
:这个方法是在ApplicationContext完全初始化并刷新后被调用,即所有bean都已经被实例化、配置并且依赖注入已完成。ContextRefreshedEvent是一个事件,表示容器现在处于可用状态。当接收到这个事件时,如果事件源是当前的ApplicationContext,说明容器已经准备就绪,因此可以安全地执行finishRegistration,以完成注册任务。这样做的好处是允许其他监听器有机会在相同的时间点执行它们自己的初始化逻辑,确保所有必要的服务都已设置完毕。
在 ApplicationContext 中运行 -> 注册任务这么晚…让其他 ContextRefreshedEvent 侦听器有机会同时执行他们的工作(例如 Spring Batch 的作业注册)。
进入finishRegistration方法
private void finishRegistration() {
//当前this.scheduler==null,当前对象的scheduler属性未初始化
if (this.scheduler != null) { //检查Scheduler:首先,函数检查当前对象的scheduler属性是否已初始化,如果非空,则将这个scheduler设置给registrar。
this.registrar.setScheduler(this.scheduler);
}
//获取并排序SchedulingConfigurer:接着,如果beanFactory是ListableBeanFactory类型,函数会获取所有实现了SchedulingConfigurer接口的bean,将它们放入一个列表中,并按照AnnotationAwareOrderComparator进行排序。
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);
}
}
if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
try {
// Search for TaskScheduler bean...
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
}
catch (NoUniqueBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique TaskScheduler bean - attempting to resolve by name: " +
ex.getMessage());
}
try {
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskScheduler bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default TaskScheduler bean - attempting to find ScheduledExecutorService: " +
ex.getMessage());
}
// Search for ScheduledExecutorService bean next...
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
}
catch (NoUniqueBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find unique ScheduledExecutorService bean - attempting to resolve by name: " +
ex2.getMessage());
}
try {
this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
}
catch (NoSuchBeanDefinitionException ex3) {
if (logger.isInfoEnabled()) {
logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex2.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex2) {
if (logger.isTraceEnabled()) {
logger.trace("Could not find default ScheduledExecutorService bean - falling back to default: " +
ex2.getMessage());
}
// Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
}
}
}
this.registrar.afterPropertiesSet();
}
检查Scheduler:首先,函数检查当前对象的scheduler
属性是否已初始化,如果非空,则将这个scheduler设置给registrar。
获取并排序SchedulingConfigurer
:接着,如果beanFactory是ListableBeanFactory类型,函数会获取所有实现了SchedulingConfigurer接口的bean,将它们放入一个列表中,并按照AnnotationAwareOrderComparator进行排序。
配置Tasks:遍历排序后的SchedulingConfigurer列表,对每个配置器调用configureTasks方法,允许它们自定义任务调度。
设置Scheduler
:
如果registrar有需要执行的任务,但是还没有设置调度器,函数会尝试从beanFactory中找到一个TaskScheduler
或者ScheduledExecutorService
的bean。这个查找过程首先尝试通过类型匹配,如果找不到,会尝试通过名称匹配(期望的bean名称为’taskScheduler’)。如果仍然找不到,会输出相关信息并回退到使用registrar的内置默认调度器。
找TaskScheduler
找ScheduledExecutorService
初始化registrar
:在所有的设置完成后,调用registrar
的afterPropertiesSet
方法,这通常用于初始化和验证registrar的所有必要属性。
ScheduledTaskRegistrar
进入ScheduledTaskRegistrar
的afterPropertiesSet
方法,调用scheduleTasks
检查任务调度器:首先,函数检查是否有已设置的任务调度器(taskScheduler)。如果没有,它会创建一个新的SingleThreadScheduledExecutor,并将其包装为ConcurrentTaskScheduler实例,存储在taskScheduler变量中。
处理触发器任务(TriggerTask)
处理触发器任务(TriggerTask):如果存在triggerTasks集合,函数会遍历这个集合中的每个触发器任务,并调用scheduleTriggerTask(task)方法来安排任务。安排后的任务会被添加到结果列表中。
进入scheduleTriggerTask(TriggerTask task)
方法
最后,如果任务是新创建的(newTask为true),返回ScheduledTask对象,否则返回null,表示任务已经存在且无需再次安排。
进入ConcurrentTakScheduler
类找到对应trigger任务的schedule(Runnable task, Trigger trigger)
方法,为什么会进入ConcurrentTakScheduler的找对应的schedule方法?是因为我们前面设置的任务调度器(taskScheduler)。创建一个新的SingleThreadScheduledExecutor,并将其包装为ConcurrentTaskScheduler实例
根据Trigger对象(任务执行时间的触发器,决定任务何时被调度执行)来计划执行一个Runnable任务。
返回我们自定义的配置类中
进入ReschedulingRunnable的schedule()方法
安排一个任务在未来特定时间执行。
@Nullable
public ScheduledFuture<?> schedule() {
synchronized (this.triggerContextMonitor) {// 1. 同步访问triggerContextMonitor,保证线程安全
// 2. 根据触发器和上下文计算任务的下次执行时间
this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
if (this.scheduledExecutionTime == null) { // 3. 如果没有下次执行时间,返回null
return null;
}
// 4. 计算从当前时间到下次执行时间的初始延迟
long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();
// 5. 使用executor安排任务在初始延迟后执行
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this; // 6. 返回ScheduledFuture对象,用于跟踪任务状态和取消任务
}
}
完成安排任务,并设置时间间隔,最后将待执行的任务放入this.scheduledTasks
中
处理cron任务(CronTask)
处理cron任务(CronTask):对于cronTasks集合中的每个Cron任务,函数调用scheduleCronTask(task)方法,依据cron表达式来安排任务,并将结果添加到结果列表。
处理固定速率任务(IntervalTask)
处理固定速率任务(IntervalTask):如果fixedRateTasks不为空,函数会遍历这个集合,对每个任务调用scheduleFixedRateTask(task),安排以固定速率执行的任务,并将结果保存。
处理固定延迟任务(IntervalTask)
处理固定延迟任务(IntervalTask):最后,对于fixedDelayTasks中的每个任务,调用scheduleFixedDelayTask(task),安排执行完一次后等待固定延迟时间再执行的任务,并添加到结果列表。
ScheduledTaskRegistrar小结
ScheduledTaskRegistrar
的scheduleTasks方法
主要目的是配置和安排各种类型的后台任务,确保它们能够按照指定的时间规则(如cron表达式、固定速率或固定延迟)在后台正确执行。
ReschedulingRunnable
最终安排好了任务,进入ReschedulingRunnable
的重写的run
方法,这个方法执行完,即表明本次定时任务已经完成,根据执行结果和外部条件动态调整后续执行计划,是实现定时任务管理和调度的关键部分。
@Override
public void run() {
//记录实际执行时间,拿到任务开始执行的确切时间
Date actualExecutionTime = new Date(this.triggerContext.getClock().millis());
super.run(); //调用父类的run方法。这里假设父类的run方法包含了该任务的核心处理逻辑或进一步的委托调用
//记录完成时间:在父类的run方法执行完毕后,再次获取当前时间作为任务的完成时间completionTime。这用于计算任务执行的持续时间。
Date completionTime = new Date(this.triggerContext.getClock().millis());
synchronized (this.triggerContextMonitor) { //同步并更新上下文
//状态检查,确保了任务有一个预定的执行时间,执行逻辑的前提
Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
//更新triggerContext上下文,包括预定执行时间、实际执行时间和完成时间,到这一步本次任务已经执行完了
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
//条件性调度: 判断当前任务的未来执行(通过obtainCurrentFuture()获得)有没有被取消
if (!obtainCurrentFuture().isCancelled()) { //根据执行结果和外部条件动态调整后续执行计划
schedule(); //安排下一次任务执行
}
}
}
再次返回我们自定义配置里,执行下一次的任务调度
进入ReschedulingRunnable的schedule()方法
最后会不断循环执行的我们的任务
最后结果
最终结论
- 使用
继承SchedulingConfigurer接口
配置动态定时任务的方式时,主动或者被动抛异常都会终止本次任务的调度,但是不会影响该任务的下一次执行调度 - 但是如果我们配置的
configureTasks
方法里面有多个业务方法,其中一个业务方法抛异常,本次任务的调度会马上结束,其它未执行的业务方法将不被执行,所以我们使用定时任务调度实现多个业务方法的时候,需要避免任一出现问题,否则,这次定时任务白忙活了。或者最好一个定时任务,一个业务方法,专人专事
本次SchedulingConfigurer源码初识:理解定时任务抛异常终止本次调度,但不会影响下一次执行调度文章到此结束,创作不易,望我佬们三连支持一下