Quartz 使用手記

 

概述

這篇文章是英文教程的中文翻譯,有些認爲暫時使用不到的特性有省略,英文文檔參見http://www.opensymphony.com/quartz/wikidocs/TutorialLesson1.html

如何使用

使用QUARTZ調試程序之前,必須使用SchedlerFactory實例化Scheduler。一旦實例化Scheduler後可以啓動或者停止,需要注意的是一旦Scheduler關閉,必須重新實例化後才能夠重啓。任務只有在Scheduler啓動後纔會執行。

下面的代碼片斷實例化並啓動Scheduler,然後執行一個任務。

java 代碼
  1. SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();   
  2. Scheduler sched = schedFact.getScheduler();   
  3. sched.start();   
  4. JobDetail jobDetail = new JobDetail("myJob"null, DumbJob.class);   
  5. Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every hour   
  6. trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));   
  7. trigger.setName("myTrigger");   
  8. sched.scheduleJob(jobDetail, trigger);  

任務/觸發器

要定義一個任務,只需要實現Job接口即可,Job接口如下:

java 代碼
  1. package org.quartz;public interface Job {  public void execute(JobExecutionContext context)      
  2.       throws JobExecutionException;      
  3. }    

當任務被觸發時將調用execute方法,JobExecutionContext 參數提供關於任務的運行時環境,包括一個Scheduler的引用,觸發這個任務的觸發器的引用,任務的JobDetail實例和一些別的信息。

JobDetail 對象在添加任務到Scheduler時創建,這個對象和*JobDataMap* 都用來保存Job實例的狀態信息。

Trigger 用來觸發任務。要計劃一個任務,需要實例化一個觸發器並設置相關的屬性,觸發器也有一個關聯的JobDataMap用來傳遞參數給指定的任務。Quartz提供幾個不同的觸發器實例,比較常用的是SimpleTrigger和CronTrigger。

如果需要在一個指定的時間,或者指定的時間後以一個指定的間隔對一個任務重複執行多次,使用SimpleTrigger。如果需要基於日曆安排任務,使用CronTrigger,比如每個星期五中午。

很多任務調用器沒有分離任務和觸發器,Quartz這樣做有很多好處。比如一個任務可以和多個觸發器關聯,可以更改或替換一個觸發器,而不必重新定義任務。

任務和觸發器都有唯一標識名稱,也可以進行分組,在一個組中任務和觸發器的名稱必須是唯一的,這意味着任務和觸發器是使用名稱+組名唯一標識的。如果不指定組名,相當於使用缺省的組名: Scheduler.DEFAULT_GROUP 。

任務

以前的Quartz要求具體的Job實現類通過get/set方法傳遞數據,現在使用JobDetail類來傳遞數據。

我們先來看一個實例: 

java 代碼
  1. JobDetail jobDetail = new JobDetail("myJob"// job name                               
  2.                                     sched.DEFAULT_GROUP, // job group (you can also specify 'null' to use the default group)   
  3.                                     DumbJob.class);               // the java class to executeTrigger trigger = TriggerUtils.makeDailyTrigger(8, 30);   
  4. trigger.setStartTime(new Date());   
  5. trigger.setName("myTrigger");sched.scheduleJob(jobDetail, trigger);  

如下是DumbJob類的代碼:

java 代碼
  1. public class DumbJob implements Job {   
  2.        public DumbJob() {   
  3.        }   
  4.        public void execute(JobExecutionContext context)   
  5.            throws JobExecutionException   
  6.        {   
  7.            System.err.println("DumbJob is executing.");   
  8.        }   
  9.    }  

注意在添加一個任務時,傳遞了一個JobDetail實例作爲參數,構建這個JobDetail實例時需要一個任務類參數。每次調用程序執行任務時,創建一個新的任務類實例並執行其execute方法,但是這種方法有一些限制,首先是所有的任務實現必須提供一個無參數的構造函數,還有就是任務實現不應該包含成員字段,因爲在每次執行後這些值都會被消除。

那麼應該如何給一個任務提供屬性或者配置呢?如何在任務的不同執行過程中保存或跟蹤任務的狀態呢?這是通過JobDetail的JobDataMap來實現。

JobDataMap

JobDataMap可以用來保存任何需要傳遞給任務實例的對象(這些對象要求是可序列化的),JobDataMap是java的Map接口的實現,添加了一些便利方法,下面的代碼片斷描述瞭如何使用JobDataMap保存數據:

