在之前一篇文章Spring Boot定时任务,我们已经了解了如何在Spring Boot中创建并管理定时任务,今天我们通过源码来看下Spring Boot中的具体实现。
EnableScheduling注解
要想在Spring Boot中创建定时任务,就必须要加上这个注解,那么这个注解到底起到什么作用呢?首先来看下EnableScheduling的详细定义
/* @see Scheduled
* @see SchedulingConfiguration
* @see SchedulingConfigurer
* @see ScheduledTaskRegistrar
* @see Trigger
* @see ScheduledAnnotationBeanPostProcessor
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
可以看到EnableScheduling通过@import这个注解,引入了SchedulingConfiguration这个类。那么这个类又是干啥的呢?
SchedulingConfiguration
以下是SchedulingConfiguration的具体实现,可以看到这个类的主要目的在于注入了ScheduledAnnotationBeanPostProcessor这个类,那么这个类又干了啥呢?
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
ScheduledAnnotationBeanPostProcessor
上述是对Spring Boot文档对ScheduledAnnotationBeanPostProcessor的描述,可以归纳为三点:
- ScheduledAnnotationBeanPostProcessor会通过TaskScheduler来调用所有标识了@Scheduled的方法
- ScheduledAnnotationBeanPostProcessor会自动检测容器中的任何SchedulingConfigurer实例
- 可以通过@EnableScheduling注解或者task:annotation-driven标签来注册ScheduledAnnotationBeanPostProcessor
下面我们来看下ScheduledAnnotationBeanPostProcessor是如何实现1,2两点的
postProcessAfterInitialization
ScheduledAnnotationBeanPostProcessor实现了BeanPostProcessor,了解Spring的同学可能会对这个类比较熟悉,它可以在Bean被实例化前后执行一些操作
public interface BeanPostProcessor {
// 在实例化之前对bean进行处理
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 在实例化之后对bean进行处理
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
下图展示了postProcessAfterInitialization方法主要的实现逻辑
ScheduledAnnotationBeanPostProcessor通过postProcessAfterInitialization方法检测Bean中是否存在配置@Scheduled注解的方法。对于配置了@Scheduled注解的方法,ScheduledAnnotationBeanPostProcessor通过processScheduled方法做了进一步处理,我们来看下processScheduled的具体实现。
processScheduled
通过源码的注释,我们可以清晰的看到processScheduled的处理流程。
- 根据传入的Bean以及配置了@Scheduled注解的方法创建线程
- 解析@Scheduled的initialDelay和initialDelayString参数
- 若@Scheduled配置了cron参数,则解析cron并生成ScheduledTask
- 若@Scheduled配置了fixed rate或者fixed delay,则根据配置生成ScheduledTask
- 保存生成的ScheduledTask
ScheduledTaskRegistrar
到目前为止了,我们已经了解ScheduledAnnotationBeanPostProcessor会将配置了@Scheduled注解的方法转化为ScheduledTask。
其中ScheduledTaskRegistrar就负责根据配置生成的ScheduledTask。
可以看到ScheduledTaskRegistrar维护了各类型的Task,包括TriggerTask,CronTask,fixedRateTask,fixedDelayTask。最终这些Task都将转化为ScheduledTask
Scheduler
ScheduledTaskRegistrar负责维护ScheduledTask,那么该由谁来调度这些Task呢?答案就是Scheduler。
ScheduledAnnotationBeanPostProcessor在初始化的过程中会为ScheduledTaskRegistrar注入对应的Scheduler,具体流程就在finishRegistration方法中。
最终在resolveSchedulerBean方法中生成Scheduler
下面的流程图展示了具体过程
最终加载ScheduledTask,其实也就是调用了ScheduledTaskRegistrar中的scheduleTasks方法
我们可以看到如果ScheduledTaskRegistrar中的taskScheduler为null,它会生成一个SingleThreadScheduledExecutor作为taskScheduler。
SchedulingConfigurer
在介绍ScheduledAnnotationBeanPostProcessor的时候,我们提到过它会自动检测容器中的任何SchedulingConfigurer实例,那么SchedulingConfigurer实例又是起到什么作用的呢?
简单来说我们可以通过SchedulingConfigurer对ScheduledTaskRegistrar进行配置。例如我们可以指定ScheduledTaskRegistrar的taskScheduler,包括设置其线程池的大小等。
下面来看具体示例,我们首先定义一个测试类
@Component
@Slf4j
public class ScheduleTaskTest {
@Scheduled(fixedDelay = 3000)
public void print(){
log.info("schedule task run");
}
}
然后,我们实现SchedulingConfigurer接口,并自定义taskScheduler
@Component
public class MySchedulingConfigurer implements SchedulingConfigurer {
private ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 设置线程池大小
taskScheduler.setPoolSize(100);
//设置线程工厂
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("scheduler-%d")
.build();
taskScheduler.setThreadFactory(threadFactory);
taskScheduler.initialize();
// 设置自定义的taskScheduler
taskRegistrar.setTaskScheduler(taskScheduler);
}
}
运行程序,我们可以看到,任务是通过自定义的taskScheduler进行调度的
总结
下面的流程图展示了Spring Boot创建定时任务的完整流程