Spring+quatz 架构及源码详解

转自 http://www.cnblogs.com/surprizeFuture/articles/4564293.html 

由于工作需要优化现有系统的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源码学习

第一节.从入口开始

QuartzSpring的整合,Spring提供一组Bean来支持。一下通过具体的配置来说明,一般情况下都是采用这样配置的:

Spring+quatz 架构及源码详解 

 

     实际上spring在加载时,通过SchedulerFactoryBean来创建scheduler实例并完成初始化的,现在我们就从这个入口源码开始看起:SchedulerFactoryBean的定义如下图,可以看到它实现了InitializingBeanSmartLifecycle接口(如不熟悉,可查看第一节),所以IOC容器初始化完成后会调用该类afterPropertiesSet方法,事实上分析代码可知整个quatz初始化(核心部分)即是在这里完成的,scheduler容器在此创建, 同时该类在start()实现中启动了scheduler的线程。

Spring+quatz 架构及源码详解

 

    具体来分析afterPropertiesSet()start(),可以看出afterPropertiesSet()的核心步骤:

 

1. 实例化一个SchdulerFactory, 此处使用的是默认的StdSchedulerFactory.class

 

2. 通过Factory获取一个scheduler实例容器。

 

3. 配置/添加 对该容器指定的listenerjobstriggers等,本质是将注入的上述属性配合到上步创建的Scheduler容器当中,比较简单,后续忽略介绍。

 

  代码说明的截图:

Spring+quatz 架构及源码详解

start()择更为简单,直接启动上面创建的scheduler容器的实例,代码说明截图如下:其中有一个startupDelay的参数,即启动延时,默认都是为0的,一般也是需要即时启动的。

Spring+quatz 架构及源码详解

 

综上,可以看到quatz启动的大概步骤, 利用spring提供的两个回调接口, 分别在bean实例的构造和容器启动阶段实现了 Scheduler容器的构造 和 scheduler的启动。

    当然理解架构只到此程度是远远不够的,上述部分的核心是通过Factory获取到一个scheduler实例,即createScheduler()方法,下一节对这个过程做出详细的说明。

 

 

第二节.scheduler容器获取(构造)过程

 

 首先,createScheduler()之中的主流程调用树结构如下,其核心是在instantiate()完成的,外部的2层主要做了些读取配置和条件判断等。

Spring+quatz 架构及源码详解

直接来看instantiate()的代码,类比较大且复杂,只列出核心行

Spring+quatz 架构及源码详解

 

 

 

1.创建了一个线程池,默认使用的是SimpleThreadPool, 代码中tp对象

 

2.创建了一个jobStore,默认使用的是RAMJobStore, 代码中的js对象

 

3.创建一个QuartzScheduler对象, 代码中的qs对象,  下图列出他的创建树。

 

 大体上有4步,有2个关键点需要掌握

 

  a.QuartzScheduler对象保存有QuartzSchedulerThread的引用,实际的调度工作基本上是由QuartzSchedulerThread这个对象做的当然实例化QuartzScheduler的目的主要也在于去实例化QuartzSchedulerThread

 

  b.QuartzSchedulerThread在构造函数中实例化,并一经创建就自启动,它是一个独立的线程,至于自启后做什么,在后面章节说明。下面是quartzScheduler的创建过程

              Spring+quatz 架构及源码详解

 

4.创建了scheduler. 

  4.1.  QuartzSchedulerResources 对象,代码中的rsrcs对象

       Contains all of the resources,可以将其视为资源缓存对象,包含了所有定时模块的资源的引用。

  4.2. Scheduler对象。 看源代码它的直接实例是StdScheduler,但实际StdScheduler中直接引用了QuartzSchedulerqs)且仅止于此,没有额外的动作。 简单的你可以认为这是一层封装。因此你可以认为真正在工作的其实就是QuartzScheduler或者说是 QuartzSchedulerThread

本节主要讲述了整个scheduler容器创建时的过程,以及4个重要的步骤节点。后续章节来分别详述这些核心步骤的具体创建和工作原理。

 

至此,通过SchedulerFactory构建Scheduler的过程已完成,可以总结为三个要点。

 

1. 创建线程池

 

2. 创建JobStore

 

3. 通过QuartzScheduler启动一个QuartzSchedulerThread 

 

