Quartz Scheduler 開發指南(1)
原文地址:http://www.quartz-scheduler.org/generated/2.2.2/html/qtz-all/
實例化調度程序(Instantiating the Scheduler)
在使用Scheduler調度程序前,它需要被實例化。你可以使用SchedulerFactory實現
一些Quartz用戶通過使用JNDI中的Factory實例,還有一些直接使用Factory實例進行實例化(如下方的例子)
一旦Scheduler調度程序被實例化后, 它可以啟動,保持准備狀態,關閉。注意:一旦關閉了Scheduler,不再次實例化是不能重啟的。Trigger不能被觸發,直到Scheduler啟動。Trigger同樣不會觸發,當Scheduler處於暫停狀態
下方例子:實例化,啟動一個Scheduler,並調度一個任務(Job)執行
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
HelloJob.java
public class HelloJob implements Job {
public static final Logger _log = LoggerFactory.getLogger(HelloJob.class);
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
_log.info("Hello World! - " + new Date() + "-" + jobExecutionContext.getJobDetail().getJobDataMap().get("key1"));
}
}
關鍵接口(Key Interfaces)
Quartz API中的關鍵接口
- Scheduler - 與調度程序交互的主要接口
- Job - 調度程序調度執行的任務需要實現這個接口
- JobDetail - 定義job實例
- Trigger - 調度程序執行哪個任務的觸發條件
- JobBuilder - 用來定義/創建JobBuilder實例(指定任務實例)
- TriggerBuilder - 用來定義/創建 Trigger實例
一個調度程序(Scheduler)的生命周期受到他的創建(通過SchedulerFactory創建)和關閉方法(shutdown() Method)控制
一個創建好的調度程序可以用來添加(add)、移除(remove)、列舉(list) 任務(jobs)和觸發器(triggers),還可以執行其他調度相關的操作(例如暫停觸發器)。但是調度程序(Scheduler)實際上不會進行任何觸發(執行指定任務),直到它被開啟(通過start()方法),就如上面的那個例子
實現上方的例子需要導入如下:
import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;
Jobs and Triggers
任務是一個類,需要實現Job接口。如下方展示的,這個接口只有一個方法
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
當一個任務的觸發器觸發時,調度程序的一個工作線程將調用Excute()方法。傳遞給該方法的參數JobExecutionContext 對象提供了任務實例以及該實例運行環境的信息,包括一個執行調度程序的調度處理、一個觸發執行的觸發器處理、任務的JobDetail對象,以及一些其他信息
在一個任務添加到調度程序中時,JobDetail對象被Quartz創建。這個對象包含很多對Job的屬性設定和一個JobDataMap,JobDataMap可以儲存給定類實例的狀態信息。JobDetail 對象實際上是對Job實例的定義
Trigger對象用來觸發任務的執行。當你希望調度一個Job時,你實例化一個觸發器並調整它的特性來提供你想要的調度方式
Triggers也許也有一個JobDataMap和它綁定。JobDataMap傳遞一些特別的觸發觸發器的任務參數時特別有用。Quartz擁有很多不同類型的觸發器,但最常用的類型是SimpleTrigger 和 CronTrigger
- SimpleTrigger十分方便---------你需要單次執行(在一個給定是時間單一的執行),或者你需要在一個給定的時間觸發他,每隔一段時間觸發一次,或者說觸發N次
- CronTrigger十分方便 ---------- 基於日歷觸發,例如每星期五中午或每個月的第十天的10:15。
為什么同時擁有Job和Trigger?有的任務調度程序沒有區分Job和Trigger的概念。一些定義Job是一個簡單的執行時間帶有一些小的標識,另一些則很像Quartz中Job和Trigger對象的結合。Quartz的設計創建了一個分離將時間進度表和根據該進度表執行的任務分離。這個設計有很多好處。
例如,你可以創建很多任務並把它們放在任務時間表中,獨立於觸發器。這允許你將相同的任務綁定在不同的觸發器中。這種松耦合的另一個好處是在綁定的觸發器過期后依舊在調度時間表中的任務可以再次被配置。這將允許你晚點調度它們,而不用再次定義它們。這也允許你修改或者代替觸發器(Trigger)而不用重新定義綁定的任務(Job)
Jobs and JobDetails
Job接口實現很簡單,只有一個excute()方法。但依舊有一些關於Job的特點,excute()方法,JobDetails需要你了解
當你實現的一個Job類在實際工作中需要執行一些特定類型的任務, Quarzt需要被告知Job實例擁有的很多屬性,這個可以通過JobDetail實現。
JobDetail實例通過JobBuilder類創建,通常靜態導入他的所有方法
import static org.quartz.JobBuilder.*;
下方的例子定義了一個Job類並執行。
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
HelloJob.java
public class HelloJob implements Job {
public static final Logger _log = LoggerFactory.getLogger(HelloJob.class);
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
_log.info("Hello World! - " + new Date() + "-" + jobExecutionContext.getJobDetail().getJobDataMap().get("key1"));
}
}
注意:我們給了Scheduler一個JobDetail實例,它知道將被執行的Job的類型,只要簡單的提供Job的類當我們創建JobDetail時。每次Scheduler執行這個Job時,在調用Excute()前它將創建這個類的實例。當執行完成時,對Job類實例的引用將被丟棄,並且該實例會被垃圾收集(即丟棄)。
這一行為的后果是Jobs必須有一個無參的構造函數(使用默認的JobFactory實現)。另一個后果是Job類中沒有定義數據,這些值是無用的,在Job執行時這些值不會被保存的
給job實例提供配置信息或者在Job執行時跟蹤Job狀態,可以使用JobDataMap,即JobDetail對象的一部分。
JobDataMap
JobDataMap可以用來保存任何數量的(序列化)數據對象,這些數據可以在任務實例執行時獲得。JobDataMap是Java Map接口的實現,同時還添加了一些方便的方法,用於存儲和檢索原始數據類型。
下面是在定義創建JobDetail時將數據存入JobDataMap,這個優先於將Job添加到Scheduler中
// define the job and tie it to our DumbJob class
JobDetail job = newJob(DumbJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();
下面是在Job執行時,從JobDataMap中獲取數據
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.err.println("Instance " + key + " of DumbJob says: "
+ jobSays + ", and val is: " + myFloatValue);
}
}
如果你使用一個持久的JobStore(在本教程的JobStore節討論),你應該小心使用JobDataMap,考慮哪些數據要放在JobDataMap中。因為這里面的對象將會被序列化,因此容易產生類版本問題(原文:they therefore become prone to class-versioning problems.)。顯然標准的Java類型應該是十分安全的, 但除此之外,任何時間一個人改變了你序列化實例的類的定義,你應該小心確保它的兼容性。或者,你可以把JDBC—JobStore和JobDataMap應用在一個模式中,在這個模式中只有基本類型和String可以被放在Map中,以此排除任何以后的序列化問題的可能性。
如果你在Job類中添加的Setter方法和JobDataMap中key的名字一樣,那么Quartz的JobFactory實例將自動獲取這些setter方法,在Job實例化時。
Trigger也可以有JobDataMap。這在某些場合十分有用。當你有一個Job在Scheduler中用多個觸發器重復執行,根據不同的觸發器,你希望給予Job不同的數據輸入。
Job執行時通過JobExecutionContext 獲取JobDataMap很方便。在Job中和Trigger中都定義了JobDataMap,那么這兩個JobDataMap將會合並,對於相同的Key,值取后加載的。
eg1: Job執行時,從JobExecutionContext’s 中獲取合並的JobDataMap
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getMergedJobDataMap();
// 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 " + key + " of DumbJob says: " + jobSays
+ ", and val is: " + myFloatValue);
}
}
eg2: 通過JobFactory注入datamap 數據 可以是這樣的
public class DumbJob implements Job {
String jobSays;
float myFloatValue;
ArrayList state;
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getMergedJobDataMap();
// Note the difference from the previous example
state.add(new Date());
System.err.println("Instance " + key + " of DumbJob says: "
+ jobSays + ", and val is: " + myFloatValue);
}
public void setJobSays(String jobSays) {
this.jobSays = jobSays;
}
public void setMyFloatValue(float myFloatValue) {
myFloatValue = myFloatValue;
}
public void setState(ArrayList state) {
state = state;
}
}
Job實例(Job Instances)
你可以創建一個Job類,創建多個JobDetail實例存儲多個該類的實例定義,每個JobDetail有他機子的屬性和JobDataMap,並將它們都添加到Scheduler中。
例如:你可以創建一個類實現了Job接口,名叫SalesReportJob。這個任務希望通過JobDataMap來傳遞參數來識別銷售的名字,銷售報告需要基於這個名字。它們可能創建多個job的定義(JobDetail),例如SalesReportForJoe,SalesReportForMike,它們有“joe”和"Mike"在JobDataMap中來輸入到各自的Job中。
當一個Trigger觸發時,與他相關的JobDetail(Job的實例定義)將被載入,它相關的Job類將通過JobFactory實例化。默認的JobFactory將調用Job類的newInstance(),然后嘗試調用Job類中setter方法(和JobDataMap中的key值相同)。你可以創建自定義JobFactory的實現來完成一些功能,例如應用的IoC
每個存儲了的JobDetail被認為是Job定義或者JobDetail實例,每個執行Job認為是Job實例或者Job定義實例。通常當屬於Job引用時,它通常是指一個Job的定義或者JobDetail。
Job State and Concurrency
有兩個注釋你可以添加到你的Job類中,這將影響Quartz的行為。
@DisallowConcurrentExecution - 有這個QUartz將不會同時執行多個給定Job定義的實例。在上一個例子中,如果SalesReportJob 有這個注解,那么在一個給定的時間,只有一個SalesReportForJoe 實例可以執行,但可以同時執行SalesReportForMike實例。約束是基於JobDetail,而不是Job類的實例。但注解是在Job類上的,因為行為的不同是Job類的代碼造成的。
@PersistJobDataAfterExecution - Quartz在成功執行了execute()方法后,JobDetail中JobDataMap將被更新,下一次執行相同job時,使用的是更新后的數據。和@DisallowConcurrentExecution一樣,注解也是定義在Job類上的。
如果你使用了@PersistJobDataAfterExecution注解,你應該考慮同時使用@DisallowConcurrentExecution注解,來避免當同一個job(JobDetail)的兩個實例被並發執行時,由於競爭,JobDataMap中存儲的數據很可能是不確定的。
Other Attributes Of Jobs
- Durability - 如果一個job是非持久的,它將在與它綁定的Scheduler不存在時同時不存在,也就是說他的生命周期是與它綁定的Trigger是相同的。
- RequestsRecovery - 當一個job在執行時意外關閉,那么job將再次執行當Scheduler再次啟動時。
JobExecutionException
execute方法中僅允許拋出一種類型的異常(包括RuntimeExceptions),即JobExecutionException。因此,你應該將execute方法中的所有內容都放到一個”try-catch”塊中。你也應該花點時間看看JobExecutionException的文檔,因為你的job可以使用該異常告訴scheduler,你希望如何來處理發生的異常。