最近的新项目和数据同步相关,有定时调度的需求。之前一直有使用过Quartz、XXL-Job、Easy Scheduler等调度框架,后来越发觉得这些框架太重量级了,于是想到了Spring内置的Scheduling模块。而原生的Scheduling模块只是内存态的调度模块,不支持任务的持久化或者配置(配置任务通过@Scheduled注解进行硬编码,不能抽离到类之外),因此考虑理解Scheduling模块的底层原理,并且基于此造一个简单的轮子,使之支持调度任务配置:通过配置文件或者JDBC数据源。
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表达式解析器等工具类。
如果想单独使用Scheduling,只需要引入spring-context这个依赖。但是现在流行使用SpringBoot,引入spring-boot-starter-web已经集成了spring-context,可以直接使用Scheduling模块,笔者编写本文的时候(2020-03-14)SpringBoot的最新版本为2.2.5.RELEASE,可以选用此版本进行源码分析或者生产应用:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.boot.version>2.2.5.RELEASE</spring.boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>开启Scheduling模块支持只需要在某一个配置类中添加@EnableScheduling注解即可,一般为了明确模块的引入,建议在启动类中使用此注解,如:
@EnableScheduling @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } 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中的钩子接口方法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实现。