1. 程式人生 > >Quartz排程器開發指南----中文版

Quartz排程器開發指南----中文版

Quartz排程器開發指南     V 2.2.1--轉載

目錄

關鍵介面

配置元件

日誌

外掛

高階功能

叢集

JTA事務

使用Quartz API

例項化Scheduler

在你使用Scheduler之前,首先需要例項化。你必須使用SchedulerFactory來完成。

很多Quartz的使用者在JNDI中儲存一個工廠的例項,這樣非常容易直接使用一個工廠例項。

一旦一個scheduler被例項化,它就能被啟動、設定為備用模式、停止。請注意,一旦你關閉了scheduler,不重新例項化就無法重啟它。Scheduler不啟動trigger也不會啟動(因此,job也不會執行)。Scheduler處於暫停狀態它們都不會工作。

下面的程式碼段例項化並啟動一個scheduler,安排一個job執行。

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

Scheduler sched = schedFact.getScheduler();

sched.start();

// define the job and tie it to our HelloJob class

JobDetail job = new Job(HelloJob.class)

.withIdentity("myJob", "group1")

.build();

// Trigger the job to run now, and then every 40 seconds

Trigger trigger = new Trigger()

.withIdentity("myTrigger", "group1")

.startNow()

.withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever())

.build();

// Tell quartz to schedule the job using our trigger

sched.scheduleJob(job, trigger);

關鍵介面

Quartz API的關鍵介面:

l  Scheduler——Scheduler進行互動的主要API。

l  Job——你想讓Scheduler執行時需要實現的介面元件。

l  JobDetail——用於定義Job的例項。

l  Trigger——一個定義在將要執行Job的scheduler上的元件。

l  JobBuilder——已定義的Job例項,用於定義/構建的JobDetail例項。

l  TriggerBuilder——用於定義/構建Trigger例項。

一個scheduler的生命週期是有限的,從SchedulerFactory開始直到呼叫它的shutdown()方法。

Scheduler介面一旦建立,就能新增、移除、和列出job和trigger,並執行另一些與scheduling相關的操作(例如暫停trigger),然而,在scheduler被start()方法啟動前,它不會作用於任何trigger(執行job),見第3頁“例項化Scheduler”。

Quartz提供”builder”類定義領域特定語言(或DSL,有時也被稱為“連貫介面”)。這裡有一個例子:

// define the job and tie it to our HelloJob class

JobDetail job = new Job(HelloJob.class)

.withIdentity("myJob", "group1") // name "myJob", group "group1"

.build();

// Trigger the job to run now, and then every 40 seconds

Trigger trigger = new Trigger()

              .withIdentity("myTrigger", "group1")

.startNow()

.withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever())

.build();

// Tell quartz to schedule the job using our trigger

sched.scheduleJob(job, trigger);

使用JobBuilder類的靜態方法建立job的程式碼塊。同樣,構建trigger的程式碼塊使用TriggerBuilder類的靜態方法建立,SimpleScheduleBulder類也一樣。

DSL的靜態匯入可以接受這樣的匯入語句:

import static org.quartz.JobBuilder.*;

import static org.quartz.SimpleScheduleBuilder.*;

import static org.quartz.CronScheduleBuilder.*;

import static org.quartz.CalendarIntervalScheduleBuilder.*;

import static org.quartz.TriggerBuilder.*;

import static org.quartz.DateBuilder.*;

不同的SchedulerBuilder類擁有不同定義schedules型別的方法。

DateBuilder類包含有不同型別的方法去簡單的構造java.util.Date例項的特定的時間點(例如如果當前時間為9:43:27,下一小時執行的時間,或者換句話說10:00)。

Job和Trigger

一個job就是實現了Job介面的類。如下所示,這個介面有一個簡單的方法:

package org.quartz;

publicinterface Job {

         publicvoid execute(JobExecutionContext context) throws JobExecutionException;

}

當一個job的觸發器觸發時,execute(..)方法被Scheduler的一個工作執行緒呼叫。JobExecutionContext物件就是通過這個方法提供job例項的資訊,如執行環境,包括scheduler執行的控制代碼,trigger觸發執行的控制代碼,job的JobDetail物件,和一些其它資訊。

JobDetail物件由Quartz客戶端(你的程式)在job新增進scheduler的時候建立。這個物件包含了很多job的屬性設定,和JobDataMap一樣,它能夠儲存你的job例項的狀態資訊。JobDetail物件本質上是定義的job例項。

Trigger物件常常用於觸發job的執行(或者觸發),當你希望安排一個job,你可以例項化一個trigger,並“調整”其屬性來提供你想要的排程。

Trigger可能有一個與它們關聯的JobDataMap物件。JobDataMap用於傳遞特定於觸發器觸發的工作引數。Quartz附帶一些不同的觸發型別,但是最常用的就是SimpleTrigger和CronTrigger。

