Spring and Quartz

概述
   
各種企業應用幾乎都會碰到任務調度的需求,就拿論壇來說:每隔半個小時生成精華文章的 RSS 文件,每天凌晨統計論壇用戶的積分排名,每隔 30 分鐘執行鎖定 用戶解鎖任務。對於一個典型的 MIS 系統來說,在每月 1 號凌晨統計上個月各部門的業務數據生成月報表,每半個小時查詢用戶是否已經有快到期的待處理業 …… ,這樣的例子俯拾皆是,不勝枚舉。

    Quartz
在開源任務調度框架中的翹首,它提供了強大任務調度機制,難能可貴的是它同時保持了使用的簡單性。 Quartz 允許開發人員靈活地定義觸發器的調度時間表,並可以對觸發器和任務進行關聯映射。此外, Quartz 提供了調度運行環境的持久化機制,可以保存並恢復調度 現場,即使系統因故障關閉,任務調度現場數據並不會丟失。此外, Quartz 還提供了組件式的偵聽器、各種插件、線程池等功能。

    Spring
創建 Quartz Scheduler Trigger JobDetail 提供了便利的 FactoryBean 類,以便能夠在 Spring 容器中享受注入的好處。此外 Spring 還提供了一些便利工具類直接將 Spring 中的 Bean 包裝成合法的任務。 Spring 進一步降低了使用 Quartz 的難度,能以更具 Spring 風格的方式使用 Quartz 。概括來說它提供了兩方面的支持:
    1)
Quartz 的重要組件類提供更具 Bean 風格的擴展類;
    2)
提供創建 Scheduler BeanFactory 類,方便在 Spring 環境下創建對應的組件對象,並結合 Spring 容器生命週期進行啓動和停止的動作。
   
創建 JobDetail
   
你可以直接使用 Quartz JobDetail Spring 配置 JobDetail Bean ,但是 JobDetail 使用帶參的構造函數,對於習慣通過屬性配置的 Spring 用戶來說存在使用上的不便。爲此 Spring 通過擴展 JobDetail 提供了一個更具 Bean 風格的 JobDetailBean 。此外, Spring 提供了一個 MethodInvokingJobDetailFactoryBean ,通過這個 FactoryBean 可以將 Spring 容器中 Bean 的方法包裝成 Quartz 任務,這樣開發者就不必爲 Job 創建對應的類。
    JobDetailBean
    JobDetailBean
擴展於 Quartz JobDetail 。使用該 Bean 聲明 JobDetail 時, Bean 的名字即是任務的名字,如果沒有指定所屬組,即使用默認組。除了 JobDetail 中的屬性外,還定義了以下屬性:
    ● jobClass
:類型爲 Class ,實現 Job 接口的任務類;
    ● beanName
:默認爲 Bean id 名,通過該屬性顯式指定 Bean 名稱,它對應任務的名稱;
    ● jobDataAsMap
:類型爲 Map ,爲任務所對應的 JobDataMap 提供值。之所以需要提供這個屬性,是因爲除非你手工註冊一 個編輯器,你不能直接配置 JobDataMap 類型的值,所以 Spring 通過 jobDataAsMap 設置 JobDataMap 的值;
    ●applicationContextJobDataKey
:你可以將 Spring ApplicationContext 的引用保存到 JobDataMap 中,以便在 Job 的代碼中訪   ApplicationContext 。爲了達到這個目的,你需要指定一個鍵,用以在 jobDataAsMap 中保存 ApplicationContext ,如果不設置此鍵, JobDetailBean 就不將 ApplicationContext 放入到 JobDataMap 中;
    ●jobListenerNames
:類型爲 String[] ,指定註冊在 Scheduler 中的 JobListeners 名稱,以便讓這些監聽器對本任務的事件進行監聽。

下面配置 片斷使用JobDetailBeanSpring 中配置一個JobDetail

<bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="jobClass" value="com.baobaotao.quartz.MyJob" />

