@EnableScheduling
众所周知,SpringBoot中启用某些组件通常使用@EnableXXX开头的注解,比如@EnableCaching开启本地缓存,@EnableRetry开启重试,同样开启定时任务@EnableScheduling。那么@EnableScheduling究竟做了啥呢?
这里我们可以看到@EnableScheduling注解上,又标注了一个@Import(SchedulingConfiguration.class)注解,熟悉IOC容器的同学应该知道,它是为我们注入Bean的。接下来我们看看SchedulingConfiguration这个类做了什么。
SchedulingConfiguration
在这个类里面,使用@Bean标记了一个类ScheduledAnnotationBeanPostProcessor,看到BeanPostProcessor结尾的类,我们应该能想到它是接口BPP的实现类,并且会在初始化Bean的前后调用前置和后置处理器。接下来看下ScheduledAnnotationBeanPostProcessor类做了哪些操作。
ScheduledAnnotationBeanPostProcessor
这里我们可以看到几个比较关键的类
- ApplicationContextAware注入ApplicationContext上下文
- SmartInitializingSingleton对单例Bean实例化之后扩展
- ApplicationListener监听容器刷新事件
- DisposableBean销毁Bean的时候回调
执行顺序应该是1->2->3->4,所以这里会监听到容器刷新的方法,finishRegistration()被调用。在方法里面首先会去查找容器中存在的TaskScheduler,获取一个TaskScheduler并设置到ScheduledTaskRegistrar中,如果没有获取到就打印info日志。然后再调用ScheduledTaskRegistrar的方法afterPropertiesSet()。
ScheduledTaskRegistrar
这里的cronTasks,fixedRateTasks,fixedDelayTasks是ScheduledAnnotationBeanPostProcessor的后置处理器解析@Scheduled注解得到的,triggerTasks是通过实现SchedulingConfigurer接口添加的。通常我们会使用cron表达式,所以这里重点关注scheduleCronTask()方法。
前面我们已经拿到了一个TaskScheduler,所以这里的taskScheduler不为空,会执行schedule方法。但是我们可以看到TaskScheduler是一个接口,这里肯定是它的某个实现类,究竟是哪个呢?在这里先埋下个疑问,我们进去ThreadPoolTaskScheduler先看看。
ThreadPoolTaskScheduler-1
这里拿到了一个ScheduledExecutorService,并new了一个ReschedulingRunnable类,传递进去了一个executor,调用了它的schedule()方法.
ReschedulingRunnable
看到这里我们就清楚了,通过传递进来的executor执行器,去周期性执行我们的任务。在我们的印象中ScheduledThreadPoolExecutor线程池是执行周期性任务的。任务执行的慢,肯定和这个executor执行器有关,这里我们往回推理。executor来源于ThreadPoolTaskScheduler中scheduledExecutor。ThreadPoolTaskScheduler类继承了ExecutorConfigurationSupport。
ExecutorConfigurationSupport
ExecutorConfigurationSupport实现了InitializingBean,在初始化之后会调用afterPropertiesSet(),然后调用initialize(),最后调用子类ThreadPoolTaskScheduler的initializeExecutor()和createExecutor()。
ThreadPoolTaskScheduler-2
会不会和这里的poolSize有关呢?往下看。
看到这里是不是有种恍然大悟的感觉,创建了ScheduledThreadPoolExecutor线程池,最大线程数是Integer的最大值,队列是DelayedWorkQueue的无界队列。关于线程池我们都知道,在没有达到核心线程数的时候,会继续创建线程,达到核心线程数之后添加到队列。如果核心线程数很小,这个无界队列很大,当任务到了执行时间,但是没有线程来做工,是不是所有的任务都会被拖慢呢。有了初步的猜想,那么前面的poolSize究竟是多少。可以看到这个poolSize默认是1,恰好符合我们的猜想。但是poolSize大小是可以被改变的,究竟有没有被改大呢?ThreadPoolTaskScheduler又是在哪里实例化的呢?
不知道你们是否还记得SpringBoot的自动化配置,我们来看看。在org.springframework.boot.autoconfigure.EnableAutoConfiguration下面有这样一个类TaskSchedulingAutoConfiguration。
TaskSchedulingAutoConfiguration
实例化Bean的时候,TaskSchedulingProperties中Pool里面的size=1,配置文件中又没有对应的配置去改变值的大小,所以设置到ThreadPoolTaskScheduler中的poolSize就是1。