java 代碼
  1. jobDetail.getJobDataMap().put("jobSays""Hello World!");   
  2. jobDetail.getJobDataMap().put("myFloatValue"3.141f);   
  3. jobDetail.getJobDataMap().put("myStateData"new ArrayList());  

下面的示例描述瞭如何在任務執行過程中從JobDataMap獲取數據:

  1. public class DumbJob implements Job {          
  2.        public DumbJob() {   
  3.        }          
  4.   
  5.        public void execute(JobExecutionContext context)   
  6.            throws JobExecutionException   
  7.        {   
  8.            String instName = context.getJobDetail().getName();   
  9.            String instGroup = context.getJobDetail().getGroup();              
  10.           JobDataMap dataMap = context.getJobDetail().getJobDataMap();              
  11.            String jobSays = dataMap.getString("jobSays");   
  12.            float myFloatValue = dataMap.getFloat("myFloatValue");   
  13.            ArrayList state = (ArrayList)dataMap.get("myStateData");   
  14.            state.add(new Date());              
  15.            System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);   
  16.        }   
  17.    }   
java 代碼

如果使用可持久化的JobStore(隨後會有討論),需要小心決定將JobDataMap保存在什麼地方,因爲JobDataMap對象中保存的對象是可序列化的,因此可能會遇到類版本問題。

有狀態/無狀態

觸發器也可以使用JobDataMap保存數據。當需要使用多個觸發器重用保存在Scheduler中的單個任務實例時,並且針對每個觸發器希望提供任務不同的數據時,這是比較有用的。

JobDataMap在任務執行過程中,可以在JobExecutionContext中找到,這裏的JobDataMap是JobDetail中的JobDataMap和觸發器中的JobDataMap合併的結果,如果遇到命名相同的元素,後者會重寫前者。

如下是從JobExecutionContext中獲取JobDataMap的代碼片斷:

java 代碼
  1. public class DumbJob implements Job {             
  2.     public DumbJob() {   
  3.     }            
  4.     public void execute(JobExecutionContext context)   
  5.                 throws JobExecutionException   
  6.      {   
  7.                 String instName = context.getJobDetail().getName();   
  8.                 String instGroup = context.getJobDetail().getGroup();                   
  9.                 JobDataMap dataMap = context.getJobDataMap();    // Note the difference from the previous example                   
  10.                  String jobSays = dataMap.getString("jobSays");   
  11.                 float myFloatValue = dataMap.getFloat("myFloatValue");   
  12.                 ArrayList state = (ArrayList)dataMap.get("myStateData");   
  13.                 state.add(new Date());                   
  14.                  System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);   
  15.           }   
  16.     }  

 

有狀態任務

任務可以被定義成有狀態或無狀態的,無狀態任務僅僅通過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接口如下:

java 代碼
  1. package org.quartz;        
  2. public interface Calendar    
  3. {                
  4.     public boolean isTimeIncluded(long timeStamp);    
  5.     public long getNextIncludedTime(long timeStamp);        
  6. }  

注意這些方法的參數單位是微秒,這意味着Calendar可以精確到微秒,但是通常我們只關心天,Quartz提供一個org.quartz.impl.HolidayCalendar類用來簡化Calendar的使用

Calendar必須使用Scheduler的addCalendar方法進行註冊。如果使用HolidayCalendar,實例化後應該調用addExcludedDate(Date date)添加那些需要排除的日期,同一個Calendar可以用於多個觸發器。

java 代碼
  1. HolidayCalendar cal = new HolidayCalendar();   
  2. cal.addExcludedDate( someDate );       
  3. sched.addCalendar("myHolidays", cal, false);       
  4.   
  5. Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every one hour interval   
  6. trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));    // start on the next even hour   
  7. trigger.setName("myTrigger1");      
  8. trigger.setCalendarName("myHolidays");    // .. schedule job with trigger       
  9.   
  10. Trigger trigger2 = TriggerUtils.makeDailyTrigger(80); // fire every day at 08:00   
  11. trigger.setStartTime(new Date()); // begin immediately   
  12. trigger2.setName("myTrigger2");      
  13. 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有幾個構造函數,下面是其中一個:

java 代碼
  1. public SimpleTrigger(String name,   
  2.                        String group,   
  3.                        Date startTime,   
  4.                        Date endTime,   
  5.                        int repeatCount,   
  6.                        long repeatInterval)   

