Quartz 定時器

這篇文章是英文教程的中文翻譯,有些認爲暫時使用不到的特性有省略,英文文檔參見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);    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章