他们的关系如下图,最终返回的这是QuatzScheduler的引用。本图转自网络。

Spring+quatz 架构及源码详解

第三节. 对第二节的一个小结

   通过对上节真个启动过程的源码解读,应该可以较为深入的理解spring 管理的Quatz的启动流程,网络上一张时序图可以作为很好的参考:

Spring+quatz 架构及源码详解

 

 

 

 

第四节.QuartzSchedulerThread是如何工作的。

    在第二节中讲到启动是一个核心步骤就是创建QuartzSchedulerThread并启动它,因为它是继承自Thread的,因此我们可以跟踪它的run方法看它究竟做哪些工作

     Spring+quatz 架构及源码详解

 

解析这个方法如下:

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里面。

Spring+quatz 架构及源码详解

 

B.WorkerThread是一个内部类,看下被启动是具体做什么工作(run方法)。

   比较简单,“一直不停的在等待runable类型的事件的注入,一旦进来,就执行”。

Spring+quatz 架构及源码详解

 

   本节和上节可以结合在一起归纳一下,QuartzSchedulerThread一直寻找可执行目标,找到后就交给SimpleThreadPool具体执行, 而相应的SimpleThreadPool的线程们也被设计成随时等待runnable事件的注入。举个通俗的例子,QuartzSchedulerThread就是青楼的老妈子,SimpleThreadPool

里的线程就是待接客的姑娘们,老妈子负责不断寻找/接待客人,然后将目标客户引给闲置的姑娘, 具体的接待工作由姑娘完成。

     现在提出两个问题,“客人”是如何被找到的? “姑娘”是如何开始接待的? 在下节讨论。

 

6.RAMJobStore的介绍

 

      JobStore负责保存所有配置到调度器里的工作数据:jobstriggerscalendars等等,“RAM”顾名思义就是利用内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和 运行,但是当应用程序停止运行时,所有调度信息将被丢失。RAMJobStorespring+quatz默认的jobstore

 

在第4节中讲解QuartzSchedulerThread的工作原理中多次提到了jobStore,其中有一个步骤便是通过jobStore获取下一个应该执行的trigger,现在就来具体分析。

 

 

A. 利用jobStore获取下一个需要执行的trigger, 此处调用的是acquireNextTrigger()方法。 注意一点,在这步中一般情况下是先将第一个执行的triggertreeset中删除,然后返回它。 treeset是按照触发是每个trigger最近一次应该执行的时间排序的。 大概原理是这样的,1.往队列添加trigger的时候,是以该trigger的下次触发时间来

确定它在treeset的顺序 2.要执行trigger时,是直接将trigger移出treeset. 3.在恰当的时刻,将该trigger按照第一步加入到treeset

Spring+quatz 架构及源码详解

B.上面的一点你可能会思考一个问题,这个trigger的下一次是怎么触发的(因为已经从timeTriggers队列里删除了)。

  实际上在第四节的代码讲解中有一句注释的代码,当时没有提及,因这代码前后都有其他逻辑,所以是以注释的形式来展现的。 在此处说明比较合适。 

Spring+quatz 架构及源码详解

此方法包含了如下操作, 当然包含了诸多其他内容,此处不展开说明:

 根据当前的nextFireTime来实现自我更新, 然后将此trigger重新加入到timeTriggers

  而此方法的返回值bndleC中详解。

Spring+quatz 架构及源码详解

 

C. 第四节讲到拿到Trigger的下一步是创建一个jobshell丢给线程池去执行。现在看看jobShell是如何创建的。

首先B中 triggerFired方法返回的bndle对象,它是TriggerFiredBundle类型,该类属性如下,实质上这些数据也是从jobStore那里引用过来的,封装成一个对象方便后续处理。

Spring+quatz 架构及源码详解

 

创建runshell的数据来源正是基于这个对象的。 创建runshell实例很简单,对实例的初始化过程主要是由JobRunShell.initialize()方法负责的。具体分析:

Spring+quatz 架构及源码详解

 

runShell创建并初始化之后,是被丢进ThreadPool中执行的,而执行过程就是线程调用runShellrun()方法, 大家可以具体查看,这里由于我整理的文档涉及

我们项目内容,省略掉这一部分。

 

 

文档至此,你应当比较全面的了解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  SpringQuartz的封装实现简单分析及使用注意事项