l  SimpleTrigger很方便,如果你需要“一次性”執行(只是在一個給定時刻執行job),或者如果你需要一個job在一個給定的時間,並讓它重複N次,並在執行之間延遲T。

l  CronTrigger是有用的,如果你想擁有引發基於當前日曆時間表,如“每個星期五,中午”或“在每個月的第十天 10:15。”

為什麼會同時有job和trigger?許多工作排程器沒有單獨的job和trigger的概念。一些定義一個job只是一個執行時間(或計劃)以及一些小的工作識別符號。其它的大部分就像Quartz的job和triiger物件的結合體。Quartz旨在建立一個分離的scheduler和scheduler的執行。這個設計有很多好處。

例如,你可以建立獨立於trigger的job並將他們儲存在job scheduler。這使你能在同一個job上關聯多個trigger。另一個解耦的好處是,能夠在scheduler關聯的trigger已過期時仍能配置job。這使你在重新安排它們後,不用重新定義它們。它還允許您修改或替換一個trigger,而不必重新定義相關的job。

特性

當Job和trigger註冊進Quartz scheduler時給出了key。Job和trigger(JobKey和TriggerKey)的key允許它們放被放置到group中,group可用於分類組織你得job和trigger,例如“報表job”和“維護job”。Job或者trigger的key值在group中必須是唯一的。換句話說,job或trigger的完整的key(或標識)是由key和group組成。

有關job和trigger的詳細資訊,請參見第6頁“Job和JobDetails”和第10頁“使用trigger”。


使用Job和JobDetail

Job和JobDetail

Job很容易實現,介面中只有一個“execute”方法。Job有更多的東西你需要了解,就是關於Job介面的execute(..)方法,和JobDetails。

當你實現一個job類時,程式碼知道如何實現job的特定型別的實際工作,你或許希望瞭解job例項擁有的Quartz的各種屬性。

使用JobBuilder類構建JobDetail例項。你通常會想要使用靜態匯入匯入所有的方法,為了你的程式碼能夠DSL-feel。

import static org.quartz.JobBuilder.*;

下面的程式碼片段定義了一個job並安排它的執行:

// define the job and tie it to our HelloJob class

JobDetail job = new Job(HelloJob.class)

.withIdentity("myJob", "group1") // name "myJob", group "group1"

.build();

// Trigger the job to run now, and then every 40 seconds

Trigger trigger = new Trigger()

.withIdentity("myTrigger", "group1")

.startNow()

.withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever())

.build();

// Tell quartz to schedule the job using our trigger

sched.scheduleJob(job, trigger);

現在考慮這個job類HelloJob,如下所示:

publicclass HelloJob implements Job {

public HelloJob() {

}

publicvoid execute(JobExecutionContext context) throws JobExecutionException {

System.err.println("Hello! HelloJob is executing.");

}

}

請注意我們給scheduler一個JobDetail例項,而且它知道要執行的job的型別,只需提供我們構建JobDetail的job類。每次scheduler執行job,它會在呼叫它的execute(..)方法之前建立這個類的新例項。當執行完成,job例項的引用被刪除,然後垃圾收集器回收例項。

這種行為的後果之一是job必須有一個無引數的建構函式(當使用預設JobFactory實現)。另一個是job類上定義的沒有意義的資料欄位的狀態,它們的值在Job執行期間不會被儲存。

你能夠使用JobDataMap為job例項提供屬性/配置或跟蹤job執行間的狀態,,它是JobDetail物件的一部分。

JobDataMap

JobDataMap可以用來儲存大量那些在job例項執行時你希望得到的 (序列化)資料物件。JobDataMap是Java Map介面的一個實現,並且新增一些便利的方法來儲存和檢索資料的原始型別。

當定義/構建JobDetail,這裡有一些將job新增進scheduler之前放到JobDataMap的資料。

// define the job and tie it to our DumbJob class

JobDetail job = new Job(DumbJob.class)

.withIdentity("myJob", "group1") // name "myJob", group "group1"

.usingJobData("jobSays", "Hello World!")

.usingJobData("myFloatValue", 3.141f)

.build();

這是一個例子,在job執行期間,從JobDataMap獲取資料:

publicclass DumbJob implements Job {

public DumbJob() {

}

publicvoid execute(JobExecutionContext context) throws JobExecutionException {

JobKey key = context.getJobDetail().getKey();

JobDataMap dataMap = context.getJobDetail().getJobDataMap();

String jobSays = dataMap.getString("jobSays");

floatmyFloatValue = dataMap.getFloat("myFloatValue");

System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);

}

}