創建一個10秒鐘後只執行一次的觸發器:

java 代碼
  1. long startTime = System.currentTimeMillis() + 10000L;   
  2.   
  3. SimpleTrigger trigger = new SimpleTrigger("myTrigger",   
  4.                                             null,   
  5.                                             new Date(startTime),   
  6.                                             null,   
  7.                                             0,   
  8.                                             0L);   

創建一個每隔60秒重複執行的觸發器:

  1. SimpleTrigger trigger = new SimpleTrigger("myTrigger",   
  2.                                             null,   
  3.                                             new Date(),   
  4.                                             null,   
  5.                                             SimpleTrigger.REPEAT_INDEFINITELY,   
  6.                                             60L * 1000L);  
java 代碼

創建一個40秒後開始執行,每隔10秒執行一次的觸發器:

java 代碼
  1. long endTime = System.currentTimeMillis() + 40000L;   
  2.   
  3. SimpleTrigger trigger = new SimpleTrigger("myTrigger",   
  4.                                             "myGroup",   
  5.                                             new Date(),   
  6.                                             new Date(endTime),   
  7.                                             SimpleTrigger.REPEAT_INDEFINITELY,   
  8.                                             10L * 1000L);   

創建一個觸發器,在2002年3月17日開始執行,重複5次,每次間隔爲30秒:

java 代碼

 

  1. java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);   
  2.   cal.set(cal.HOUR, 10);   
  3.   cal.set(cal.MINUTE, 30);   
  4.   cal.set(cal.SECOND, 0);   
  5.   cal.set(cal.MILLISECOND, 0);  Data startTime = cal.getTime()  SimpleTrigger trigger = new SimpleTrigger("myTrigger",   
  6.                                             null,   
  7.                                             startTime,   
  8.                                             null,   
  9.                                             5,   
  10.                                             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"表示這個月的第三個星期五。

下面是一些示例:
創建一個每五分鐘激活一次的觸發器:

java 代碼
  1. "0 0/5 * * * ?"   

創建一個觸發器在當前分鐘的第10秒後,每五分鐘執行一次,比如上午10:00:10 am,上午10:05:10:

java 代碼
  1. "10 0/5 * * * ?"   

創建一個觸發器,在每個星期三和星期五的10:30, 11:30, 12:30, 和13:30執行。

java 代碼
  1. "0 30 10-13 ? * WED,FRI"   

創建一個觸發器,在每個月的第5天和第20天的上午8點到10點執行,每隔半小時執行一次,注意上午10:00不會執行:

java 代碼
  1. "0 0/30 8-9 5,20 * ?"  

監聽器


基於觸發器的監聽器接口如下:

  1. public interface TriggerListener {   
  2.   
  3.     public String getName();   
  4.   
  5.     public void triggerFired(Trigger trigger, JobExecutionContext context);   
  6.   
  7.     public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);   
  8.   
  9.     public void triggerMisfired(Trigger trigger);   
  10.   
  11.     public void triggerComplete(Trigger trigger, JobExecutionContext context,   
  12.             int triggerInstructionCode);   
  13. }   
java 代碼

基於任務的監聽器接口如下:

java 代碼
  1. public interface JobListener {   
  2.   
  3.     public String getName();   
  4.   
  5.     public void jobToBeExecuted(JobExecutionContext context);   
  6.   
  7.     public void jobExecutionVetoed(JobExecutionContext context);   
  8.   
  9.     public void jobWasExecuted(JobExecutionContext context,   
  10.             JobExecutionException jobException);   
  11.   
  12. }   
註冊監聽器

要創建一個監聽器,只需要實現相應的接口就可以了。監聽器需要在Scheduler中註冊,監聽器可以被註冊爲全局的或者本地的,註冊監聽器時必須指定一個名字,或者監聽器本身的getName方法返回一個值。

java 代碼
  1. scheduler.addGlobalJobListener(myJobListener);   
  2. or   
  3. 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",則表示在本月的最後一個工作日觸發(一般指發工資

# 序號(表示每月的第幾個周幾),例如在周字段上設置"6#3"表示在每月的第三個週六.注意如果指定"#5",正好第五週沒有周六,則不會觸發該配置(用在母親節和父親節再合適不過了)
小提示

周字段的設置,若使用英文字母是不區分大小寫的 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分觸發(光棍節)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章