程式扎記: [ Java 套件 ] Quartz Scheduler 2.2 - 入門學習

標籤

2014年7月7日 星期一

[ Java 套件 ] Quartz Scheduler 2.2 - 入門學習

Preface: 
平常都是使用 JDK 自帶的 TimerTask 與 Timer 來完成需要 Scheduling 的工作, 抑或是利用 Shell script 並透過 crontab/at 命令來完成. 剛剛 Google 了一下, 原來還有一個 Quartz 套件提供更強大與相容於 Java 的 Scheduling 利器: 
Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for JTA transactions and clustering.

Quartz is freely usable, licensed under the Apache 2.0 license.

底下以一個門外漢的角度來看看怎麼使用這個套件. 

Hello Quartz: 
要學一樣語言或是套件, 免不了 Say Hello. 底下的範例代碼會以 40 秒為周期執行 HelloJob 上的 execute(...) 方法; 並於 5 分鐘後結束 Scheduling: 
  1. package demo;  
  2.   
  3. import static org.quartz.JobBuilder.newJob;  
  4. import static org.quartz.SimpleScheduleBuilder.simpleSchedule;  
  5. import static org.quartz.TriggerBuilder.newTrigger;  
  6.   
  7. import org.apache.log4j.PropertyConfigurator;  
  8. import org.quartz.JobDetail;  
  9. import org.quartz.Scheduler;  
  10. import org.quartz.SchedulerException;  
  11. import org.quartz.Trigger;  
  12. import org.quartz.impl.StdSchedulerFactory;  
  13.   
  14. public class QuartzTest {  
  15.     public static void main(String args[])  
  16.     {         
  17.         try {  
  18.             // Configuration of log4j  
  19.             PropertyConfigurator.configure("log4j.properties");  
  20.               
  21.             // Grab the Scheduler instance from the Factory   
  22.             Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();  
  23.   
  24.             // and start it off  
  25.             scheduler.start();  
  26.   
  27.             // define the job and tie it to our HelloJob class -> JobBuilder.newJob()  
  28.             JobDetail job = newJob(HelloJob.class)  
  29.                 .withIdentity("job1""group1")  
  30.                 .build();  
  31.   
  32.             // Trigger the job to run now, and then repeat every 40 seconds -> TriggerBuilder.newTrigger()  
  33.             Trigger trigger = newTrigger()  
  34.                 .withIdentity("trigger1""group1")  
  35.                 .startNow()  
  36.                 .withSchedule(simpleSchedule()  
  37.                               .withIntervalInSeconds(40)  
  38.                               .repeatForever())              
  39.                               .build();  
  40.   
  41.             // Tell quartz to schedule the job using our trigger  
  42.             scheduler.scheduleJob(job, trigger);  
  43.             Thread.sleep(1000*60*5); // Sleep 5 minutes  
  44.             scheduler.shutdown();  
  45.   
  46.         }   
  47.         catch (SchedulerException se)   
  48.         {  
  49.             se.printStackTrace();  
  50.         }   
  51.         catch (Exception e)   
  52.         {  
  53.               
  54.         }  
  55.           
  56.     }  
  57. }  