如果你使用一個持久化的JobStore(在本教程的JobStore節討論)你應該注意在決定你在JobDataMap中安排什麼,因為裡面的物件將序列化,他們因此會有類版本問題。顯然標準Java型別應該很安全,但除此之外,任何時候有人改變一個類的定義的序列化例項,必須小心不要打破相容性。可選地,您可以將JDBC-JobStore和JobDataMap放入model中,只允許基本型別和字串儲存在map中,從而消除任何後來序列化問題的可能性。

如果你為job類新增setter方法,相當於在JobDataMap中對應的key (如上面的示例中setJobSays(String val)方法,然後quartz的預設JobFactory實現將自動呼叫這些setter當Job被例項化,從而防止需要顯式地在你的執行方法獲得map的值。

trigger也可以有與他們有關的JobDataMaps。如果你有一個job儲存在scheduler由多個trigger常規/重複使用,然而每個獨立的觸發,你想工作提供不同的資料輸入,這是有用的。

job執行期間在JobExecutionContext上建立JobDataMap十分方便。它是基於JobDetail的JobDataMap和基於Trigger上的一個合併,後者任何相同名字的值將覆蓋前者。

這是一個簡單的例子在job執行期間從JobExecutionContext合併的JobDataMap獲取資料:

publicclass DumbJob implements Job {

public DumbJob() {

}

publicvoid execute(JobExecutionContext context) throws JobExecutionException {

JobKey key = context.getJobDetail().getKey();

JobDataMap dataMap = context.getMergedJobDataMap();

// Note the difference from the previous example

String jobSays = dataMap.getString("jobSays");

floatmyFloatValue = dataMap.getFloat("myFloatValue");

ArrayList state = (ArrayList) dataMap.get("myStateData");

state.add(new Date());

System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);

}

}

或者如果你想依靠JobFactory注入資料對映到類的值,它可能看起來像這樣:

publicclass DumbJob implements Job {

String jobSays;

floatmyFloatValue;

ArrayList state;

public DumbJob() {

}

publicvoid execute(JobExecutionContext context) throws JobExecutionException {

JobKey key = context.getJobDetail().getKey();

JobDataMap dataMap = context.getMergedJobDataMap();

// Note the difference from the previous example

state.add(new Date());

System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);

}

publicvoid setJobSays(String jobSays) {

this.jobSays = jobSays;

}

publicvoid setMyFloatValue(floatmyFloatValue) {

myFloatValue = myFloatValue;

}

publicvoid setState(ArrayList state) {

state = state;

}

}

你會注意到整個程式碼的類是長,但是execute()方法中的程式碼更乾淨。當然你也可以說,雖然程式碼比較長,它實際上花了更少的編碼,如果程式設計師用IDE自動生成setter方法,而不必手工從JobDataMap檢索值。這是你的選擇。

Job例項

你可以建立一個單一的工作類,在scheduler中儲存被JobDetails建立的多個例項定義——每個都有自己的一組屬性和JobDataMap——並將它們新增到排程器。

例如,您可以建立一個類稱為SalesReportJob,實現了job介面。這個job也許被編碼成預計的引數(通過JobDataMap)傳送給指定的基於銷售報告的銷售人員。然後,他們可能會建立多個Job定義(JobDetails),比如SalesReportForJoe和SalesReportForMike中指定相應的JobDataMaps作為“Joe”和“Mike”各自的輸入工作。

當trigger觸發,JobDetail(例項定義)被關聯載入,Job類通過scheduler中的配置JobFactory例項化。預設JobFactory簡單地呼叫Job類的newInstance(),然後試圖呼叫類中與JobDataMap鍵的名稱相匹配setter方法。您可能想要建立自己的JobFactory實現,諸如應用程式的IoC或DI容器生產/例項化job類。

每個儲存的JobDetail作為job定義或JobDetail例項被引用,每個執行的job都是一個job例項或job定義的例項。一般來說,使用術語“工作”時,指的是定義的名稱或JobDetail。類實現的工作介面,稱為“job類”。

Job狀態和併發

下面是job狀態資料和併發的一些注意點。你可以新增一些註解在你的job類上,在一些方面影響Quartz的行為。

@DisallowConcurrentExecution註釋,可以新增到job類上,通知Quartz不要同時執行給定的job定義(引用給定的job類)的多個例項。在上一個例子中,如果SalesReportJob用了這個註解,在給定時間將只能執行SalesReportJob的一個例項,但是它可以同時執行“SalesReportForMike”的一個例項。約束是基於例項的定義(JobDetail),而不是job類的例項。然而,它決定了註解參與到類本身中(在Quartz設計時),因為它對類如何編碼產生影響。

@PersistJobDataAfterExecution註釋可以新增到job類,通知Quartz在execute()方法成功執行後更新JobDetail的JobDataMap的拷貝,這樣相同的job下一次執行時接收到更新的值,而不是原來儲存的值。和@DisallowConcurrentExecution註解一樣,應用到job定義的例項上,而不是job類的例項,雖然它給job類帶來了屬性,因為它對類如何編碼產生影響。(例如,“有狀態性”需要明確理解執行方法的程式碼)。

