概述
各種企業應用幾乎都會碰到任務調度的需求,就拿論壇來說:每隔半個小時生成精華文章的
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
名稱,以便讓這些監聽器對本任務的事件進行監聽。
下面配置 片斷使用JobDetailBean 在Spring 中配置一個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
的數據。此外,通過指定 applicationContextJobDataKey
讓Job
的JobDataMap
持有Spring ApplicationContext
的引用。
這樣,MyJob
在運行時就可以通過JobDataMap
訪問到size
和ApplicationContext
了。來看一下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
內部機制的用戶來說,比起 statefule
,concurrent
顯然更簡明達意些。
MyService
服務類擁有一個doJob()
方法,它的代碼如下所示:
package com.baobaotao.service;
public class MyService ...{
public void doJob()...{① 被封裝成任務的目標方法
System.out.println("in MyService.dojob().");
}
}
doJob() 方法即可以是static ,也可以是非static 的,但不能擁有方法入參。通過MethodInvokingJobDetailFactoryBean 產生的JobDetail 不能被序列化,所以不能被持久化到數據庫 中的,如果希望使用持久化任務,則你只能創建正規的Quartz 的Job 實現類了。
創建Trigger
Quartz
中另一個重要的組件就是Trigger
,Spring
按照相似的思路分別爲SimpleTrigger
和CronTrigger
提供了更具Bean
風格的SimpleTriggerBean
和CronTriggerBean
擴展類,通過這兩個擴展類更容易在Spring
中以Bean
的方式配置Trigger
。
SimpleTriggerBean
默認情況下,通過SimpleTriggerBean
配置的Trigger
名字即爲Bean
的名字,並屬於默認組Trigger
組。SimpleTriggerBean
在SimpleTrigger
的基礎上,新增了以下屬性:
● jobDetail
:對應的JobDetail
;
● beanName
:默認爲Bean
的id
名,通過該屬性顯式指定Bean
名稱,它對應Trigger
的名稱;
● jobDataAsMap
:以Map
類型爲Trigger
關聯的JobDataMap
提供值;
● startDelay
:延遲多少時間開始觸發,單位爲毫秒,默認爲0
;
● triggerListenerNames
:類型爲String[]
,指定註冊在Scheduler
中的TriggerListener
名稱,以便讓這些監聽器對本觸發器的事件進行監聽。
下面的實例使用SimpleTriggerBean
定義了一個Trigger
,該Trigger
和jobDetail
相關聯,延遲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>
需要特別注意的是,① 處配置的JobDataMap 是Trigger 的JobDataMap ,任務執行時必須通過以下方式獲取配置的值:
package com.baobaotao.quartz;
…
public class MyJob implements StatefulJob ...{
public void execute(JobExecutionContext jctx) throws JobExecutionException ...{
Map dataMap = jctx.getTrigger().getJobDataMap();① 獲取Trigger 的JobDataMap
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
容器的生命週期,完成自動啓動和關閉的操作,必須讓Scheduler
和Spring
容 器的生命週期相關聯。以便在Spring
容器啓動後,Scheduler
自動開始工作,而在Spring
容器關閉前,自動關閉Scheduler
。爲 此,Spring
提供SchedulerFactoryBean
,這個FactoryBean
大致擁有以下的功能:
1)
以更具Bean
風格的方式爲Scheduler
提供配置
信息;
2)
讓Scheduler
和Spring
容器的生命週期建立關聯,相生相息;
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
,在①
處,我們註冊了 一個Trigger
。Scheduler
擁有一個類似於ServletContext
的SchedulerContext
。 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
集成JPA
、Hibernate
框架,就知道這是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
爲Quartz
的JobDetail
和Trigger
提供了更具Bean
風格的支持類,這使我們能夠更地方便地在Spring
中通過配置定製 這些組件實例。Spring
的SchedulerFactoryBean
讓我們可以脫離Quartz
自身的配置體系,而以更具Spring
風格的方式定義 Scheduler
。此外,還可以享受Scheduler
生命週期和Spring
容器生命週期綁定的好處。