Spring+quatz 架构及源码详解
由于工作需要优化现有系统的spring+quartz中一些任务的执行策略, 我特地花了几天时间看了spring+quartz的源码 以及参考了网上相关的文章, 现就做一下总结真理, 其中主体结构和大部内容都是借鉴 “quatz学习笔记”这篇文章的,后面参考资料有链接。 我按自己的思路重新做了解析和整理。下面开始介绍
一.前提知识点
1.InitializingBean 与 afterPorpertiesSet()
关于在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:
第一种:通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
第二种是:通过 在xml中定义init-method 和 destory-method方法
第三种是: 通过bean实现InitializingBean和 DisposableBean接口
Spirng的InitializingBean为bean提供了定义初始化方法的方式。InitializingBean是一个接口,它仅仅包含一个方法:afterPropertiesSet()。 在spring 初始化后,执行完所有属性设置方法(即setXxx)将自动调用 afterPropertiesSet(), 在配置文件中无须特别的配置, 但此方式增加了bean对spring 的依赖,应该尽量避免使用。
实现org.springframework.beans.factory.DisposableBean接口允许一个bean,可以在包含它的BeanFactory销毁的时候得到一个回调。DisposableBean也只指定了一个方法:
void destroy() throws Exception;
2.SmartLifecycle系列
有时候我们可能需要一些组件的功能随着spring容器的启动而启动(如新开启一个线程来进行监听),容器的销毁而销毁。为此spring提供了一个接口,从方法名字很容易看到方法的作用:
public interface Lifecycle {
void start();
void stop();
boolean isRunning(); //Check whether this component is currently running
}
当启动的时候,由于组件之间的依赖,启动的顺序是相当重要的。depends-on属性可以决定多个lifecycle的实现的顺序,但是有时候依赖是未知的。为此spring定义可一个新的接口,SmartLifecycle:
public interface Phased {
int getPhase(); //Return the phase value of this object. 调用优先级
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup(); //是否自启动
void stop(Runnable callback);
}
isAutoStartup决定是随着容器的启动自启动,Phased接口定义的方法则决定了启动顺序,其返回值越小启动越早,越大启动越晚。如果一个Lifecycle未实现Phased接口,则默认其值为0.Stop方法可以异步的执行析构函数,spring默认会为每一个Phase等待30秒钟。这个时间是可以配置的。
二. spring+quartz源码学习
第一节.从入口开始
Quartz与Spring的整合,Spring提供一组Bean来支持。一下通过具体的配置来说明,一般情况下都是采用这样配置的:
实际上spring在加载时,通过SchedulerFactoryBean来创建scheduler实例并完成初始化的,现在我们就从这个入口源码开始看起:SchedulerFactoryBean的定义如下图,可以看到它实现了InitializingBean和SmartLifecycle接口(如不熟悉,可查看第一节),所以IOC容器初始化完成后会调用该类afterPropertiesSet方法,事实上分析代码可知整个quatz初始化(核心部分)即是在这里完成的,scheduler容器在此创建, 同时该类在start()实现中启动了scheduler的线程。
具体来分析afterPropertiesSet()和start(),可以看出afterPropertiesSet()的核心步骤:
1. 实例化一个SchdulerFactory, 此处使用的是默认的StdSchedulerFactory.class
2. 通过Factory获取一个scheduler实例容器。
3. 配置/添加 对该容器指定的listener、jobs、triggers等,本质是将注入的上述属性配合到上步创建的Scheduler容器当中,比较简单,后续忽略介绍。
代码说明的截图:
start()择更为简单,直接启动上面创建的scheduler容器的实例,代码说明截图如下:其中有一个startupDelay的参数,即启动延时,默认都是为0的,一般也是需要即时启动的。
综上,可以看到quatz启动的大概步骤, 利用spring提供的两个回调接口, 分别在bean实例的构造和容器启动阶段实现了 Scheduler容器的构造 和 scheduler的启动。
当然理解架构只到此程度是远远不够的,上述部分的核心是通过Factory获取到一个scheduler实例,即createScheduler()方法,下一节对这个过程做出详细的说明。
第二节.scheduler容器获取(构造)过程
首先,createScheduler()之中的主流程调用树结构如下,其核心是在instantiate()完成的,外部的2层主要做了些读取配置和条件判断等。
直接来看instantiate()的代码,类比较大且复杂,只列出核心行
1.创建了一个线程池,默认使用的是SimpleThreadPool, 代码中tp对象
2.创建了一个jobStore,默认使用的是RAMJobStore, 代码中的js对象
3.创建一个QuartzScheduler对象, 代码中的qs对象, 下图列出他的创建树。
大体上有4步,有2个关键点需要掌握
a.QuartzScheduler对象保存有QuartzSchedulerThread的引用,实际的调度工作基本上是由QuartzSchedulerThread这个对象做的. 当然实例化QuartzScheduler的目的主要也在于去实例化QuartzSchedulerThread。
b.QuartzSchedulerThread在构造函数中实例化,并一经创建就自启动,它是一个独立的线程,至于自启后做什么,在后面章节说明。下面是quartzScheduler的创建过程
4.创建了scheduler.
4.1. QuartzSchedulerResources 对象,代码中的rsrcs对象
Contains all of the resources,可以将其视为资源缓存对象,包含了所有定时模块的资源的引用。
4.2. Scheduler对象。 看源代码它的直接实例是StdScheduler,但实际StdScheduler中直接引用了QuartzScheduler(qs)且仅止于此,没有额外的动作。 简单的你可以认为这是一层封装。因此你可以认为真正在工作的其实就是QuartzScheduler或者说是 QuartzSchedulerThread
本节主要讲述了整个scheduler容器创建时的过程,以及4个重要的步骤节点。后续章节来分别详述这些核心步骤的具体创建和工作原理。
至此,通过SchedulerFactory构建Scheduler的过程已完成,可以总结为三个要点。
1. 创建线程池
2. 创建JobStore
3. 通过QuartzScheduler启动一个QuartzSchedulerThread
他们的关系如下图,最终返回的这是QuatzScheduler的引用。本图转自网络。
第三节. 对第二节的一个小结
通过对上节真个启动过程的源码解读,应该可以较为深入的理解spring 管理的Quatz的启动流程,网络上一张时序图可以作为很好的参考:
第四节.QuartzSchedulerThread是如何工作的。
在第二节中讲到启动是一个核心步骤就是创建QuartzSchedulerThread并启动它,因为它是继承自Thread的,因此我们可以跟踪它的run方法看它究竟做哪些工作
解析这个方法如下:
1. 方法体是一个无限循环,一直在等任务的出现,然后启动执行。
2. 刚被启动时,是在等pause信号量。 这个信号在何时改变了,也行你已经在上面时序图中看到了, 前面章节提到的start()的实现其本质的工作就是修改这个pause信号量。 使
QuartzSchedulerThread正式进入工作状态(虽然其构造时就被启动,但没有真正开始工作)。
3.总会从jobstore里找出下个应该执行的trigger任务(具体步骤后面解析),然后等待这一时刻到来。(通过不停的比较trigger的时间和当前系统时间)
4. 之后通过jobstore将其对应的jobdetail封装成一个runShell (具体步骤后面解析)
5. 将runShell交给线程池去执行,runInThread方法,下节会具体讲解。
6. 继续循环寻找下一个执行trigger,依次往复。
因此上面提到过其实在quartz里面,真正做实际工作的调度线程其实就是它。
第五节.SimpleThreadPool的介绍
总体来讲,SimpleThreadPool的实现很简单。
第2节介绍createScheduler过程时,提到第一个步骤便是创建一个线程池,该默认的线程池便是SimpleThreadPool. 跟踪源代码可见,最然构建tp实例时未做任何操作,但在实例之后,又调用了SimpleThreadPool.initialize(), 此方法相当于初始配置tp以及启用它。下面来分析源码
a.代码比较简单,注意一点是创建的线程是WorkerThread,因此看具体工作还需要追踪到WorkerThread里面。
B.WorkerThread是一个内部类,看下被启动是具体做什么工作(run方法)。
比较简单,“一直不停的在等待runable类型的事件的注入,一旦进来,就执行”。
本节和上节可以结合在一起归纳一下,QuartzSchedulerThread一直寻找可执行目标,找到后就交给SimpleThreadPool具体执行, 而相应的SimpleThreadPool的线程们也被设计成随时等待runnable事件的注入。举个通俗的例子,QuartzSchedulerThread就是青楼的老妈子,SimpleThreadPool
里的线程就是待接客的姑娘们,老妈子负责不断寻找/接待客人,然后将目标客户引给闲置的姑娘, 具体的接待工作由姑娘完成。
现在提出两个问题,“客人”是如何被找到的? “姑娘”是如何开始接待的? 在下节讨论。
6.RAMJobStore的介绍
JobStore负责保存所有配置到调度器里的工作数据:jobs,triggers,calendars等等,“RAM”顾名思义就是利用内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和 运行,但是当应用程序停止运行时,所有调度信息将被丢失。RAMJobStore是spring+quatz默认的jobstore
在第4节中讲解QuartzSchedulerThread的工作原理中多次提到了jobStore,其中有一个步骤便是通过jobStore获取下一个应该执行的trigger,现在就来具体分析。
A. 利用jobStore获取下一个需要执行的trigger, 此处调用的是acquireNextTrigger()方法。 注意一点,在这步中一般情况下是先将第一个执行的trigger从treeset中删除,然后返回它。 treeset是按照触发是每个trigger最近一次应该执行的时间排序的。 大概原理是这样的,1.往队列添加trigger的时候,是以该trigger的下次触发时间来
确定它在treeset的顺序 2.要执行trigger时,是直接将trigger移出treeset. 3.在恰当的时刻,将该trigger按照第一步加入到treeset
B.上面的一点你可能会思考一个问题,这个trigger的下一次是怎么触发的(因为已经从timeTriggers队列里删除了)。
实际上在第四节的代码讲解中有一句注释的代码,当时没有提及,因这代码前后都有其他逻辑,所以是以注释的形式来展现的。 在此处说明比较合适。
此方法包含了如下操作, 当然包含了诸多其他内容,此处不展开说明:
根据当前的nextFireTime来实现自我更新, 然后将此trigger重新加入到timeTriggers。
而此方法的返回值bndle在C中详解。
C. 第四节讲到拿到Trigger的下一步是创建一个jobshell丢给线程池去执行。现在看看jobShell是如何创建的。
首先B中 triggerFired方法返回的bndle对象,它是TriggerFiredBundle类型,该类属性如下,实质上这些数据也是从jobStore那里引用过来的,封装成一个对象方便后续处理。
创建runshell的数据来源正是基于这个对象的。 创建runshell实例很简单,对实例的初始化过程主要是由JobRunShell.initialize()方法负责的。具体分析:
runShell创建并初始化之后,是被丢进ThreadPool中执行的,而执行过程就是线程调用runShell的run()方法, 大家可以具体查看,这里由于我整理的文档涉及
我们项目内容,省略掉这一部分。
文档至此,你应当比较全面的了解quatz的初始化过程以及调用机制
三. Spring+quatz的参考文档
以下参考文档分别从各方面侧重介绍了quatz,其中主要参考了《quartz学习笔记》
http://jinnianshilongnian.iteye.com/blog/1902886 详解spring事件驱动模型
http://www.cnblogs.com/yunxuange/archive/2012/08/28/2660141.html quatz 学习笔记
http://www.cnblogs.com/chanedi/p/4169510.html Quartz的线程池解析
http://blog.csdn.net/luccs624061082/article/details/39288275 Spring 之生命周期机制混合使用
http://www.cnblogs.com/langtianya/archive/2013/05/15/3079109.html quatz详解
http://seanhe.iteye.com/blog/691835 Spring对Quartz的封装实现简单分析及使用注意事项