如果你使用@PersistJobDataAfterExecution註解,您還應該考慮使用@DisallowConcurrentExecution註解,以避免可能的混亂(競爭條件)的資料被儲存在相同的工作的兩個例項(JobDetail)併發執行。

Job的其他屬性

這裡有你可以通過JobDetail物件定義job例項的另一些屬性的快速總結:

永續性——如果一個job不是持久的, 一旦不再有任何活動的與之關聯的trigger它將自動從scheduler刪除。換句話說,不持久的job的生命週期依賴於它的trigger的存在。

RequestsRecovery——如果一份工作“請求恢復”,在scheduler強制關閉期間執行(即程序是執行崩潰,或機器關閉),在scheduler再次啟動時會重新執行。在這種情況下,JobExecutionContext.isRecovering()方法將返回true。

JobExecutionException

最後,我們需要告訴你Job.execute(..)一些細節。唯一型別的異常(包括RuntimException),你可以從執行方法丟擲JobExecutionException。因此,你通常應該使用“rycatch”塊包裹執行方法。你也應該花些時間看著JobExecutionException的文件,因為你的job可以使用它提供scheduler作為你想如何處理異常的各種指令。


使用trigger

Trigger

和job一樣,trigger容易處理,但是他們有不同的定製項,在你充分使用Quartz之前需要了解它們。

不同型別的trigger用來滿足不同的排程需求。常用的有兩種,SimpleTrigger和CronTrigger。關於這些特定觸發器的細節在第10頁“使用trigger”。

通用的trigger屬性

除了所有的觸發器都有TriggerKey屬性作為追蹤它們的識別符號外,它們還有很多通用的其它屬性。當你建立trigger定義時你可以使用TriggerBuilder設定這些通用的屬性(如下示例)。

這是通用的觸發器型別:

l  屬性jobKey是trigger觸發將要執行的job的標識。

l  startTime是trigger的計劃第一次生效的標識。值是一個java.util.Date物件,定義了一個給定的日期時間。對於許多型別的trigger來說,trigger應該在開始時間準確的啟動。對於其它型別來說標誌著scheduler開始被追蹤。這意味著你可以這樣儲存一個scheduler的trigger,例如一月“每月的第5天執行”,如果startTime屬性設定為4月1日,在首次觸發前,還要過幾個月。

l  endTime屬性表示trigger的計劃不再有效。換句話說,一個“每月5號”的trigger,endTime為7月1號,它上次執行的時間就是6月5號。

其它屬性,還有更多的解析,將在下面討論。

優先順序

有時,當你有很多觸發器(或者你得Quartz執行緒池有很多工作執行緒),Quartz也許沒有足夠的資源在同一時間觸發所有的trigger。這種情況下,你也許想控制你的哪些觸發器可以優先獲得Quartz的工作執行緒。為此,你需要設定trigger的優先順序屬性。如果N個觸發器同一時間觸發,但是當前僅有Z和可用的工作執行緒,前Z個具有高優先順序的觸發器將首先執行。

任何整數,正數或者負數都允許作為優先順序。如果你的trigger沒有設定優先順序它將使用預設的優先順序為5。

注意:優先順序僅僅在同一時間觸發的trigger中才有效。一個在10:59觸發的trigger將永遠比在11:00觸發的trigger先執行。

注意:當觸發器被監測到需要恢復時,它的優先順序將與原始的優先順序相同。

觸發失敗說明

Trigger另一個重要的屬性就是“過時觸發指令”。如果因為當前scheduler被關閉或者Quartz的執行緒池沒有可用的執行緒用來執行job導致當前的觸發器錯過了觸發時間,就是出現錯誤。

不同型別的trigger有不同的失敗處理方式。預設情況下,它們使用“智慧策略”,其擁有基於trigger型別和配置的動態行為。Scheduler啟動時,它將搜尋所有失敗的持久化的trigger,然後基於它們的失敗配置來更新它們。

當你在你的專案中使用Quartz,一定要熟悉它們javaDoc中給定trigger的失敗處理策略。更多關於特殊trigger型別的失敗策略在第10頁“使用trigger”也有提供。

日曆

Quartz日曆物件(不是java.util.Calendar物件)能夠與在scheduler中定義和儲存的trigger時間相關聯。

日曆可用於從trigger的觸發計劃中排除時間塊。例如,你可以建立一個trigger在工作日的9:30觸發,但是排除法定假日。

日曆可以使任何實現了Calendar介面的序列化物件(如下所示):

package org.quartz;

publicinterface Calendar {

publicboolean isTimeIncluded(long timeStamp);

publiclong getNextIncludedTime(long timeStamp);

}

