這篇文章是英文教程的中文翻譯,有些認爲暫時使用不到的特性有省略,英文文檔參見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
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 (可選字段)
每個字段都有一些有效值。比如秒和分可以取值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"表示這個月的第三個星期五。
'*'字符表示是通配字符,表示該字段可以接受任何可能的值,比如Day-Of-Week字段的*表示每天。
下面是一些示例:
創建一個每五分鐘激活一次的觸發器:
"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);