## 前提
最近的新项目和数据同步相关,有定时调度的需求。之前一直有使用过`Quartz`、`XXL-Job`、`Easy Scheduler`等调度框架,后来越发觉得这些框架太重量级了,于是想到了`Spring`内置的`Scheduling`模块。而原生的`Scheduling`模块只是内存态的调度模块,不支持任务的持久化或者配置(配置任务通过`@Scheduled`注解进行硬编码,不能抽离到类之外),因此考虑理解`Scheduling`模块的底层原理,并且基于此造一个简单的轮子,使之支持调度任务配置:通过配置文件或者`JDBC`数据源。
## Scheduling模块
`Scheduling`模块是`spring-context`依赖下的一个包`org.springframework.scheduling`:
![](https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202004/s-b-s-f-1.png)
这个模块的类并不多,有四个子包:
- 顶层包的定义了一些通用接口和异常。
- `org.springframework.scheduling.annotation`:定义了调度、异步任务相关的注解和解析类,常用的注解如`@Async`、`@EnableAsync`、`@EnableScheduling`和`@Scheduled`。
- `org.springframework.scheduling.concurrent`:定义了调度任务执行器和相对应的`FactoryBean`。
- `org.springframework.scheduling.config`:定义了配置解析、任务具体实现类、调度任务`XML`配置文件解析相关的解析类。
- `org.springframework.scheduling.support`:定义了反射支持类、`Cron`表达式解析器等工具类。
如果想单独使用`Scheduling`,只需要引入`spring-context`这个依赖。但是现在流行使用`SpringBoot`,引入`spring-boot-starter-web`已经集成了`spring-context`,可以直接使用`Scheduling`模块,笔者编写本文的时候(`2020-03-14`)`SpringBoot`的最新版本为`2.2.5.RELEASE`,可以选用此版本进行源码分析或者生产应用:
```xml
UTF-8
1.8
1.8
2.2.5.RELEASE
org.springframework.boot
spring-boot-dependencies
${spring.boot.version}
pom
import
org.springframework.boot
spring-boot-starter-web
```
开启`Scheduling`模块支持只需要在某一个配置类中添加`@EnableScheduling`注解即可,一般为了明确模块的引入,建议在启动类中使用此注解,如:
```java
@EnableScheduling
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
```
## Scheduling模块的工作流程
![](https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202004/s-b-s-f-2.png)
这个图描述了`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`中的钩子接口方法`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`提供的功能(笔者已经把功能一致的`default`方法暂时移除):
```java
// 省略一些功能一致的default方法
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种(解析的优先顺序也是如下):
1. `Cron`表达式任务,支持通过`Cron`表达式配置执行的周期,对应的任务类型为`org.springframework.scheduling.config.CronTask`。
2. 固定延迟间隔任务,也就是上一轮执行完毕后间隔固定周期再执行本轮,依次类推,对应的的任务类型为`org.springframework.scheduling.config.FixedDelayTask`。
3. 固定频率任务,基于固定的间隔时间执行,**不会理会上一轮是否执行完毕本轮会照样执行**,对应的的任务类型为`org.springframework.scheduling.config.FixedRateTask`。
关于这几类`Task`,举几个简单的例子。`CronTask`是通过`cron`表达式指定执行周期的,并且**不支持延迟执行**,可以使用特殊字符`-`禁用任务执行:
```java
// 注解声明式使用 - 每五秒执行一次,不支持initialDelay
@Scheduled(cron = "*/5 * * * * ?")
public void processTask(){
}
// 注解声明式使用 - 禁止任务执行
@Scheduled(cron = "-")
public void processTask(){
}
// 编程式使用
public class Tasks {
static DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();
CronTask cronTask = new CronTask(() -> {
System.out.println(String.format("[%s] - CronTask触发...", F.format(LocalDateTime.now())));
}, "*/5 * * * * ?");
taskScheduler.schedule(cronTask.getRunnable(),cronTask.getTrigger());
Thread.sleep(Integer.MAX_VALUE);
}
}
// 某次执行输出结果
[2020-03-16 01:07:00] - CronTask触发...
[2020-03-16 01:07:05] - CronTask触发...
......
```
`FixedDelayTask`需要配置延迟间隔值(`fixedDelay`或者`fixedDelayString`)和可选的起始延迟执行时间(`initialDelay`或者`initialDelayString`),这里注意一点是`fixedDelayString`和`initialDelayString`都支持从`EmbeddedValueResolver`(简单理解为配置文件的属性处理器)读取和`Duration`(例如`P2D`就是`parses as 2 days`,表示86400秒)支持格式的解析:
```java
// 注解声明式使用 - 延迟一秒开始执行,延迟间隔为5秒
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
public void process(){
}
// 注解声明式使用 - spring-boot配置文件中process.task.fixedDelay=5000 process.task.initialDelay=1000
@Scheduled(fixedDelayString = "${process.task.fixedDelay}", initialDelayString = "${process.task.initialDelay}")
public void process(){
}
// 编程式使用
public class Tasks {
static DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();
FixedDelayTask fixedDelayTask = new FixedDelayTask(() -> {
System.out.println(String.format("[%s] - FixedDelayTask触发...", F.format(LocalDateTime.now())));
}, 5000, 1000);
Date startTime = new Date(System.currentTimeMillis() + fixedDelayTask.getInitialDelay());
taskScheduler.scheduleWithFixedDelay(fixedDelayTask.getRunnable(), startTime, fixedDelayTask.getInterval());
Thread.sleep(Integer.MAX_VALUE);
}
}
// 某次执行输出结果
[2020-03-16 01:06:12] - FixedDelayTask触发...
[2020-03-16 01:06:17] - FixedDelayTask触发...
......
```
`FixedRateTask`需要配置固定间隔值(`fixedRate`或者`fixedRateString`)和可选的起始延迟执行时间(`initialDelay`或者`initialDelayString`),这里注意一点是`fixedRateString`和`initialDelayString`都支持从`EmbeddedValueResolver`(简单理解为配置文件的属性处理器)读取和`Duration`(例如`P2D`就是`parses as 2 days`,表示86400秒)支持格式的解析:
```java
// 注解声明式使用 - 延迟一秒开始执行,每隔5秒执行一次
@Scheduled(fixedRate = 5000, initialDelay = 1000)
public void processTask(){
}
// 注解声明式使用 - spring-boot配置文件中process.task.fixedRate=5000 process.task.initialDelay=1000
@Scheduled(fixedRateString = "${process.task.fixedRate}", initialDelayString = "${process.task.initialDelay}")
public void process(){
}
// 编程式使用
public class Tasks {
static DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();
FixedRateTask fixedRateTask = new FixedRateTask(() -> {
System.out.println(String.format("[%s] - FixedRateTask触发...", F.format(LocalDateTime.now())));
}, 5000, 1000);
Date startTime = new Date(System.currentTimeMillis() + fixedRateTask.getInitialDelay());
taskScheduler.scheduleAtFixedRate(fixedRateTask.getRunnable(), startTime, fixedRateTask.getInterval());
Thread.sleep(Integer.MAX_VALUE);
}
}
// 某次执行输出结果
[2020-03-16 23:58:25] - FixedRateTask触发...
[2020-03-16 23:58:30] - FixedRateTask触发...
......
```
### 简单分析核心流程的源代码
在`SpringBoot`注解体系下,`Scheduling`模块的所有逻辑基本在`ScheduledAnnotationBeanPostProcessor`和`ScheduledTaskRegistrar`中。一般来说,一个类实现的接口代表了它能提供的功能,先看`ScheduledAnnotationBeanPostProcessor`实现的接口:
- `ScheduledTaskHolder`接口:返回`Set`,表示持有的所有任务实例。
- `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`,监听上下文刷新的事件,如果事件中携带的`ApplicationContext`实例和`ApplicationContextAware`回调的`ApplicationContext`实例一致,那么在此监听回调方法中开始调度所有加载完成的任务,也就是在`ScheduledAnnotationBeanPostProcessor`这个类中,`SmartInitializingSingleton`接口的实现和`ApplicationListener`接口的实现逻辑是**互斥**的。
- `DisposableBean`接口:当前`Bean`实例销毁时候回调,也就是`ScheduledAnnotationBeanPostProcessor`自身被销毁的时候回调,用于取消和清理所有的`ScheduledTask`。
**上面分析的钩子接口在SpringBoot体系中可以按需使用,了解回调不同钩子接口的回调时机,可以在特定时机完成达到理想的效果。**
`@Scheduled`注解的解析集中在`postProcessAfterInitialization()`方法:
```java
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> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup>) method -> {
Set 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;
}
```
`processScheduled(Scheduled scheduled, Method method, Object bean)`就是具体的注解解析和`Task`封装的方法:
```java
// 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;
}
....// 省略无关代码
// 这个就是最终的任务方法执行的核心方法,抑制修饰符,然后反射调用
@Override
public void run() {
try {
ReflectionUtils.makeAccessible(this.method);
this.method.invoke(this.target);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowRuntimeException(ex.getTargetException());
}
catch (IllegalAccessException ex) {
throw new UndeclaredThrowableException(ex);
}
}
}
// 通过方法所在Bean实例和方法封装Runnable适配器ScheduledMethodRunnable实例
protected Runnable createRunnable(Object target, Method method) {
Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");
Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
return new ScheduledMethodRunnable(target, invocableMethod);
}
// 这个方法十分长,不过逻辑并不复杂,它只做了四件事
// 0. 解析@Scheduled中的initialDelay、initialDelayString属性,适用于FixedDelayTask或者FixedRateTask的延迟执行
// 1. 优先解析@Scheduled中的cron属性,封装为CronTask,通过ScheduledTaskRegistrar进行缓存
// 2. 解析@Scheduled中的fixedDelay、fixedDelayString属性,封装为FixedDelayTask,通过ScheduledTaskRegistrar进行缓存
// 3. 解析@Scheduled中的fixedRate、fixedRateString属性,封装为FixedRateTask,通过ScheduledTaskRegistrar进行缓存
protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try {
// 通过方法宿主Bean和目标方法封装Runnable适配器ScheduledMethodRunnable实例
Runnable runnable = createRunnable(bean, method);
boolean processedSchedule = false;
String errorMessage =
"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
// 缓存已经装载的任务
Set tasks = new LinkedHashSet<>(4);
// Determine initial delay
// 解析初始化延迟执行时间,initialDelayString支持占位符配置,如果initialDelayString配置了,会覆盖initialDelay的值
long initialDelay = scheduled.initialDelay();
String initialDelayString = scheduled.initialDelayString();
if (StringUtils.hasText(initialDelayString)) {
Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
if (this.embeddedValueResolver != null) {
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = parseDelayAsLong(initialDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
}
}
// Check cron expression
// 解析时区zone的值,支持支持占位符配置,判断cron是否存在,存在则装载为CronTask
String cron = scheduled.cron();
if (StringUtils.hasText(cron)) {
String zone = scheduled.zone();
if (this.embeddedValueResolver != null) {
cron = this.embeddedValueResolver.resolveStringValue(cron);
zone = this.embeddedValueResolver.resolveStringValue(zone);
}
if (StringUtils.hasLength(cron)) {
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
TimeZone timeZone;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();
}
// 此方法虽然表面上是调度CronTask,实际上由于ScheduledTaskRegistrar不持有TaskScheduler,只是把任务添加到它的缓存中
// 返回的任务实例添加到宿主Bean的缓存中,然后最后会放入宿主Bean -> List映射中
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
}
}
}
// At this point we don't need to differentiate between initial delay set or not anymore
// 修正小于0的初始化延迟执行时间值为0
if (initialDelay < 0) {
initialDelay = 0;
}
// 解析fixedDelay和fixedDelayString,如果同时配置,fixedDelayString最终解析出来的整数值会覆盖fixedDelay,封装为FixedDelayTask
long fixedDelay = scheduled.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
String fixedDelayString = scheduled.fixedDelayString();
if (StringUtils.hasText(fixedDelayString)) {
if (this.embeddedValueResolver != null) {
fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
}
if (StringUtils.hasLength(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = parseDelayAsLong(fixedDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
// 此方法虽然表面上是调度FixedDelayTask,实际上由于ScheduledTaskRegistrar不持有TaskScheduler,只是把任务添加到它的缓存中
// 返回的任务实例添加到宿主Bean的缓存中,然后最后会放入宿主Bean -> List映射中
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
}
// 解析fixedRate和fixedRateString,如果同时配置,fixedRateString最终解析出来的整数值会覆盖fixedRate,封装为FixedRateTask
long fixedRate = scheduled.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
String fixedRateString = scheduled.fixedRateString();
if (StringUtils.hasText(fixedRateString)) {
if (this.embeddedValueResolver != null) {
fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
}
if (StringUtils.hasLength(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = parseDelayAsLong(fixedRateString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
// 此方法虽然表面上是调度FixedRateTask,实际上由于ScheduledTaskRegistrar不持有TaskScheduler,只是把任务添加到它的缓存中
// 返回的任务实例添加到宿主Bean的缓存中,然后最后会放入宿主Bean -> List映射中
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
}
// Check whether we had any attribute set
Assert.isTrue(processedSchedule, errorMessage);
// Finally register the scheduled tasks
synchronized (this.scheduledTasks) {
// 注册所有任务实例,这个映射Key为宿主Bean实例,Value为List,后面用于调度所有注册完成的任务
Set regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
regTasks.addAll(tasks);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalStateException(
"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
}
}
```
总的来说,这个方法做了四件事:
- 解析`@Scheduled`中的`initialDelay`、`initialDelayString`属性,适用于`FixedDelayTask`或者`FixedRateTask`的延迟执行。
- 优先解析`@Scheduled`中的`cron`属性,封装为`CronTask`,通过`ScheduledTaskRegistrar`进行缓存。
- 解析`@Scheduled`中的`fixedDelay`、`fixedDelayString`属性,封装为`FixedDelayTask`,通过`ScheduledTaskRegistrar`进行缓存。
- 解析`@Scheduled`中的`fixedRate`、`fixedRateString`属性,封装为`FixedRateTask`,通过`ScheduledTaskRegistrar`进行缓存。
`@Scheduled`修饰的某个方法如果同时配置了`cron`、`fixedDelay|fixedDelayString`和`fixedRate|fixedRateString`属性,意味着此方法同时封装为三种任务`CronTask`、`FixedDelayTask`和`FixedRateTask`。解析`xxString`值的使用,用到了`EmbeddedValueResolver`解析字符串的值,支持占位符,这样可以直接获取环境配置中的占位符属性(基于`SPEL`的特性,甚至可以支持嵌套占位符)。解析成功的所有任务实例存放在`ScheduledAnnotationBeanPostProcessor`的一个映射`scheduledTasks`中:
```java
// 宿主Bean实例 -> 解析完成的任务实例Set
private final Map