注意這些方法的引數型別是long。這是毫秒的時間戳格式。這意味著日曆可以“阻擋”精確至一毫秒的時間。最有可能的是你會對“遮蔽”一整天感興趣。Quartz包括一個org.quartz.impl.HolidayCalendar就是幹這個的,非常方便。

日曆必須通過addCalendar(..)方法例項化並註冊到scheduler中。如果你使用HolidayCalendar,例項化之後,你應該使用它的addExcludedDate(Date date)方法將你想排除在排程器之外的日期新增進去。同一個日曆例項可以被多個trigger使用,如下所示:

HolidayCalendar cal = new HolidayCalendar();

cal.addExcludedDate(someDate);

cal.addExcludedDate(someOtherDate);

sched.addCalendar("myHolidays", cal, false);

Trigger t = new Trigger()

.withIdentity("myTrigger")

.forJob("myJob")

.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30

.modifiedByCalendar("myHolidays") // but not on holidays

.build();

// .. schedule job with trigger

Trigger t2 = new Trigger()

.withIdentity("myTrigger2")

.forJob("myJob2")

.withSchedule(dailyAtHourAndMinute(11, 30)) // execute job daily at 11:30

.modifiedByCalendar("myHolidays") // but not on holidays

.build();

// .. schedule job with trigger2

上面的程式碼建立了兩個觸發器,每天都會執行。然而,任何發生在排除日曆的觸發將被跳過。

org.quartz.impl.calendar包提供了許多Calendar的實現,也許可以滿足你的需求。

SimpleTrigger

如果你需要一個job在特定的時間執行一次或者在特定的時間,以特定的間隔執行多次SimpleTrigger能夠滿足你的需求。例如:你想讓一個trigger在2015年1月13號11:23:54分觸發,或者你想在那個時間執行,執行5次,每10秒一次。

這樣描述,你也許不難發現SimpleTrigger的屬性:開始時間,結束時間,重複次數,重複間隔。所有這些屬性正是你期望的,只有一組與結束時間關聯的特殊屬性。

重複的次數可以是0,正數或者常數SimpleTrigger.REPEAT_INDEFINITELY。重複的間隔屬性必須是0或一個正的長整型,代表毫秒值。注意,間隔為0將導致觸發器同時觸發“重複次數”這麼多次(儘可能接近scheduler的併發管理數)。

如果你還不熟悉Quartz的DateBuilder類,你可能發現依賴於你的開始時間(或結束時間)計算trigger的觸發次數很有幫助。

endTIme屬性重寫了重複次數的屬性。如果你想建立一個每10秒觸發一次的trigger直到一個給定的時刻,這也許會有用。而不必計算開始時間和結束時間間的重複次數,你可以簡單的指出結束時間然後使用REPEAT_INDEFINITELY表明重複次數(你甚至可以指定較大的重複次數超過在結束時間到達之前trigger觸發的次數)。SimpleTrigger例項通過使用TriggerBuilder(建立trigger的主屬性)和SimpleSchedulerBuilder(建立SimpleTrigger特殊屬性)建立。使用靜態匯入DSL-style使用這些構造工具。

import static org.quartz.TriggerBuilder.*;

import static org.quartz.SimpleScheduleBuilder.*;

import static org.quartz.DateBuilder.*:

使用不同的Scheduler定義trigger的例子

下面是使用簡單的scheduler定義觸發器的一些例子。仔細觀察,它們每個至少展示了一個新的/不同的點。

同時,花費一些時間檢視TriggerBuilder和SimpleSchedulerBuilder定義的所有可用方法以便熟悉這裡的示例沒展示但是你可能會使用到的選項。

注意:注意如果你不顯示的設定屬性TriggerBuilder(和其它的quartz建立者)會選取一個合理的值。例如,如果你不呼叫withIdentity(..)的某個方法,TriggerBuilder將為你的trigger生成一個隨機的名字,同樣,如果你沒有呼叫startAt(..),則設定為當前的時間(立即執行)。

建立一個指定時間執行的trigger,沒有重複次數

SimpleTrigger trigger = (SimpleTrigger) new Trigger()

.withIdentity("trigger1", "group1")

.startAt(myStartTime) // some Date

.forJob("job1", "group1") // identify job with name, group strings

.build();

在指定的時間建立一個trigger,然後每10秒觸發一次,共觸發10次

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.startAt(myTimeToStartFiring) // if a start time is not given (if this line  were omitted), "now" is implied

.withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings

.forJob(myJob) // identify job with handle to its JobDetail itself

.build();

建立一個觸發一次的trigger,5分鐘後觸發

trigger = (SimpleTrigger) new Trigger()

.withIdentity("trigger5", "group1")

.startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future

.forJob(myJobKey) // identify job with its JobKey

.build();

建立一個立即觸發的trigger,每5分鐘觸發一次,直到22:00結束