如果你熟悉 crontab 的語法, 且你希望在每天的 10:42am 執行的話, 只需要替換 trigger: (透過 CronSchedulerBuilder 上的靜態方法 cronSchedule(String cronExpression)
  1. trigger = newTrigger()  
  2.     .withIdentity("trigger3""group1")  
  3.     .withSchedule(cronSchedule("0 42 10 * * ?"))  
  4.     .forJob(myJobKey)  
  5.     .build();  
不懂 crontab? 沒關係下面的代碼有異曲同工之妙: (透過 CronSchedulerBuilder 上的靜態方法 dailyAtHourAndMinute(int hour, int minute)
  1. trigger = newTrigger()  
  2.     .withIdentity("trigger3""group1")  
  3.     .withSchedule(dailyAtHourAndMinute(1042))  
  4.     .forJob(myJobKey)  
  5.     .build();  
底下針對 Quartz 這個套件的使用流程簡單進行介紹. 

The Quartz API: 
Quartz API 常見的使用介面包括: 
Scheduler - the main API for interacting with the scheduler.
Job - an interface to be implemented by components that you wish to have executed by the scheduler.
JobDetail - used to define instances of Jobs.
Trigger - a component that defines the schedule upon which a given Job will be executed.
JobBuilder - used to define/build JobDetail instances, which define instances of Jobs.
TriggerBuilder - used to define/build Trigger instances.

Scheduler 的生命週期從使用 SchedulerFactory 建立 Scheduler 物件開始; 並在呼叫該物件的 shutdown() 方法結束. 建立 Scheduler 物件後, 你可以透過上面的方法添加或移除 Jobs 與Triggers 並執行 scheduling-related 操作 (如 pausing a trigger). 但這一切的動作都只會在執行 Scheduler 物件上的 start() 方法後才會生效! 

Quartz 提供許多的 "builder" 類別, 並透過這些類別定義一些 Domain Specific Language (or DSL, also sometimes referred to as a "fluent interface"), 而這些 "builder" 類別在前面範例出現的代碼片段如下: 
  1. // define the job and tie it to our HelloJob class -> JobBuilder.newJob()  
  2. JobDetail job = newJob(HelloJob.class)  
  3.     .withIdentity("job1""group1")  
  4.     .build();  
  5.   
  6. // Trigger the job to run now, and then repeat every 40 seconds -> TriggerBuilder.newTrigger()  
  7. Trigger trigger = newTrigger()  
  8.     .withIdentity("trigger1""group1")  
  9.     .startNow()  
  10.     .withSchedule(simpleSchedule()  
  11.                   .withIntervalInSeconds(40)  
  12.                   .repeatForever())              
  13.                   .build();  
  14.   
  15. // Tell quartz to schedule the job using our trigger  
  16. scheduler.scheduleJob(job, trigger);  
JobDetail 物件是透過 JobBuilder 上的靜態方法 newJob() 產生出來; 同樣的 Trigger 是透過 TriggerBuilder 類別上的靜態方法 newTrigger() 創建出來. 而常見的 "builders" 可以透過下面的靜態 import 進代碼: 
  1. import static org.quartz.JobBuilder.*;  
  2. import static org.quartz.SimpleScheduleBuilder.*;  
  3. import static org.quartz.CronScheduleBuilder.*;  
  4. import static org.quartz.CalendarIntervalScheduleBuilder.*;  
  5. import static org.quartz.TriggerBuilder.*;  
  6. import static org.quartz.DateBuilder.*;  
上面的 SchedulerBuilder 提供許多靜態方法讓你產生各式各樣的 scheduler 物件; 而 DateBuilder 則是用來產生 scheduling 會需要的 java.util.Date 或是 (用來指示 Trigger 的時間, such as a date that represents the next even hour - or in other words 10:00:00 if it is currently 9:43:27). 常用的方法有: 
dateOf(int hour, int minute, int second): Get a Date object that represents the given time, on today's date (equivalent to todayAt(int, int, int)).
evenHourDate(Date date): Returns a date that is rounded to the next even hour above the given date.
evenHourDateAfterNow(): Returns a date that is rounded to the next even hour after the current time.
futureDate(int interval, DateBuilder.IntervalUnit unit): Return a date shift with given time unit in the future.
tomorrowAt(int hour, int minute, int second): Get a Date object that represents the given time, on tomorrow's date.
translateTime(Date date, TimeZone src, TimeZone dest): Translate a date & time from a users time zone to the another (probably server) time zone to assist in creating a simple trigger with the right date & time. 

底下是一些使用的範例代碼: 
  1. Date now = new Date();  
  2. System.out.printf("\t[Info] Now is: %s!\n", now);  
  3. System.out.printf("\t[Info] dateOf(10,10,10)->%s\n", dateOf(10,10,10));  
  4. System.out.printf("\t[Info] evenHourDate(now)->%s\n",  evenHourDate(now));  
  5. System.out.printf("\t[Info] futureDate(2, DateBuilder.IntervalUnit.DAY)->%s\n", futureDate(2, DateBuilder.IntervalUnit.DAY));  
  6. System.out.printf("\t[Info] tomorrowAt(2,2,2)->%s\n", tomorrowAt(2,2,2));  
執行結果: 
[Info] Now is: Mon Jul 07 15:07:49 CST 2014!
[Info] dateOf(10,10,10)->Mon Jul 07 10:10:10 CST 2014
[Info] evenHourDate(now)->Mon Jul 07 16:00:00 CST 2014
[Info] futureDate(2, DateBuilder.IntervalUnit.DAY)->Wed Jul 09 15:07:49 CST 2014
[Info] Tue Jul 08 02:02:02 CST 2014

Jobs and Triggers: 
在 Quartz 中的 Job 必須實作 Job 介面, 上面定義了一個需要實作的方法: 
  1. package org.quartz;  
  2.   
  3.   public interface Job {  
  4.   
  5.     public void execute(JobExecutionContext context)  
  6.       throws JobExecutionException;  
  7.   }  
當設定的 trigger 觸發後, Job 介面上的方法 execute(...) 將會被 scheduler 的工作線程執行, 而 JobExecutionContext 物件為傳入的參數. 透過它你可以與 Scheduler 或是觸發 Job 的 Trigger等進行互動. 而用來觸發 Job 的 Trigger 物件上面提供 JobDataMap 讓你對相同的觸發 Job , 針對不同的觸發點提供不同的輸入資料: 
JobDataMap instances can also be stored with a Trigger. This can be useful in the case where you have a Job that is stored in the scheduler for regular/repeated use by multiple Triggers, yet with each independent triggering, you want to supply the Job with different data inputs.

常見 Trigger 介面的種類有 SimpleTrigger 與 CronTriggerSimpleTrigger 在你只需要執行一次的場合相當方便 (在特定時間點執行); 如果你需要重複執行 N 次並且能夠 delay 指定時間後才開始 scheduling 的話 (such as "every Friday, at noon" or "at 10:15 on the 10th day of every month."), 那 CronTrigger 會是不錯的選擇. 

Identities: 
Jobs 與 Triggers 都需要一個唯一的 identifying keys 來向 Quartz scheduler 註冊. 而這些 identifying keys 可以被 group 起來, 方便你對這些 Jobs 與 Triggers 進行管理 (such as "reporting jobs" and "maintenance jobs"). 而在 Scheduler 介面定義的方法中, 常見來管理這些註冊的 Jobs 與 Triggers 有: 
getCurrentlyExecutingJobs(): Return a list of JobExecutionContext objects that represent all currently executing Jobs in this Scheduler instance.
getJobGroupNames(): Get the names of all known JobDetail groups.
getJobKeys(GroupMatcher matcher) : Get the keys of all the JobDetails in the matching groups.
getPausedTriggerGroups(): Get the names of all Trigger groups that are paused.
getTrigger(TriggerKey triggerKey): Get the Trigger instance with the given key.
getTriggerGroupNames(): Get the names of all known Trigger groups.
getTriggersOfJob(JobKey jobKey): Get all Triggers that are associated with the identified JobDetail.
getTriggerState(TriggerKey triggerKey): Get the current state of the identified Trigger.
interrupt(JobKey jobKey): Request the interruption, within this Scheduler instance, of all currently executing instances of the identified Job, which must be an implementor of the InterruptableJob interface.
isShutdown(): Reports whether the Scheduler has been shutdown.
isStarted(): Whether the scheduler has been started.
pauseAll(): Pause all triggers - similar to calling pauseTriggerGroup(group) on every group, however, after using this method, resumeAll() must be called to clear the scheduler's state of 'remembering' that all new triggers will be paused as they are added.
pauseJob(JobKey jobKey): Pause the JobDetail with the given key - by pausing all of its current Trigger.
pauseTrigger(TriggerKey triggerKey): Pause the Trigger with the given key.
rescheduleJob(TriggerKey triggerKey, Trigger newTrigger): Remove (delete) the Trigger with the given key, and store the new given one - which must be associated with the same job (the new trigger must have the job name & group specified) - however, the new trigger need not have the same name as the old trigger. 

Supplement: 
Quartz Documentation][u]Quartz Document 
Tutorial Lesson 1: Using Quartz 
Tutorial Lesson 2: The Quartz API, Jobs And Triggers 
Tutorial Lesson 3: More About Jobs & JobDetails 
Tutorial Lesson 4: More About Triggers. 
Tutorial Lesson 5: SimpleTriggers 
Tutorial Lesson 6: CronTriggers 
Tutorial Lesson 7: TriggerListeners & JobListeners 
Tutorial Lesson 8: SchedulerListeners 
Tutorial Lesson 9: JobStores 
Tutorial Lesson 10: Configuration, Resource Usage and SchedulerFactory 
Tutorial Lesson 11: Advanced (Enterprise) Features 
Tutorial Lesson 12: Miscellaneous Features

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!