概述
這篇文章是英文教程的中文翻譯,有些認爲暫時使用不到的特性有省略,英文文檔參見http://www.opensymphony.com/quartz/wikidocs/TutorialLesson1.html。
如何使用
使用QUARTZ調試程序之前,必須使用SchedlerFactory實例化Scheduler。一旦實例化Scheduler後可以啓動或者停止,需要注意的是一旦Scheduler關閉,必須重新實例化後才能夠重啓。任務只有在Scheduler啓動後纔會執行。
下面的代碼片斷實例化並啓動Scheduler,然後執行一個任務。
- SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
- Scheduler sched = schedFact.getScheduler();
- sched.start();
- JobDetail jobDetail = new JobDetail("myJob", null, DumbJob.class);
- Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every hour
- trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));
- trigger.setName("myTrigger");
- sched.scheduleJob(jobDetail, trigger);
任務/觸發器
要定義一個任務,只需要實現Job接口即可,Job接口如下:
- package org.quartz;public interface Job { public void execute(JobExecutionContext context)
- throws JobExecutionException;
- }
當任務被觸發時將調用execute方法,JobExecutionContext 參數提供關於任務的運行時環境,包括一個Scheduler的引用,觸發這個任務的觸發器的引用,任務的JobDetail實例和一些別的信息。
JobDetail 對象在添加任務到Scheduler時創建,這個對象和*JobDataMap* 都用來保存Job實例的狀態信息。
Trigger 用來觸發任務。要計劃一個任務,需要實例化一個觸發器並設置相關的屬性,觸發器也有一個關聯的JobDataMap用來傳遞參數給指定的任務。Quartz提供幾個不同的觸發器實例,比較常用的是SimpleTrigger和CronTrigger。
如果需要在一個指定的時間,或者指定的時間後以一個指定的間隔對一個任務重複執行多次,使用SimpleTrigger。如果需要基於日曆安排任務,使用CronTrigger,比如每個星期五中午。
很多任務調用器沒有分離任務和觸發器,Quartz這樣做有很多好處。比如一個任務可以和多個觸發器關聯,可以更改或替換一個觸發器,而不必重新定義任務。
任務和觸發器都有唯一標識名稱,也可以進行分組,在一個組中任務和觸發器的名稱必須是唯一的,這意味着任務和觸發器是使用名稱+組名唯一標識的。如果不指定組名,相當於使用缺省的組名: Scheduler.DEFAULT_GROUP 。
任務
以前的Quartz要求具體的Job實現類通過get/set方法傳遞數據,現在使用JobDetail類來傳遞數據。
我們先來看一個實例:
- JobDetail jobDetail = new JobDetail("myJob", // job name
- sched.DEFAULT_GROUP, // job group (you can also specify 'null' to use the default group)
- DumbJob.class); // the java class to executeTrigger trigger = TriggerUtils.makeDailyTrigger(8, 30);
- trigger.setStartTime(new Date());
- trigger.setName("myTrigger");sched.scheduleJob(jobDetail, trigger);
如下是DumbJob類的代碼:
- public class DumbJob implements Job {
- public DumbJob() {
- }
- public void execute(JobExecutionContext context)
- throws JobExecutionException
- {
- System.err.println("DumbJob is executing.");
- }
- }
注意在添加一個任務時,傳遞了一個JobDetail實例作爲參數,構建這個JobDetail實例時需要一個任務類參數。每次調用程序執行任務時,創建一個新的任務類實例並執行其execute方法,但是這種方法有一些限制,首先是所有的任務實現必須提供一個無參數的構造函數,還有就是任務實現不應該包含成員字段,因爲在每次執行後這些值都會被消除。
那麼應該如何給一個任務提供屬性或者配置呢?如何在任務的不同執行過程中保存或跟蹤任務的狀態呢?這是通過JobDetail的JobDataMap來實現。
JobDataMap
JobDataMap可以用來保存任何需要傳遞給任務實例的對象(這些對象要求是可序列化的),JobDataMap是java的Map接口的實現,添加了一些便利方法,下面的代碼片斷描述瞭如何使用JobDataMap保存數據:
- jobDetail.getJobDataMap().put("jobSays", "Hello World!");
- jobDetail.getJobDataMap().put("myFloatValue", 3.141f);
- jobDetail.getJobDataMap().put("myStateData", new ArrayList());
下面的示例描述瞭如何在任務執行過程中從JobDataMap獲取數據:
- public class DumbJob implements Job {
- public DumbJob() {
- }
- public void execute(JobExecutionContext context)
- throws JobExecutionException
- {
- String instName = context.getJobDetail().getName();
- String instGroup = context.getJobDetail().getGroup();
- JobDataMap dataMap = context.getJobDetail().getJobDataMap();
- String jobSays = dataMap.getString("jobSays");
- float myFloatValue = dataMap.getFloat("myFloatValue");
- ArrayList state = (ArrayList)dataMap.get("myStateData");
- state.add(new Date());
- System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
- }
- }
如果使用可持久化的JobStore(隨後會有討論),需要小心決定將JobDataMap保存在什麼地方,因爲JobDataMap對象中保存的對象是可序列化的,因此可能會遇到類版本問題。
有狀態/無狀態
觸發器也可以使用JobDataMap保存數據。當需要使用多個觸發器重用保存在Scheduler中的單個任務實例時,並且針對每個觸發器希望提供任務不同的數據時,這是比較有用的。
JobDataMap在任務執行過程中,可以在JobExecutionContext中找到,這裏的JobDataMap是JobDetail中的JobDataMap和觸發器中的JobDataMap合併的結果,如果遇到命名相同的元素,後者會重寫前者。
如下是從JobExecutionContext中獲取JobDataMap的代碼片斷:
- public class DumbJob implements Job {
- public DumbJob() {
- }
- public void execute(JobExecutionContext context)
- throws JobExecutionException
- {
- String instName = context.getJobDetail().getName();
- String instGroup = context.getJobDetail().getGroup();
- JobDataMap dataMap = context.getJobDataMap(); // Note the difference from the previous example
- String jobSays = dataMap.getString("jobSays");
- float myFloatValue = dataMap.getFloat("myFloatValue");
- ArrayList state = (ArrayList)dataMap.get("myStateData");
- state.add(new Date());
- System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
- }
- }
有狀態任務
任務可以被定義成有狀態或無狀態的,無狀態任務僅僅通過JobDataMap傳遞數據,這意味着每次任務執行後對JobDataMap的改變都會丟失,而有狀態任務恰恰相反,每次任務執行後JobDataMap都被恢復。有狀態任務不能併發執行。
實現*StatefulJob*接口的任務是有狀態的
任務屬性
下面是通過JobDetail對象定義的一些任務屬性:
Durability - 如果這個值爲false,每次任務沒有活動的觸發器關聯時都將從Scheduler中刪除。
Volatility - 如果任務是暫態的,在每次重啓Scheduler時將不會被持久化
RequestsRecovery - 如果任務是"requests recovery",當他在Scheduler關閉的時間正在執行時,當Scheduler再次啓動時將再次被執行。
JobListeners - 任務可以添加多個JobListener實例,當任務執行時,這些監聽器將接收到通知。
JobExecutionException
Job的execute方法只能拋出JobExecutionException,這就意味着通常你需要try-catch方法中的所有代碼。詳細信息可以參考JAVADOC。
觸發器
Calendars
Quartz Calendar 對象 (不是 java.util.Calendar對象) 可以和觸發器關聯,當需要從觸發器中排除一些時間時,Calendar是比較有用的。比如你希望創建一個觸發器,在每個星期三的上午九點激活一個任務,然後通過加一個Calendar實例排除所有的節假日。Calendar接口如下:
- package org.quartz;
- public interface Calendar
- {
- public boolean isTimeIncluded(long timeStamp);
- public long getNextIncludedTime(long timeStamp);
- }
注意這些方法的參數單位是微秒,這意味着Calendar可以精確到微秒,但是通常我們只關心天,Quartz提供一個org.quartz.impl.HolidayCalendar類用來簡化Calendar的使用。
Calendar必須使用Scheduler的addCalendar方法進行註冊。如果使用HolidayCalendar,實例化後應該調用addExcludedDate(Date date)添加那些需要排除的日期,同一個Calendar可以用於多個觸發器。
- HolidayCalendar cal = new HolidayCalendar();
- cal.addExcludedDate( someDate );
- sched.addCalendar("myHolidays", cal, false);
- Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every one hour interval
- trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date())); // start on the next even hour
- trigger.setName("myTrigger1");
- trigger.setCalendarName("myHolidays"); // .. schedule job with trigger
- Trigger trigger2 = TriggerUtils.makeDailyTrigger(8, 0); // fire every day at 08:00
- trigger.setStartTime(new Date()); // begin immediately
- trigger2.setName("myTrigger2");
- trigger2.setCalendarName("myHolidays"); // .. schedule job with trigger2
優先級
待補充。
Misfire指令
待補充。
TriggerUtils
TriggerUtils 類包含一些創建觸發器和日期的便利方法。使用這個類可以很容易的創建基於分鐘,小時,天,星期,月的觸發器。
TriggerListener
觸發器也可以註冊監聽器,監聽器必須實現*TriggerListener* 接口。
SimpleTrigger
如果需要計劃一個任務在指定的時間執行,或者在指定的時間後以指定的間隔連續執行多次,比如希望在2005年1月12號上午11:22:54開始執行一個任務,在這之後每隔20分鐘執行一次,共執行一次,這種情況下可以使用SimpleTrigger。
SimpleTrigger包含幾個屬性:開始時間,結束時間,重複次數和間隔。
重複次數可以是大於等於0,或者是常量值SimpleTrigger.REPEAT_INDEFINITELY,間隔必須大於等於0的長整數,單位是微秒。如果間隔爲0表示併發執行重複次數。
如果不熟悉java.util.Calendar類,可能經常需要根據開始時間計算觸發時間,org.quartz.helpers.TriggerUtils 可以幫助完成這些任務。
結束時間屬性重寫重複次數屬性。如果希望創建一個觸發器,每隔10秒執行一次,直到一個指定的時間,可以簡單的指定結束時間, 重複次數值爲REPEAT_INDEFINITELY。
SimpleTrigger有幾個構造函數,下面是其中一個:
- public SimpleTrigger(String name,
- String group,
- Date startTime,
- Date endTime,
- int repeatCount,
- long repeatInterval)
創建一個10秒鐘後只執行一次的觸發器:
- long startTime = System.currentTimeMillis() + 10000L;
- SimpleTrigger trigger = new SimpleTrigger("myTrigger",
- null,
- new Date(startTime),
- null,
- 0,
- 0L);
創建一個每隔60秒重複執行的觸發器:
- SimpleTrigger trigger = new SimpleTrigger("myTrigger",
- null,
- new Date(),
- null,
- SimpleTrigger.REPEAT_INDEFINITELY,
- 60L * 1000L);
創建一個40秒後開始執行,每隔10秒執行一次的觸發器:
- long endTime = System.currentTimeMillis() + 40000L;
- SimpleTrigger trigger = new SimpleTrigger("myTrigger",
- "myGroup",
- new Date(),
- new Date(endTime),
- SimpleTrigger.REPEAT_INDEFINITELY,
- 10L * 1000L);
創建一個觸發器,在2002年3月17日開始執行,重複5次,每次間隔爲30秒:
- java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);
- cal.set(cal.HOUR, 10);
- cal.set(cal.MINUTE, 30);
- cal.set(cal.SECOND, 0);
- cal.set(cal.MILLISECOND, 0); Data startTime = cal.getTime() SimpleTrigger trigger = new SimpleTrigger("myTrigger",
- null,
- startTime,
- null,
- 5,
- 30L * 1000L);
CronTrigger
如果需要基於日曆指定觸發器,可以使用CronTrigger。使用CronTrigger可以實現類似的觸發器,比如:每個星期五的下午。比如每個星期一,三和五的上午9點到10點之間每隔5分鐘。
CronTrigger也有一個開始時間和結束時間屬性,用來指定什麼時候任務開始和結束。
Cron表達式
*Cron*表達式用來配置CronTrigger。Cron表達式是一個由七個部分組成的字符串,這七個部分用空隔進行分隔:
Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (可選字段)
'*'字符表示指定字段的所有可能值,比如Day-Of-Week字段的*表示每天。
每個字段都有一些有效值。比如秒和分可以取值0-59,小時可以取值0-23。Day-of-Month可以取值0-31,需要注意一個月有多少天。 月可以取值0-11,或者通過使用JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 和 DEC。 Days-of-Week可以取值1-7(1==Sunday)或者SUN, MON, TUE, WED, THU, FRI 和SAT。
'/' 字符可以用來指定增量,比如如果指定Minute字段爲"0/15"表示在第0分鐘啓動,每隔15分鐘的間隔;"3/20"表示每三分鐘啓動,每隔20分鐘的間隔。
'?' 字符可以在day-of-month和day-of-week 字段中使用,用來表示沒有指定值。
'L'字符可以在day-of-month和day-of-week 字段中使用,這個字符表示最後一個的意思。比如在day-of-month字段中表示這個月的最後一天,如果在day-of-week字段表示"7"或者"SAT",但是如果在day-of-week字段L在另一個值後面,意味着這個月的最後XXX天,比如"6L"表示這個月的最後一個星期五。使用這個字符,不能指定列表,範圍值。
'W'字符用來指定離指定天最近的星期XXX,比如如果day-of-month字段值爲"15W",表示離這個月15號最近的一個weekday。
'#'字符用來表示這個月的第幾個XXX,比如day-of-week字段的"6#3"表示這個月的第三個星期五。
下面是一些示例:
創建一個每五分鐘激活一次的觸發器:
- "0 0/5 * * * ?"
創建一個觸發器在當前分鐘的第10秒後,每五分鐘執行一次,比如上午10:00:10 am,上午10:05:10:
- "10 0/5 * * * ?"
創建一個觸發器,在每個星期三和星期五的10:30, 11:30, 12:30, 和13:30執行。
- "0 30 10-13 ? * WED,FRI"
創建一個觸發器,在每個月的第5天和第20天的上午8點到10點執行,每隔半小時執行一次,注意上午10:00不會執行:
- "0 0/30 8-9 5,20 * ?"
監聽器
基於觸發器的監聽器接口如下:
- public interface TriggerListener {
- public String getName();
- public void triggerFired(Trigger trigger, JobExecutionContext context);
- public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
- public void triggerMisfired(Trigger trigger);
- public void triggerComplete(Trigger trigger, JobExecutionContext context,
- int triggerInstructionCode);
- }
基於任務的監聽器接口如下:
- public interface JobListener {
- public String getName();
- public void jobToBeExecuted(JobExecutionContext context);
- public void jobExecutionVetoed(JobExecutionContext context);
- public void jobWasExecuted(JobExecutionContext context,
- JobExecutionException jobException);
- }
註冊監聽器
要創建一個監聽器,只需要實現相應的接口就可以了。監聽器需要在Scheduler中註冊,監聽器可以被註冊爲全局的或者本地的,註冊監聽器時必須指定一個名字,或者監聽器本身的getName方法返回一個值。
- scheduler.addGlobalJobListener(myJobListener);
- or
- scheduler.addJobListener(myJobListener);
CronTrigger配置格式:
格式: [秒] [分] [小時] [日] [月] [周] [年]
序號 | 說明 |
是否必填 | 允許填寫的值 | 允許的通配符 |
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 |
, - * / |
3 | 小時 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符說明:
* 表示所有值. 例如:在分的字段上設置 "*",表示每一分鐘都會觸發。
? 表示不指定值。使用的場景爲不需要關心當前設置這個字段的值。例如:要在每月的10號觸發一個操作,但不關心是周幾,所以需要周位置的那個字段設置爲"?" 具體設置爲 0 0 0 10 * ?
- 表示區間。例如 在小時上設置 "10-12",表示 10,11,12點都會觸發。
, 表示指定多個值,例如在周字段上設置 "MON,WED,FRI" 表示週一,週三和週五觸發
/ 用於遞增觸發。如在秒上面設置"5/15" 表示從5秒開始,每增15秒觸發(5,20,35,50)。 在月字段上設置'1/3'所示每月1號開始,每隔三天觸發一次。
L 表示最後的意思。在日字段設置上,表示當月的最後一天(依據當前月份,如果是二月還會依據是否是潤年[leap]), 在周字段上表示星期六,相當於"7"或"SAT"。如果在"L"前加上數字,則表示該數據的最後一個。例如在周字段上設置"6L"這樣的格式,則表示“本月最後一個星期五"
W 表示離指定日期的最近那個工作日(週一至週五). 例如在日字段上設置"15W",表示離每月15號最近的那個工作日觸發。如果15號正好是週六,則找最近的週五(14號)觸發, 如果15號是周未,則找最近的下週一(16號)觸發.如果15號正好在工作日(週一至週五),則就在該天觸發。如果指定格式爲 "1W",它則表示每月1號往後最近的工作日觸發。如果1號正是週六,則將在3號下週一觸發。(注,"W"前只能設置具體的數字,不允許區間"-").
小提示 |
'L'和 'W'可以一組合使用。如果在日字段上設置"LW",則表示在本月的最後一個工作日觸發(一般指發工資 ) |
小提示 |
周字段的設置,若使用英文字母是不區分大小寫的 MON 與mon相同. |
常用示例:
0 0 12 * * ? | 每天12點觸發 |
0 15 10 ? * * | 每天10點15分觸發 |
0 15 10 * * ? | 每天10點15分觸發 |
0 15 10 * * ? * | 每天10點15分觸發 |
0 15 10 * * ? 2005 | 2005年每天10點15分觸發 |
0 * 14 * * ? | 每天下午的 2點到2點59分每分觸發 |
0 0/5 14 * * ? | 每天下午的 2點到2點59分(整點開始,每隔5分觸發) |
0 0/5 14,18 * * ? | 每天下午的 2點到2點59分(整點開始,每隔5分觸發) 每天下午的 18點到18點59分(整點開始,每隔5分觸發) |
0 0-5 14 * * ? | 每天下午的 2點到2點05分每分觸發 |
0 10,44 14 ? 3 WED | 3月分每週三下午的 2點10分和2點44分觸發 |
0 15 10 ? * MON-FRI | 從週一到週五每天上午的10點15分觸發 |
0 15 10 15 * ? | 每月15號上午10點15分觸發 |
0 15 10 L * ? | 每月最後一天的10點15分觸發 |
0 15 10 ? * 6L | 每月最後一週的星期五的10點15分觸發 |
0 15 10 ? * 6L 2002-2005 | 從2002年到2005年每月最後一週的星期五的10點15分觸發 |
0 15 10 ? * 6#3 | 每月的第三週的星期五開始觸發 |
0 0 12 1/5 * ? | 每月的第一個中午開始每隔5天觸發一次 |
0 11 11 11 11 ? | 每年的11月11號 11點11分觸發(光棍節) |