trigger = new Trigger().withIdentity("trigger7", "group1")

.withSchedule(simpleSchedule().withIntervalInMinutes(5).repeatForever())

.endAt(dateOf(22, 0, 0))

.build();

建立一個trigger,每小時開始的時候觸發,每2小時出發一次,直到永遠

trigger = new Trigger()

.withIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group

.startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))

.withSchedule(simpleSchedule().withIntervalInHours(2).repeatForever())// note that in this example, 'forJob(..)' is not called - which is valid if the trigger is passed to the scheduler along with the job

.build();

scheduler.scheduleJob(trigger, job);

SimpleTrigger失敗說明

SimpleTrigger有幾個指令,用來通知quartz當觸發失敗該怎麼做。(關於trigger觸發失敗的資訊,參見trigger章節)這些指令被SimpleTrigger自己定義(它們的行為在Javadoc中有描述)。這些常量包括:

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

MISFIRE_INSTRUCTION_FIRE_NOW

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

所有的trigger都有MISFIRE_INSTRUCTION_SMART_POLICY屬性可以使用,這個指定對所有的觸發器型別都是可用的。

如果使用“智慧策略”,SimpleTrigger會基於給定SimpleTrigger例項的配置或語句之間動態的選擇觸發失敗指令。SimpleTrigger.updateAfterMisfire()方法的Javadoc解釋了這些動態行為的細節。

在建立SimpleTrigger時,你可以指定失敗指令作為簡單scheduler的一部分(通過SimpleSchedulerBuilder):

trigger = new Trigger()

.withIdentity("trigger7", "group1")

.withSchedule(simpleSchedule().withIntervalInMinutes(5).repeatForever().withMisfireHandlingInstructionNextWithExistingCount())

.build();

CronTriggers

Cron是一個UNIX工具,已經存在了很長時間,所以它的排程功能強大且得到證實。CronTrigger類是基於cron的排程功能。

CronTrigger使用“cron表示式”,其能夠像這樣建立觸發的計劃:“每週一至週五早上8:00”或者“每月最後一個週五早上1:30”。

如果你需要基於類似日曆的觀念反覆觸發一個job,而不是使用SimpleTrigger指定一個固定的時間間隔,相對於SimpleTrigger,CronTrigger往往更有用。

使用CronTrigger,你可以指定一個觸發計劃例如“每週五的中午”,或者“每個工作日上午9:00”,甚至“1月的週一,週三和週五早上9:00至10:00每五分鐘”。

即便如此,和SimpleTrigger一樣, CronTrigger也有一個startTime指定計劃的生效時間也有一個(可選的)endTime指定計劃的停止時間。

Cron表示式

Cron表示式用於CronTrigger例項的配置。Cron表示式實際上是由7個子表示式組成,用來描述計劃安排的細節。這些子表示式使用空格分割:

l  秒

l  分

l  時

l  每月的第幾天

l  月

l  每週的第幾天

l  年(可選欄位)

一個完整的cron表示式是一個像這樣的字串“0 0 12 ? WED *”——這代表著“每週三的12:00”。

每個子表示式都能包含範圍/或列表,例如前面每週的第幾天(“WED”)可以替換成“MON-FRI”,“ MON,WED,FRI”,甚至“MON-WED,SAT”。

可使用萬用字元(“*”)代表這個欄位上的每一個可能的值。因此“月”欄位上使用“*”代表著“每個月”。“*”在“每週的第幾天”代表“一週的每一天”。

所有的欄位都可以指定一個有效值。這些值應該相當明顯,例如數字0-59代表秒數和分鐘數,0-23代表小時。每月中的第幾天可能是1-31,但是你應該注意一個指定的月份應該有多少天!月份可以指定0-11,或者使用字串JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV和DEC。每週的第幾天可以使用1-7(1=週日)或者使用字串SUN, MON, TUE, WED, THU, FRI和SAT。

“/”符號被用來指定增量值。例如,如果你在分鐘欄位設定“0/15”,意味著“從0分開始,每15分鐘”。如果你在分鐘欄位使用“3/20”,意味著“從第3分鐘開始,每20分鐘”——換句話說它和在分鐘欄位上指定“3,23,43”是一樣的。注意“/35”並不是“每35分鐘”——它是指“從0開始,每35分鐘——換句話說就和“0,35”一樣。

“?”允許在每月中的第幾天和每週的第幾天上設定。它用於指定“無特殊值”。當你需要在這兩個欄位之一指定一些值,但不能同時設定。具體參見下面示例(和CronTrigger Javadoc)。

