了解Quartz體系結(jié)構(gòu)
Quartz對任務(wù)調(diào)度的領(lǐng)域問題進(jìn)行了高度的抽象,提出了調(diào)度器、任務(wù)和觸發(fā)器這3個(gè)核心的概念,并在org.quartz通過接口和類對重要的這些核心概念進(jìn)行描述:
●Job:是一個(gè)接口,只有一個(gè)方法void execute(JobExecutionContext context),開發(fā)者實(shí)現(xiàn)該接口定義運(yùn)行任務(wù),JobExecutionContext類提供了調(diào)度上下文的各種信息。Job運(yùn)行時(shí)的信息保存在JobDataMap實(shí)例中;
●JobDetail:Quartz在每次執(zhí)行Job時(shí),都重新創(chuàng)建一個(gè)Job實(shí)例,所以它不直接接受一個(gè)Job的實(shí)例,相反它接收一個(gè)Job實(shí)現(xiàn)類,以便運(yùn)行時(shí)通過newInstance()的反射機(jī)制實(shí)例化Job。因此需要通過一個(gè)類來描述Job的實(shí)現(xiàn)類及其它相關(guān)的靜態(tài)信息,如Job名字、描述、關(guān)聯(lián)監(jiān)聽器等信息,JobDetail承擔(dān)了這一角色。
通過該類的構(gòu)造函數(shù)可以更具體地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),該構(gòu)造函數(shù)要求指定Job的實(shí)現(xiàn)類,以及任務(wù)在Scheduler中的組名和Job名稱;
●Trigger:是一個(gè)類,描述觸發(fā)Job執(zhí)行的時(shí)間觸發(fā)規(guī)則。主要有SimpleTrigger和CronTrigger這兩個(gè)子類。當(dāng)僅需觸發(fā)一次或者以固定時(shí)間間隔周期執(zhí)行,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達(dá)式定義出各種復(fù)雜時(shí)間規(guī)則的調(diào)度方案:如每早晨9:00執(zhí)行,周一、周三、周五下午5:00執(zhí)行等;
●Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日歷特定時(shí)間點(diǎn)的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個(gè)日歷時(shí)間點(diǎn),無特殊說明后面的Calendar即指org.quartz.Calendar)。一個(gè)Trigger可以和多個(gè)Calendar關(guān)聯(lián),以便排除或包含某些時(shí)間點(diǎn)。
假設(shè),我們安排每周星期一早上10:00執(zhí)行任務(wù),但是如果碰到法定的節(jié)日,任務(wù)則不執(zhí)行,這時(shí)就需要在Trigger觸發(fā)機(jī)制的基礎(chǔ)上使用Calendar進(jìn)行定點(diǎn)排除。針對不同時(shí)間段類型,Quartz在org.quartz.impl.calendar包下提供了若干個(gè)Calendar的實(shí)現(xiàn)類,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分別針對每年、每月和每周進(jìn)行定義;
●Scheduler:代表一個(gè)Quartz的獨(dú)立運(yùn)行容器,Trigger和JobDetail可以注冊到Scheduler中,兩者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據(jù),Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因?yàn)樗鼈兪遣煌愋偷模?。Scheduler定義了多個(gè)接口方法,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。
Scheduler可以將Trigger綁定到某一JobDetail中,這樣當(dāng)Trigger觸發(fā)時(shí),對應(yīng)的Job就被執(zhí)行。一個(gè)Job可以對應(yīng)多個(gè)Trigger,但一個(gè)Trigger只能對應(yīng)一個(gè)Job??梢酝ㄟ^SchedulerFactory創(chuàng)建一個(gè)Scheduler實(shí)例。Scheduler擁有一個(gè)SchedulerContext,它類似于ServletContext,保存著Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內(nèi)的信息。SchedulerContext內(nèi)部通過一個(gè)Map,以鍵值對的方式維護(hù)這些上下文數(shù)據(jù),SchedulerContext為保存和獲取數(shù)據(jù)提供了多個(gè)put()和getXxx()的方法??梢酝ㄟ^Scheduler# getContext()獲取對應(yīng)的SchedulerContext實(shí)例;
●ThreadPool:Scheduler使用一個(gè)線程池作為任務(wù)運(yùn)行的基礎(chǔ)設(shè)施,任務(wù)通過共享線程池中的線程提高運(yùn)行效率。
Job有一個(gè)StatefulJob子接口,代表有狀態(tài)的任務(wù),該接口是一個(gè)沒有方法的標(biāo)簽接口,其目的是讓Quartz知道任務(wù)的類型,以便采用不同的執(zhí)行方案。無狀態(tài)任務(wù)在執(zhí)行時(shí)擁有自己的JobDataMap拷貝,對JobDataMap的更改不會(huì)影響下次的執(zhí)行。而有狀態(tài)任務(wù)共享共享同一個(gè)JobDataMap實(shí)例,每次任務(wù)執(zhí)行對JobDataMap所做的更改會(huì)保存下來,后面的執(zhí)行可以看到這個(gè)更改,也即每次執(zhí)行任務(wù)后都會(huì)對后面的執(zhí)行發(fā)生影響。
正因?yàn)檫@個(gè)原因,無狀態(tài)的Job可以并發(fā)執(zhí)行,而有狀態(tài)的StatefulJob不能并發(fā)執(zhí)行,這意味著如果前次的StatefulJob還沒有執(zhí)行完畢,下一次的任務(wù)將阻塞等待,直到前次任務(wù)執(zhí)行完畢。有狀態(tài)任務(wù)比無狀態(tài)任務(wù)需要考慮更多的因素,程序往往擁有更高的復(fù)雜度,因此除非必要,應(yīng)該盡量使用無狀態(tài)的Job。
如果Quartz使用了數(shù)據(jù)庫持久化任務(wù)調(diào)度信息,無狀態(tài)的JobDataMap僅會(huì)在Scheduler注冊任務(wù)時(shí)保持一次,而有狀態(tài)任務(wù)對應(yīng)的JobDataMap在每次執(zhí)行任務(wù)后都會(huì)進(jìn)行保存。
Trigger自身也可以擁有一個(gè)JobDataMap,其關(guān)聯(lián)的Job可以通過JobExecutionContext#getTrigger().getJobDataMap()獲取Trigger中的JobDataMap。不管是有狀態(tài)還是無狀態(tài)的任務(wù),在任務(wù)執(zhí)行期間對Trigger的JobDataMap所做的更改都不會(huì)進(jìn)行持久,也即不會(huì)對下次的執(zhí)行產(chǎn)生影響。
Quartz擁有完善的事件和監(jiān)聽體系,大部分組件都擁有事件,如任務(wù)執(zhí)行前事件、任務(wù)執(zhí)行后事件、觸發(fā)器觸發(fā)前事件、觸發(fā)后事件、調(diào)度器開始事件、關(guān)閉事件等等,可以注冊相應(yīng)的監(jiān)聽器處理感興趣的事件。
下面寫個(gè)例子,每隔1秒控制臺打印一條信息。
1、Create a Java project,目錄結(jié)構(gòu)如下:
2、引入相關(guān)jar
3、HelloWorldJob類,定時(shí)任務(wù)類
package com.ljq.quartz;import java.text.SimpleDateFormat;import java.util.Date;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.quartz.Scheduler;import org.quartz.Trigger;/** * 定時(shí)任務(wù)類 * * @author 林計(jì)欽 * @version 1.0 2013-7-9 上午09:19:52 */public class HelloWorldJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { //執(zhí)行定時(shí)器任務(wù) Trigger trigger = context.getTrigger(); Scheduler scheduler=context.getScheduler(); System.out.println(String.format("hello world at %s, trigger[%s - %s]", date(), trigger.getGroup(), trigger.getName())); } private String date(){ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(new Date()); }}
4、 HelloWorldJobTest測試類
package com.ljq.quartz;import java.text.ParseException;import org.quartz.CronTrigger;import org.quartz.JobDetail;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.impl.StdSchedulerFactory;/** * Quartz測試類 * * @author 林計(jì)欽 * @version 1.0 2013-7-9 上午09:22:43 */public class HelloWorldJobTest { public static void main(String[] args) throws SchedulerException, ParseException { JobDetail job = new JobDetail("helloWorldJob", Scheduler.DEFAULT_GROUP, HelloWorldJob.class); //Trigger trigger = TriggerUtils.makeSecondlyTrigger(1); //trigger.setName("helloWorldTrigger"); //trigger.setStartTime(new Date()); CronTrigger trigger = new CronTrigger("helloWorldTrigger", "cronGroup", "1/1 * * * * ?"); Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); scheduler.scheduleJob(job, trigger); //scheduler.shutdown(); }}
5、運(yùn)行結(jié)果: