前一段時間項目需要做一個定時發送消息的功能,該功能依附于Web應用上,即當Web應用啟動時,該應用就開始作用。起先決定使用java.util.Timer和java.util.TimerTask來實現,但是研究了一下以后發現Java Timer的功能比較弱,而且其線程的范圍不受Web應用的約束。后來發現了Quartz這個開源的調度框架,非常有趣。
首先我們要得到Quartz的最新發布版。目前其最新的版本是1.6。我們可以從以下地址獲得它的完整下載包,包中可謂湯料十足,不僅有我們要的quartz.jar,更包含多個例程和詳細的文檔,從API到配置文件的XSD一應俱全。感興趣的朋友也可以在src目錄下找到該項目的源碼一看究竟。
廢話少說,下面就來看一看這個東東是怎么在Java Web Application中得以使用的。
首先不得不提出的是Quartz的三個核心概念:調度器、觸發器、作業。讓我們來看看他們是如何工作的吧。
一.作業總指揮——調度器
1. Scheduler接口
該接口或許是整個Quartz中最最上層的東西了,它提攜了所有觸發器和作業,使它們協調工作。每個Scheduler都存有JobDetail和Trigger的注冊,一個Scheduler中可以注冊多個JobDetail和多個Trigger,這些JobDetail和Trigger都可以通過group name和他們自身的name加以區分,以保持這些JobDetail和Trigger的實例在同一個Scheduler內不會沖突。所以,每個Scheduler中的JobDetail的組名是唯一的,本身的名字也是唯一的(就好像是一個JobDetail的ID)。Trigger也是如此。
Scheduler實例由SchedulerFactory產生,一旦Scheduler實例生成后,我們就可以通過生成它的工廠來找到該實例,獲取它相關的屬性。下面的代碼為我們展示了如何從一個Servlet中找到SchedulerFactory并獲得相應的Scheduler實例,通過該實例,我們可以獲取當前作業中的testmode屬性,來判斷該作業是否工作于測試模式。
//從當前Servlet上下文中查找StdSchedulerFactory ??????????? ServletContext ctx=request.getSession().getServletContext(); ??????????? StdSchedulerFactory factory = (StdSchedulerFactory) ctx.getAttribute("org.quartz.impl.StdSchedulerFactory.KEY"); ??????????? ??????????? Scheduler sch = null; ??????????? try { ??????????????? //獲取調度器 ??????????????? sch = factory.getScheduler("SchedulerName"); ??????????????? //通過調度器實例獲得JobDetail,注意領會JobDetailName和GroupName的用法 ??????????????? JobDetail jd=sch.getJobDetail("JobDetailName", "GroupName"); ??????????????? Map jobmap1=jd.getJobDataMap(); ??????????????? istest=jobmap1.get("testmode")+""; ??????????? } catch (Exception se) { ??????????????? //如果得不到當前作業,則從配置文件中讀取testmode ??????????????? ReadXML("job.xml").get(“job.testmode”); ??????????? } |
?
Scheduler實例生成后,它處于"stand-by"模式,需要調用其start方法來使之投入運作。
public class SendMailShedule{ ??? //設置標準SchedulerFactory ??? static SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); ??? static Scheduler sched; ??? ??? public static void run()throws Exception{ ??????? //生成Scheduler實例 ???????????? sched = schedFact.getScheduler(); ??????? //創建一個JobDetail實例,對應的Job實現類是SendMailJob ???????????? JobDetail jobDetail = new JobDetail("myJob",sched.DEFAULT_GROUP,SendMailJob.class); ??????? //設置CronTrigger,利用Cron表達式設定觸發時間 ??????? CronTrigger trigger = new CronTrigger("myTrigger","test","0 0 8 1 * ?"); ??????? sched.scheduleJob(jobDetail, trigger); ??????? sched.start(); ??? } ??? ??? public static void? stop()throws Exception{ ??????? sched.shutdown(); ??? } } |
另外,我們也可以通過監聽器來跟蹤作業和觸發器的工作狀態。
二.作業及其相關
1. Job
作業實際上是一個接口,任何一個作業都可以寫成一個實現該接口的類,并實現其中的execute()方法,來完成具體的作業任務。
2. JobDetail
JobDetail可以指定我們作業的詳細信息,比如可以通過反射機制動態的加載某個作業的實例,可以指定某個作業在單個調度器內的作業組名稱和具體的作業名稱,可以指定具體的觸發器。
一個作業實例可以對應多個觸發器(也就是說學校每天10點放一次眼保健操錄音,下午3點半可以再放一次),但是一個觸發器只能對應一個作業實例(10點鐘的時候學校不可能同時播放眼保健操和廣播體操的錄音)。
3. JobDataMap
這是一個給作業提供數據支持的數據結構,使用方法和java.util.Map一樣,非常方便。當一個作業被分配給調度器時,JobDataMap實例就隨之生成。
Job有一個StatefulJob子接口,代表有狀態的任務,該接口是一個沒有方法的標簽接口,其目的是讓Quartz知道任務的類型,以便采用不同的執行方案。無狀態任務在執行時擁有自己的JobDataMap拷貝,對JobDataMap的更改不會影響下次的執行。而有狀態任務共享共享同一個JobDataMap實例,每次任務執行對JobDataMap所做的更改會保存下來,后面的執行可以看到這個更改,也即每次執行任務后都會對后面的執行發生影響。
正因為這個原因,無狀態的Job可以并發執行,而有狀態的StatefulJob不能并發執行,這意味著如果前次的StatefulJob還沒有執行完畢,下一次的任務將阻塞等待,直到前次任務執行完畢。有狀態任務比無狀態任務需要考慮更多的因素,程序往往擁有更高的復雜度,因此除非必要,應該盡量使用無狀態的Job。
如果Quartz使用了數據庫持久化任務調度信息,無狀態的JobDataMap僅會在Scheduler注冊任務時保持一次,而有狀態任務對應的JobDataMap在每次執行任務后都會進行保存。
JobDataMap實例也可以與一個觸發器相關聯。這種情況下,對于同一作業的不同觸發器,我們可以在JobDataMap中添加不同的數據,以便作業在不同時間執行時能夠提供更為靈活的數據支持(學校上午放眼保健操錄音第一版,下午放第二版)。
不管是有狀態還是無狀態的任務,在任務執行期間對Trigger的JobDataMap所做的更改都不會進行持久,也即不會對下次的執行產生影響。
?下一篇 Quartz調度框架應用總結(續1)