“L”允許在每月的第幾天和每週的第幾天上設定。這個符號是“last”的簡寫,但是它在這兩個欄位上的意思是不同的。例如,“L”在每月的第幾天欄位上指的是“每月的最後一天”——1月31號,和非閏年的2月28號。如果用在每週的第幾天,就是“7”或者“SAT”的意思。但是如果用在每週第幾天的一個值後面,就意味著“每月最後一個星期的第幾天”——例如“6L”或者“FRIL”都表示“每月最後一個週五”。你還可以為每月的最後一天指定一個偏移量,例如“L-3”意味著每月倒數第三天。當使用“L”選項時,重要的是不要指定列表或範圍值,不然你將得到混亂/不期望的結果。

“W”用於指定距離給定日期最近的一個工作日(週一至週五)。例如,如果你在每月的第幾天欄位上指定“15W”,意味著:“離本月15號最近的工作日”。

“#”用於指定每月中的“第幾個”周幾。例如,在每週的第幾天設定“6#3”或者“FRI#3”表示“本月第三個週五”。

格式

欄位如下:

欄位名稱

必填項

允許的值

允許的特殊字元

Seconds

YES

0-59

, - *

Minutes

YES

0-59

, - *

Hours

YES

0-23

, - *

Day of month

YES

1-31

, - * ? / L W

Month

YES

1-12或JAN-DEC

, - *

Day of week

YES

1-7或SUN-SAT

, - * ? / L #

Year

NO

空值,1970-2099

, - *

最簡單的cron表示式可以寫成:* * * * ? *

或者比較複雜的:0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010

特殊字元

l  *(所有值)——用來選擇一個欄位上的所有值。例如:分鐘欄位上的“*”表示“每分鐘”。

l  ?(無特定值)——當你需要在允許的兩個欄位中的一個指定一些值的時候使用。例如,你想讓你得trigger在每月的某一天觸發(假如,10號),但是不關係在周幾發生,你應該在每月的第幾天欄位上設定“10”,在每週的第幾天欄位上設定“?”。具體參見下面例項。

l  -——用於指定範圍。例如,小時欄位上設定“10-12”表示“10點,11點,12點”。

l  ,——用於指定額外的值。例如,在每週的第幾天設定“MON,WED,FRI”表示“週一,週三,週五”。

l  /——用於指定增量。例如,秒欄位上“0/15”表示“0秒,15秒,30秒,45秒”。秒欄位上“5/15”表示“5秒,20秒,35秒,50秒”,你還可以在“character - in this case”後面指定“/”,相當於“/”前有一個“0”,每月中的第幾天設定“1/3”表示“從每月的第一天開始每3天觸發一次”。

l  L(“last”簡寫)——可用在兩個欄位每個子段的含義不同。例如,“L”在每月的第幾天欄位上指的是“每月的最後一天”——1月31號,和非閏年的2月28號。如果用在每週的第幾天,就是“7”或者“SAT”的意思。但是如果用在每週第幾天的一個值後面,就意味著“每月最後一個星期的第幾天”——例如“6L”表示“每月最後一個週五”。你還可以為每月的最後一天指定一個偏移量,例如“L-3”意味著每月倒數第三天。當使用“L”選項時,重要的是不要指定列表或範圍值,不然你將得到混亂/不期望的結果。

l  W(工作日)——用來指定距離指定日期最近的工作日(週一至週五)。例如,如果你指定了“15W”在每月第幾天的欄位上,表示:“離本月15號最近的工作日”。所以,如果15號是週六,trigger將在14號週五觸發,如果15號是週日,trigger將在16號週一觸發,如果15號是週二,trigger將在15號週二觸發。然而如果在每月的第幾天欄位指定“1W”,而且1號是週六,trigger將會在3號週一觸發,它不會跳過月的邊界。“W”只能指定每月第幾天欄位中的一天,不能指定一個範圍或列表。

l  “L”和“W”字元也可以在每月的第幾天欄位組合成“LW”,表示“每月的最後一個工作日”。

l  #——用於指定每月中的第幾個周幾。例如,每週第幾天欄位上的“6#3”表示“每月的第3個週五”(”6“=週五,”#3“=每月的第3個)。另一個例子:”2#1“=每月的第1個週一,”4#5“每月的第5個週三。注意,如果你指定”#5”,但是該月沒有第5個這一週的天數,觸發器將不會執行。

注意:月份的英文名和周幾的英文是不區分大小寫的。MON與mon都是合法的字元。

示例

這裡有一些完整的示例:

表示式

含義

0 0 12 * * ?

每天12:00觸發

0 15 10 ? * *

每天10:15觸發

0 15 10 * * ?

每天10:15觸發

0 15 10 * * ? *

每天10:15觸發

0 15 10 * * ? 2005

2005年的每天10:15觸發

0 * 14 * * ?

每天14:00至14:59每分鐘觸發

0 0/5 14 * * ?

每天14:00開始至14:55每5分鐘觸發

0 0/5 14,18 * * ?