<property name="jobDataAsMap">①

<map>

<entry key="size" value="10" />

</map>

</property>

<property name="applicationContextJobDataKey" value="applicationContext"/>②

</bean>

 

    JobDetailBean 封裝了MyJob 任務類,併爲Job 對應JobDataMap 設置了一個size 的數據。此外,通過指定 applicationContextJobDataKeyJobJobDataMap 持有Spring ApplicationContext 的引用。
這樣,MyJob 在運行時就可以通過JobDataMap 訪問到sizeApplicationContext 了。來看一下MyJob 的代碼,如代碼清單 8 所示:
   
代碼清單 8 MyJob

package com.baobaotao.quartz;

import org.quartz.Job;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

import org.springframework.context.ApplicationContext;

public class MyJob implements Job ...{

public void execute(JobExecutionContext jctx) throws JobExecutionException ...{

Map dataMap = jctx.getJobDetail().getJobDataMap();① 獲取JobDetail 關聯的JobDataMap

String size =(String)dataMap.get("size");②

ApplicationContext ctx = (ApplicationContext)dataMap.get("applicationContext");③

System.out.println("size:"+size);

dataMap.put("size",size+"0");④ JobDataMap 所做的更改是否被會持久,取決於任務的類型

//do sth...

}

}

 

處獲取size 值,在 處還可以根據鍵“applicationContext” 獲取ApplicationContext ,有了 ApplicationContext 的引用,Job 就可以毫無障礙訪問Spring 容器中的任何Bean 了。MyJob 可以在execute() 方法中 對JobDataMap 進行更改,如 所示。如果MyJob 實現Job 接口,這個更改對於下一次執行是不可見的,如果MyJob 實現 StatefulJob 接口,這種更改對下一次執行是可見的。

ethodInvokingJobDetailFactoryBean
   
通常情況下,任務都定義在一個業務類方法中。這時,爲了滿足Quartz Job 接口的規定,還需要定義一個引用業務類方法的實現類。爲了避免創建這個只包含一行調用代碼的Job 實現類,Spring 爲我們提供了MethodInvokingJobDetailFactoryBean ,藉由該FactoryBean ,我們可以將一個Bean 的某個方法封裝成滿足Quartz 要求的Job 。來看一個具體的例子:

<bean id="jobDetail_1"

class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">

<property name="targetObject" ref="myService" /> ① 引用一個Bean

<property name="targetMethod" value="doJob" /> ② 指定目標Bean 的方法

<property name="concurrent" value="false" /> ③ 指定最終封裝出的任務是否有狀態

<bean id="myService" class="com.baobaotao.service.MyService"/>

 

    jobDetail_1 MyService#doJob() 封裝成一個任務,同時通過concurrent 屬性指定任務的類型,默認情況下封裝爲無狀態 的任務,如果希望目標封裝爲有狀態的任務,僅需要將concurrent 設置爲false 就可以了。Spring 通過名爲concurrent 的屬性指定 任務的類型,能夠更直接地描述到任務執行的方式(有狀態的任務不能併發執行,無狀態的任務可併發執行),對於不熟悉Quartz 內部機制的用戶來說,比起 statefuleconcurrent 顯然更簡明達意些。
MyService
服務類擁有一個doJob() 方法,它的代碼如下所示:

package com.baobaotao.service;

public class MyService ...{

public void doJob()...{① 被封裝成任務的目標方法

System.out.println("in MyService.dojob().");

}

}

 

    doJob() 方法即可以是static ,也可以是非static 的,但不能擁有方法入參。通過MethodInvokingJobDetailFactoryBean 產生的JobDetail 不能被序列化,所以不能被持久化到數據庫 中的,如果希望使用持久化任務,則你只能創建正規的QuartzJob 實現類了。

創建Trigger
    Quartz
中另一個重要的組件就是TriggerSpring 按照相似的思路分別爲SimpleTriggerCronTrigger 提供了更具Bean 風格的SimpleTriggerBeanCronTriggerBean 擴展類,通過這兩個擴展類更容易在Spring 中以Bean 的方式配置Trigger

    SimpleTriggerBean
   
