各種企業應用幾乎都會碰到任務調度的需求,就拿論壇來說:每隔半個小時生成精華文章的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接口,這種更改對下一次執行是可見的。
MethodInvokingJobDetailFactoryBean
通常情況下,任務都定義在一個業務類方法中。這時,爲了滿足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"/>
MyService服務類擁有一個doJob()方法,它的代碼如下所示:
創建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擴展於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>
<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>
小結
Spring爲Quartz的JobDetail和Trigger提供了更具Bean風格的支持類,這使我們能夠更地方便地在Spring中通過配置定製這些組件實例。Spring的SchedulerFactoryBean讓我們可以脫離Quartz自身的配置體系,而以更具Spring風格的方式定義Scheduler。此外,還可以享受Scheduler生命週期和Spring 容器生命週期綁定的好處。