每天14:00開始至14:55每5分鐘觸發,18:00開始至18:55每5分鐘觸發

0 0-5 14 * * ?

每天14:00開始至14:05每分鐘觸發

0 10,44 14 ? 3 WED

三月份的每個週三14:10和14:44觸發

0 15 10 ? * MON-FRI

每個週一至週五上午10:15觸發

0 15 10 15 * ?

每月15號10:15觸發

0 15 10 L * ?

每月最後一天10:15觸發

0 15 10 L-2 * ?

每月倒數第2天10:15觸發

0 15 10 ? * 6L

每月最後一個週五10:15觸發

0 15 10 ? * 6L 2002-2005

2002年至2005年每月最後一個週五10:15觸發

0 15 10 ? * 6#3

每月第3個週五10:15觸發

0 0 12 1/5 * ?

每月的第一天開始,每個第5天12:00觸發

0 11 11 11 11 ?

每年11月11號11:11觸發

注意”?”和”*”在每週第幾天和每月第幾天欄位上的影響。

注意

l  支援不需要完全指定一週中的第幾天和一月中的第幾天(你必須在這兩個欄位之一使用”?”號)。

建立CronTrigger

使用TriggerBuilder(建立trigger的主屬性)和CronScheduleBuilder(建立CronTrigger特殊屬性)建立CronTrigger例項。用DSL-style使用這些構造類,使用靜態匯入:

import static org.quartz.TriggerBuilder.*;

import static org.quartz.CronScheduleBuilder.*;

import static org.quartz.DateBuilder.*:

建立一個每天8:00至17:00,每分鐘執行一次的觸發器

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))

.forJob("myJob", "group1").build();

建立一個每天10:42觸發的觸發器

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.withSchedule(dailyAtHourAndMinute(10, 42))

.forJob(myJobKey)

.build();

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.withSchedule(cronSchedule("0 42 10 * * ?"))

.forJob(myJobKey)

.build();

建立一個觸發器將在週三10:42觸發,基於系統預設的時區

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42))

.forJob(myJobKey)

.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))

.build();

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.withSchedule(cronSchedule("0 42 10 ? * WED"))

.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))

.forJob(myJobKey)

.build();

CronTrigger失敗說明

下面的說明可以在CronTrigger失敗時用來介紹Quartz資訊。(更多關於trigger失敗的情況介紹在教程中)。這些介紹是CronTrigger自己定義的常量(包括Javadoc描述的行為)。包括:

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

MISFIRE_INSTRUCTION_DO_NOTHING

MISFIRE_INSTRUCTION_FIRE_NOW

所有的trigger都有MISFIRE_INSTRUCTION_SMART_POLICY說明可供使用,這個說明也是所有trigger型別預設的。”智慧策略CronTrigger解讀為“MISFIRE_INSTRUCTION_FIRE_NOW。CronTrigger.updateAfterMisfire()方法的Javadoc解析了這些行為的細節。

在構建CronTrigger時,你可以(通過CronSchedulerBuilder)指定簡單schedule的失敗說明。

trigger = new Trigger()

.withIdentity("trigger3", "group1")

.withSchedule(cronSchedule("0 0/2 8-17 * * ?")

.withMisfireHandlingInstructionFireAndProceed())

.forJob("myJob", "group1")

.build();

使用TriggerListener和JobListener

TriggerListener和JobListener

監聽器是你基於scheduler中事件的活動建立的物件。就像它們名字所示,TriggerListener接收與trigger有關的事件,JobListener接收與Job有關的事件。

與trigger有關的事件包括trigger觸發,trigger觸發失敗(在第10頁“使用trigger”中討論),trigger執行完成(trigger完成job執行完畢)。

org.quartz.TriggerListener介面

publicinterface TriggerListener {

public String getName();

publicvoid triggerFired(Trigger trigger, JobExecutionContext context);

publicboolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

publicvoid triggerMisfired(Trigger trigger);

publicvoid triggerComplete(Trigger trigger, JobExecutionContext context, inttriggerInstructionCode);

}

與job有關的時間包括:job即將進行的通知,和job已經完成的通知。

org.quartz.JobListener介面

publicinterface JobListener {

public String getName();

publicvoid jobToBeExecuted(JobExecutionContext context);

publicvoid jobExecutionVetoed(JobExecutionContext context);

publicvoid jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);

}

建立你自己的監聽器

要建立一個監聽器,需要簡單建立一個實現org.quartz.TriggerListener介面或/和org.quartz.JobListener介面的物件。

然後在執行時監聽器被註冊到scheduler中,並且必須給定一個name(或者確切的說,它們必須能夠通過它們的getName()方法給出它們的名字)。

為了你的方便,而不需要實現這些介面,你的類還可以繼承JobListenerSupport或TriggerListenerSupport並簡單的重寫你感興趣的方法。

監聽器被schedule