默認情況下,通過SimpleTriggerBean 配置的Trigger 名字即爲Bean 的名字,並屬於默認組Trigger 組。SimpleTriggerBeanSimpleTrigger 的基礎上,新增了以下屬性:
    ● jobDetail
:對應的JobDetail
    ● beanName
:默認爲Beanid 名,通過該屬性顯式指定Bean 名稱,它對應Trigger 的名稱;
    ● jobDataAsMap
:以Map 類型爲Trigger 關聯的JobDataMap 提供值;
    ● startDelay
:延遲多少時間開始觸發,單位爲毫秒,默認爲0
    ● triggerListenerNames
:類型爲String[] ,指定註冊在Scheduler 中的TriggerListener 名稱,以便讓這些監聽器對本觸發器的事件進行監聽。
   
下面的實例使用SimpleTriggerBean 定義了一個Trigger ,該TriggerjobDetail 相關聯,延遲10 秒後啓動,時間間隔爲20 秒,重複執行100 次。此外,我們還爲Trigger 設置了JobDataMap 數據:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">

<property name="jobDetail" ref="jobDetail" />

<property name="startDelay" value="1000" />

<property name="repeatInterval" value="2000" />

<property name="repeatCount" value="100" />

<property name="jobDataAsMap"> ①

<map>

<entry key="count" value="10" />

</map>

</property>

</bean>

 

    需要特別注意的是, 處配置的JobDataMapTriggerJobDataMap ,任務執行時必須通過以下方式獲取配置的值:

package com.baobaotao.quartz;

public class MyJob implements StatefulJob ...{

public void execute(JobExecutionContext jctx) throws JobExecutionException ...{

Map dataMap = jctx.getTrigger().getJobDataMap();① 獲取TriggerJobDataMap

String count = dataMap.get("count");

dataMap.put(“count”,”30”) ② JobDataMap 的更改不會被持久,不影響下次的執行

}

}

 

    CronTriggerBean
    CronTriggerBean
擴展於CronTrigger ,觸發器的名字即爲Bean 的名字,保存在默認組中。在CronTrigger 的基礎上,新 增的屬性和SimpleTriggerBean 大致相同,配置的方法也和SimpleTriggerBean 相似,下面給出一個簡單的例子:

<bean id="checkImagesTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">

<property name="jobDetail" ref="jobDetail "/>

<property name="cronExpression" value="0/5 * * * * ?"/>

</bean>

 

創建Scheduler  
    Quartz
SchedulerFactory 是標準的工廠類,不太適合在Spring 環 境下使用。此外,爲了保證Scheduler 能夠感知Spring 容器的生命週期,完成自動啓動和關閉的操作,必須讓SchedulerSpring 容 器的生命週期相關聯。以便在Spring 容器啓動後,Scheduler 自動開始工作,而在Spring 容器關閉前,自動關閉Scheduler 。爲 此,Spring 提供SchedulerFactoryBean ,這個FactoryBean 大致擁有以下的功能:
    1)
以更具Bean 風格的方式爲Scheduler 提供配置 信息;
    2)
SchedulerSpring 容器的生命週期建立關聯,相生相息;
    3)
通過屬性配置部分或全部代替Quartz 自身的配置文件
   
來看一個SchedulerFactoryBean 配置的例子:
   
代碼清單 9 SchedulerFactoryBean 配置

<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<property name="triggers"> ① 註冊多個Trigger

<list>

<ref bean="simpleTrigger" />

</list>

</property>

<property name="schedulerContextAsMap">② Map 類型設置SchedulerContext 數據

<map>

<entry key="timeout" value="30" />

</map>

</property>

 

    ③ 顯式指定Quartz 的配置文件地址
   <property name="configLocation" value="classpath:com/baobaotao/quartz/quartz.properties" />
    </bean>
    SchedulerFactoryBean
