源码分析Spring定时任务Quartz执行全过程源码解读
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
源码分析Spring定时任务Quartz执行全过程源码解读
一、前言介绍
在日常开发中经常会用到定时任务,用来;库表扫描发送MQ、T+n账单结算、缓存数据更新、秒杀活动状态变更,等等。
因为有了Spring的Schedule极大的方便了我们对这类场景的使用。
那么,除了应用你还了解它
1. 默认初始化多少个任务线程
2. JobStore有几种实现,你平时用的都是哪个
3. 一个定时任务的执行流程简述下
蒙圈了吧,是不感觉平时只是使用了,根本没关注过这些。
有种冲动赶紧搜索答案吧!但只是知道答案是没有多少意义的,扛不住问不说,也不了解原理。
所以,如果你想真的提升自己技能,还是要从根本搞定。
二、案例工程
为了更好的做源码分析,我们将平时用的定时任务服务单独抽离出来。
工程下载,关注公众号:bugstack虫洞栈,回复:源码分析
1itstack-demo-code-schedule 2└── src 3 ├── main 4 │ ├── java 5 │ │ └── org.itstack.demo 6 │ │ ├── DemoTask.java 7 │ │ └── JobImpl.java 8 │ └── resources 9 │ ├── prop
三、环境配置
1. JDK 1.8
2. IDEA 2019.
3.1
3. Spring
4.3.24.RELEASE
4. quartz 2.3.2 {不同版本略有代码差异}
四、源码分析
1<dependency>2 <groupId>org.quartz-scheduler</groupId>3 <artifactId>quartz</artifactId>4 <version>2.3.2</version>5</dependency>
依赖于Spring版本升级quartz选择2.3.2,同时如果你如本文案例中所示使用xml配置任务。
那么会有如下更改;
Spring 3.x/org.springf ramework.scheduling.quart.CronTriggerBean
1 <bean id="taskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
2 <property name="jobDetail" ref="taskHandler"/>
3 <property name="cronExpression" value="0/5 * * * * ?"/>
4 </bea
Spring 4.x/org.springf ramework.scheduling.quartz.CronTriggerFactoryBean
1 <bean id="taskTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
2 <property name="jobDetail" ref="taskHandler"/>
3 <property name="cronExpression" value="0/5 * * * * ?"/>在正式分析前,可以看下quartz的默认配置,很多初始化动作都要从这里取得参数,同样你可以配置自己的配置文件。
例如,当你的任务很多时,默认初始化的10个线程组不满足你的业务需求,就可以按需调整。
quart.properties
1# Default Properties file for use by StdSchedulerFactory 2# to create a Quartz Scheduler Instance, if a different 3# properties file is not explicitly specified. 4# 5 6org.quartz.scheduler.instanceName: DefaultQua
1. 从一个简单案例开始
平时我们使用Schedule基本都是注解或者xml配置文件,但是为了可以更简单的分析代码,我们从一个简单的Demo入手,放到main函数中。
DemoTask.java & 定义一个等待被执行的任务
1public class DemoTask {23 private Logger logger = LoggerFactory.getLogger(DemoTask.class);45 public void execute() throws Exception{6 ("定时处理用户信息任务:0/5 * * * * ?");7 }89} MyTask.java & 测试类,将配置在xml中的代码抽离出来
1public class MyTask { 2 3 public static void main(String[] args) throws Exception { 4 5 DemoTask demoTask = new DemoTask(); 6 7 // 定义了;执行的内容 8 MethodInvokingJobDetailFactoryBean m 23 SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();24 schedulerFactoryBean.setTriggers(cronTriggerFactoryBean.getObject());25 schedulerFactoryBean.setAutoStartup(如果一切顺利,那么会有如下结果:
12020-01-04 10:47:16.369 [main] INFO org.quartz.impl.StdSchedulerFactory[1220] - Using default implementation for ThreadExecutor 22020-01-04 10:47:16.421 [main] INFO org.quartz.core.SchedulerSignalerImp 04 10:47:16.426 [main] INFO org.quartz.core.QuartzScheduler[2293] - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@3e9b1010162020-01-04 10:47:16.651 [main] INFO org.quar
2. 定义执行内容(M et hod Invoking J ob D et ailF act ory B ean)
1// 定义了;执行的内容2MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();3methodInvokingJobDetailFactoryBean.setTargetObject(dem 这块内容主要将我们的任务体(即待执行任务DemoTask)交给MethodInvokingJobDetailFactoryBean管理,首先设置必要信息;
targetObject:目标对象bean,也就是demoTask
targetMethod:目标方法name,也就是execute
concurrent:是否并行执行,非并行执行任务,如果上一个任务没有执行完,下一刻不会执行
name:xml配置非必传,源码中可以获取beanName
最后我们通过手动调用 afterPropertiesSet() 来模拟初始化。
如果我们的类是交给 Spring 管理的,那么在实现了 InitializingBean 接口的类,在类配置信息加载后会自动执行 afterPropertiesSet() 。
一般实现了 Initializin MethodInvokingJobDetailFactoryBean.af terPropertiesSet()
1public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException { 2 prepare(); 3 // Use specific name if given, else fall back to bean name. 4 String name = ( != null ? this.源码168行:根据是否并行执行选择任务类,这两个类都是MethodInvokingJobDetailFactoryBean的内部类,非并行执行的StatefulMethodInvokingJob只是继承MethodInvokingJob添加了标记注解。
源码171行:创建JobDetailImpl,添加任务明细信息,注意这类的jdi.setJobClass((Class) jobClass)实际就是MethodInvokingJob。
MethodInvokingJob也是我们最终要反射调用执行的内容。
源码177行:初始化任务后赋值给this.jobDetail = jdi,也就是最终的类对象
MethodInvokingJobDetailFactoryBean.getObject()
1@Override2public JobDetail getObject() {3 return this.jobDetail;4}
源码:220行:获取对象时返回 this.jobDetail,这也就解释了为什么 MethodInvokingJobDetailFactoryBean 初始化后直接赋值给了一个 JobDetail ;
3. 定义执行计划(C ronT riggerF act ory B eann)
1// 定义了;执行的计划2CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();3cronTriggerFactoryBean.setJobDetail(methodInvokingJobDetailFactoryBean.getObject());4cronTrigge 这一块主要定义任务的执行计划,并将任务执行内容交给 CronTriggerFactoryBean 管理,同时设置必要信息;
jobDetail:设置任务体,xml 中可以直接将对象赋值,硬编码中设置执行的 JobDetail 对象信息。
也就是我们上面设置的 JobDetailImpl ,通过 getObject() 获取出来。
cronExpression:计划表达式;秒、分、时、日、月、周、年
CronTriggerFactoryBean.af terPropertiesSet()
1@Override 2public void afterPropertiesSet() throws ParseException { 3 4 // ... 校验属性信息 5 6 CronTriggerImpl cti = new CronTriggerImpl(); 7 cti.setName(); 8 cti.setGroup(this.group); 9 if (this.jo 源码237行:创建触发器 CronTriggerImpl 并设置相关属性信息
源码245行:生成执行计划类 cti.setCronExpression(this.cronExpression);
1public void setCronExpression(String cronExpression) throws ParseException {2 TimeZone origTz = getTimeZone();3 this.cronEx = new CronExpression(cronExpression);4 this.cronEx.setTimeZone(origTz CronExpression.java & 解析Cron表达式
1protected void buildExpression(String expression) throws ParseException { 2 expressionParsed = true; 3 try { // ... 初始化 TreeSet xxx = new TreeSet<Integer>(); 4 5 int exprOn = SECOND; 6 StringT Cron表达式有7个字段,CronExpression 把7个字段解析为7个 TreeSet 对象。
填充TreeSet对象值的时候,表达式都会转换为起始值、结束值和增量的计算模式,然后计算出匹配的值放进TreeSet对象
CronTriggerFactoryBean.getObject()
1@Override2public CronTrigger getObject() {3 return this.cronTrigger;4}
源码257行:获取对象时返回 this.cronTrigger ,也就是 CronTriggerImpl 对象
4. 调度执行计划(S chedulerF act ory B ean)
1// 调度了;执行的计划(scheduler)2SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();3schedulerFactoryBean.setTriggers(cronTriggerFactoryBean.getObject());4schedulerFactoryBean 这一部分如名字一样调度工厂,相当于一个指挥官,可以从全局做调度,比如监听哪些trigger已经ready、分配线程等等,同样也需要设置必要的属性信息;
triggers:按需可以设置多个触发器,本文设置了一个 cronTriggerFactoryBean.getObject() 也就是 CronTriggerImpl 对象
autoStartup:默认是否自动启动任务,默认值为true
这个过程较长包括:调度工厂、线程池、注册任务等等,整体核心加载流程如下;
整个加载过程较长,抽取部分核心代码块进行分析,其中包括的类;
StdScheduler
StdSchedulerFactory
SimpleThreadPool
QuartzScheduler
QuartzSchedulerThread
5. 启动定时任务。