Scheduling模块
Scheduling
模块是spring-context
依赖下的一个包org.springframework.scheduling
:
这个模块的类并不多,有四个子包:
-
顶层包的定义了一些通用接口和异常。
-
org.springframework.scheduling.annotation
:定义了调度、异步任务相关的注解和解析类,常用的注解如@Async
、@EnableAsync
、@EnableScheduling
和@Scheduled
。 -
org.springframework.scheduling.concurrent
:定义了调度任务执行器和相对应的FactoryBean
。 -
org.springframework.scheduling.config
:定义了配置解析、任务具体实现类、调度任务XML
配置文件解析相关的解析类。 -
org.springframework.scheduling.support
:定义了反射支持类、Cron
表达式解析器等工具类。
在SpringBoot
中引入spring-boot-starter-web
已经集成了spring-context
.
开启Scheduling
模块支持只需要在某一个配置类中添加@EnableScheduling
注解即可,一般为了明确模块的引入,建议在启动类中使用此注解,如:
同时一般还会引入@EnableSchedulerLock
, 这是一个轻量级的分布级锁, 下文再叙述.
Scheduling模块的工作流程
这个图描述了Scheduling
模块的工作流程,这里分析一下非XML
配置下的流程(右边的分支):
-
通过注解
@EnableScheduling
中的@Import
引入了SchedulingConfiguration
,而SchedulingConfiguration
中配置了一个类型为ScheduledAnnotationBeanPostProcessor
名称为org.springframework.context.annotation.internalScheduledAnnotationProcessor
的Bean
,这里有个常见的技巧,Spring
内部加载的Bean
一般会定义名称为internalXXX
,Bean
的role
会定义为ROLE_INFRASTRUCTURE = 2
。 -
Bean
后置处理器ScheduledAnnotationBeanPostProcessor
会解析和处理每一个符合特定类型的Bean
中的@Scheduled
注解(注意@Scheduled
只能使用在方法或者注解上),并且把解析完成的方法封装为不同类型的Task
实例,缓存在ScheduledTaskRegistrar
中的。ScheduledAnnotationBeanPostProcessor
的属性://string属性解析器,用来解析${}对应的配置文件的属性,aware接口注入
-
ScheduledAnnotationBeanPostProcessor
中的钩子接口方法afterSingletonsInstantiated()
在所有单例初始化完成之后回调触发,在此方法中设置了ScheduledTaskRegistrar
中的任务调度器(TaskScheduler
或者ScheduledExecutorService
类型)实例,并且调用ScheduledTaskRegistrar#afterPropertiesSet()
方法添加所有缓存的Task
实例到任务调度器中执行。
任务调度器
Scheduling
模块支持TaskScheduler
或者ScheduledExecutorService
类型的任务调度器,而ScheduledExecutorService
其实是JDK
并发包java.util.concurrent
的接口,一般实现类就是调度线程池ScheduledThreadPoolExecutor
。 实际上,ScheduledExecutorService
类型的实例最终会通过适配器模式转变为ConcurrentTaskScheduler
,所以这里只需要分析TaskScheduler
类型的执行器。
-
ThreadPoolTaskScheduler
:基于线程池实现的任务执行器,这个是最常用的实现,底层依赖于ScheduledThreadPoolExecutor
实现。 -
ConcurrentTaskScheduler
:TaskScheduler
接口和ScheduledExecutorService
接口的适配器,如果自定义一个ScheduledThreadPoolExecutor
类型的Bean
,那么任务执行器就会适配为ConcurrentTaskScheduler
。 -
DefaultManagedTaskScheduler
:JDK7
引入的JSR-236
的支持,可以通过JNDI
配置此调度执行器,一般很少用到,底层也是依赖于ScheduledThreadPoolExecutor
实现。
也就是说,内置的三个调度器类型底层都依赖于JUC
调度线程池ScheduledThreadPoolExecutor
。这里分析一下顶层接口org.springframework.scheduling.TaskScheduler
提供的功能
public interface TaskScheduler {
// 调度一个任务,通过触发器实例指定触发时间周期
ScheduledFuture<?> schedule(Runnable task, Trigger trigger);
// 指定起始时间调度一个任务 - 单次执行
ScheduledFuture<?> schedule(Runnable task, Date startTime);
// 指定固定频率调度一个任务,period的单位是毫秒
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period);
// 指定起始时间和固定频率调度一个任务,period的单位是毫秒
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period);
// 指定固定延迟间隔调度一个任务,delay的单位是毫秒
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay);
// 指定起始时间和固定延迟间隔调度一个任务,delay的单位是毫秒
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
}
Task的分类
Scheduling
模块中支持不同类型的任务,主要包括下面的3种(解析的优先顺序也是如下):
-
Cron
表达式任务,支持通过Cron
表达式配置执行的周期,对应的任务类型为org.springframework.scheduling.config.CronTask
。 -
固定延迟间隔任务,也就是上一轮执行完毕后间隔固定周期再执行本轮,依次类推,对应的的任务类型为
org.springframework.scheduling.config.FixedDelayTask
。 -
固定频率任务,基于固定的间隔时间执行,不会理会上一轮是否执行完毕本轮会照样执行,对应的的任务类型为
org.springframework.scheduling.config.FixedRateTask
。
核心流程分析
在SpringBoot
注解体系下,Scheduling
模块的所有逻辑基本在ScheduledAnnotationBeanPostProcessor
和ScheduledTaskRegistrar
中。一般来说,一个类实现的接口代表了它能提供的功能,先看ScheduledAnnotationBeanPostProcessor
实现的接口:
-
ScheduledTaskHolder
接口:返回Set<ScheduledTask>
,表示持有的所有任务实例。 -
MergedBeanDefinitionPostProcessor
接口:Bean
定义合并时回调,预留空实现,暂时不做任何处理。 -
BeanPostProcessor
接口:也就是MergedBeanDefinitionPostProcessor
的父接口,Bean
实例初始化前后分别回调,其中,后回调的postProcessAfterInitialization()
方法就是用于解析@Scheduled
和装载ScheduledTask
,需要重点关注此方法的逻辑。 -
DestructionAwareBeanPostProcessor
接口:具体的Bean
实例销毁的时候回调,用于Bean
实例销毁的时候移除和取消对应的任务实例。 -
Ordered
接口:用于Bean
加载时候的排序,主要是改变ScheduledAnnotationBeanPostProcessor
在BeanPostProcessor
执行链中的顺序。 -
EmbeddedValueResolverAware
接口:回调StringValueResolver
实例,用于解析带占位符的环境变量属性值。 -
BeanNameAware
接口:回调BeanName
。 -
BeanFactoryAware
接口:回调BeanFactory
实例,具体是DefaultListableBeanFactory
,也就是熟知的IOC
容器。 -
ApplicationContextAware
接口:回调ApplicationContext
实例,也就是熟知的Spring
上下文,它是IOC
容器的门面,同时是事件广播器、资源加载器的实现等等。 -
SmartInitializingSingleton
接口:所有单例实例化完毕之后回调,作用是在持有的applicationContext
为NULL
的时候开始调度所有加载完成的任务,这个钩子接口十分有用,笔者常用它做一些资源初始化工作。 -
ApplicationListener
接口:监听Spring
应用的事件,具体是ApplicationListener<ContextRefreshedEvent>
,监听上下文刷新的事件,如果事件中携带的ApplicationContext
实例和ApplicationContextAware
回调的ApplicationContext
实例一致,那么在此监听回调方法中开始调度所有加载完成的任务,也就是在ScheduledAnnotationBeanPostProcessor
这个类中,SmartInitializingSingleton
接口的实现和ApplicationListener
接口的实现逻辑是互斥的。 -
DisposableBean
接口:当前Bean
实例销毁时候回调,也就是ScheduledAnnotationBeanPostProcessor
自身被销毁的时候回调,用于取消和清理所有的ScheduledTask
。
钩子接口在SpringBoot体系中可以按需使用,了解回调不同钩子接口的回调时机,可以在特定时机完成达到理想的效果。
postProcessAfterInitialization()
@Scheduled
注解的解析集中在postProcessAfterInitialization()
方法:
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 忽略AopInfrastructureBean、TaskScheduler和ScheduledExecutorService三种类型的Bean
if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
bean instanceof ScheduledExecutorService) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
// 获取Bean的用户态类型,例如Bean有可能被CGLIB增强,这个时候要取其父类
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
// nonAnnotatedClasses存放着不存在@Scheduled注解的类型,缓存起来避免重复判断它是否携带@Scheduled注解的方法
if (!this.nonAnnotatedClasses.contains(targetClass) &&
AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
// 因为JDK8之后支持重复注解,因此获取具体类型中Method -> @Scheduled的集合,也就是有可能一个方法使用多个@Scheduled注解,最终会封装为多个Task
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
method, Scheduled.class, Schedules.class);
return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
});
// 解析到类型中不存在@Scheduled注解的方法添加到nonAnnotatedClasses缓存
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(targetClass);
if (logger.isTraceEnabled()) {
logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
}
}
else {
// Method -> @Scheduled的集合遍历processScheduled()方法进行登记
annotatedMethods.forEach((method, scheduledMethods) ->
scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
if (logger.isTraceEnabled()) {
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
}
}
return bean;
}
postScheduled()
processScheduled(Scheduled scheduled, Method method, Object bean)
就是具体的注解解析和Task
封装的方法:
// Runnable适配器 - 用于反射调用具体的方法,触发任务方法执行
public class ScheduledMethodRunnable implements Runnable {
private final Object target;
private final Method method;
public ScheduledMethodRunnable(Object target, Method method) {
this.target = target;
this.method = method;
}
....// 省略无关代码
// 这个就是最终的任务方法执行的核心方法,抑制修饰符,然后反射调用