triggers 屬性爲Trigger[] 類型,可以通過該屬性註冊多個Trigger ,在 處,我們註冊了 一個TriggerScheduler 擁有一個類似於ServletContextSchedulerContext SchedulerFactoryBean 允許你以Map 的形式設置SchedulerContext 的參數值,如 所示。默認情況下,Quartz 在類 路徑下查詢quartz.properties 配置文件,你也可以通過configLocation 屬性顯式指定配置文件位置,如 所示。
   
除了實例中所用的屬性外,SchedulerFactoryBean 還擁有一些常見的屬性:
    ●calendars
:類型爲Map ,通過該屬性向Scheduler 註冊Calendar
    ●jobDetails
:類型爲JobDetail[] ,通過該屬性向Scheduler 註冊JobDetail
    ●autoStartup
SchedulerFactoryBean 在初始化後是否馬上啓動Scheduler ,默認爲true 。如果設置爲false ,需要手工啓動Scheduler
    ●startupDelay
:在SchedulerFactoryBean 初始化完成後,延遲多少秒啓動Scheduler ,默認爲0 ,表示馬上啓動。如 果並非馬上擁有需要執行的任務,可通過startupDelay 屬性讓Scheduler 延遲一小段時間後啓動,以便讓Spring 能夠更快初始化容器中 剩餘的Bean

●SchedulerFactoryBean 的一個重要功能是允許你將Quartz 配置文件中的信息轉移到Spring 配置文件中,帶來的好處是,配置信息的集中化管理,同時我們不必熟悉多種框架的配置文件結構。回憶一個Spring 集成JPAHibernate 框架,就知道這是Spring 在集成第三方框架經常採用的招數之一。SchedulerFactoryBean 通過以下屬性代替框架的自身配置文件:
    ●dataSource
:當需要使用數據庫 來持久化任務調度數據時,你可以在Quartz 中配置數據源,也可以直接在Spring 中通過dataSource 指定一個Spring 管理的數據源。如果指定了該屬性,即使quartz.properties 中已經定義了數據源,也會被此dataSource 覆蓋;
    ●transactionManager
:可以通過該屬性設置一個Spring 事務管理器。在設置dataSource 時,Spring 強烈推薦你使用一個事務管理器,否則數據表鎖定可能不能正常工作;
    ●nonTransactionalDataSource
:在全局事務的情況下,如果你不希望Scheduler 執行化數據操作參與到全局事務中,則可以通過該屬性指定數據源。在Spring 本地事務的情況下,使用dataSource 屬性就足夠了;
    ●quartzProperties
:類型爲Properties ,允許你在Spring 中定義Quartz 的屬性。其值將覆蓋 quartz.properties 配置文件中的設置,這些屬性必須是Quartz 能夠識別的合法屬性,在配置時,你可以需要查看Quartz 的相關文 檔。下面是一個配置quartzProperties 屬性的例子:

<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

<property name="quartzProperties">

<props>

<prop key="org.quartz.threadPool.class">①Quartz 屬性項1

org.quartz.simpl.SimpleThreadPool

</prop>

<prop key="org.quartz.threadPool.threadCount">10</prop>①Quartz 屬性項2

</props>

</property>

</bean>

 

    在實際應用中,我們並不總是在程序部署的時候就可能確定需要哪些任務,往往需要在運行期根據業務數據動態產生觸發器和任務。你完全可以在運行期通過代碼調用SchedulerFactoryBean 獲取Scheduler 實例,進行動態的任務註冊和調度。

   
小結
    Spring
QuartzJobDetailTrigger 提供了更具Bean 風格的支持類,這使我們能夠更地方便地在Spring 中通過配置定製 這些組件實例。SpringSchedulerFactoryBean 讓我們可以脫離Quartz 自身的配置體系,而以更具Spring 風格的方式定義 Scheduler 。此外,還可以享受Scheduler 生命週期和Spring 容器生命週期綁定的好處。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章