[譯]Java定時任務排程-Quartz文件(三)進一步講講Job和Job Detail
正如上篇文章所說的,Job很容易實現,只需要介面中唯一的execute方法。除此之外,你還需要稍微瞭解下Job、execute、Job interface和JobDetail的一些東西。
當你寫的Job類執行特定任務時,Quartz需要知道這個類應該具有的各種屬性。這就是前面所提到的JobDetail類完成的。
JobDetail例項是由JobBuilder建立的,可以這樣引入:
import static org.quartz.JobBuilder.*;
讓我們花一點時間來看看Job的原理和Quartz中Job的生命週期。首先看一下在第一篇文章中提及的一塊程式碼片段:
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "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的定義如下:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("Hello! HelloJob is executing.");
}
}
注意傳給scheduler的是一個JobDetail的例項,僅僅通過在建立JobDetail是指定了Job class,它就知道知道所執行的任務型別。每當scheduler執行任務的時候,總是建立一個新的類例項,在執行其execute方法。執行完畢後,會釋放對該例項的引用,稍後將會被垃圾回收。這種做法要求Job類必須有一個無參的構造方法,還有就是靜態域是無效的,因為無法在多個執行例項中共享。
你可能想問,如何對Job例項配置屬性呢?或者換句話說,如何跟蹤多個例項的執行呢?答案是使用JobDataMap,它屬於JobDetail物件的一部分。
JobDataMap
JobDataMap是用來在執行過程中儲存必要的資料物件。JobDataMap實現了Java Map介面,
還定義了一些存取基本資料型別的方法。
下面的例子介紹了在建立JobDetail時向JobDataMap中新增資料:
// 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();
下面的例子介紹在執行過程中如何從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,必須注意放入JobDataMap中的資料型別,因為這個物件會被序列化,可能會有版本問題。顯而易見,java的基本型別是沒問題的,但是如果是使用者定義的型別發生了變化,而存在之前序列化的物件,就必須要考慮相容性問題了。當然,也可以限定JDBC-JobStore和JobDataMap只能儲存基本型別和String,這樣就排除了序列化帶來的潛在問題。
如果你在Job類中定義了與JobDataMap中key對應的setter方法,Quartz的就會在job例項化是呼叫這些setter方法,所以應避免定義類似的方法,以保證map中資料的準確性。
Triigers也有對應的JobDataMap。當定義一個任務,關聯到多分資料、多個觸發器,定期、重複執行時,JobDataMap會很有用。
JobDataMap定義在Job執行過程中的JobExecutionContext中,由JobDetail中的JobDataMap和Trigger中的JobDataMap合併而成,如果有重名的變數,前面的將會被後面的所覆蓋。
下面是從合併後的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);
}
}
如果你想使用JobFactory將data map中的變數“注入”到你的類中,可以這樣做:
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;
}
}
你應該能注意到,下面的程式碼總的來說更長,但是execute中的程式碼卻變短了。有人會說雖然總程式碼量長,但是setter方法都可以用IDE自動生成,所以“手工”的程式碼實際是更短的,而且不必手動的從JobDataMap中取資料。怎麼做,你決定!
Job “Instances”
很多使用者花很長時間糾結於是誰建立了job例項。這裡我們將就任務的狀態和併發等問題,弄個明白。
你可以定義一個job類,然後建立多個JobDetail例項,每個例項都有自己的屬性和JobDataMap,然後把它們關聯到一個scheduler。
例如,你可以建立一個實現了Job interface的類“SalesReportJob”,這個job需要根據傳進來的銷售人員的姓名來確定銷售報告。接著需要定義多個JobDetail,例如“SalesReportForJoe” and “SalesReportForMike”,並將“joe” and “mike”存入個子的JobDataMap中。
當觸發器啟動是,與之關聯的JobDetail將會被載入,對應的job class會通過scheduler中配置的JobFactory例項化。預設的JobFactory會呼叫job class的newInstance方法,然後根據JobDataMap中的key值呼叫對應的setter方法進行變數賦值。你可能想建立自己的JobFactory實現來完成注入自己的IoC(控制反轉)或者DI(依賴注入)。
在“Quartz speak”中,我們將儲存的JobDetail看做“job definition”或者“JobDetail 例項”。通常來說,提到“job”,我們指的是一個具體的實現,即JobDetail;提到實現job interface的類,通常指的是“job class”。
Job State and Concurrency
接下來再看看任務的狀態資料和併發問題。以下是Job類中可以使用的註解,這些註解會影響Quartz的行為。
@DisallowConcurrentExecution 新增到Job class,Quartz就不會併發的執行多個任務例項。
注意這裡的用詞。在前面的例子中,如果“SalesReportJob”添加了這個註解,那麼同時只會有一個“SalesReportForJoe”例項執行,但這並不影響併發的執行“SalesReportForJoe”任務例項。這個約束是在JobDetail維度的,不是在Job class維度。
@PersistJobDataAfterExecution 添加了這個註解的job class,在執行完JobDetail後,會將其JobDataMap備份,以便於下次執行是使用,而不是使用預設的初始化資料。和第一個一樣,這個約束也是在JobDetail維度,而不是Job class維度。
如果你使用@PersistJobDataAfterExecution註解,你需要慎重考慮是否要使用@DisallowConcurrentExecution註解,以避免當一個JobDetail併發執行時資料是否會相互影響。
Other Attributes Of Jobs
下面是其他可以通過JobDetail物件定義給job例項的屬性歸類:
- Durability - 如果job是非持久的,與其相關的所有trigger觸發執行完成之後將會自動刪除,換計劃說,所有job都是以與其相關的trigger結束而結束的。
- RequestsRecovery - 如果job是“請求恢復”的,如果任務執行過程中schduler強制關閉了,那麼當scheduler重新啟動時,它將再次執行。在這種情況下JobExecutionContext.isRecovering()將返回TRUE;
JobExecutionException
最後,我們需要講下Job.execute(..)的細節。在這個方法內部唯一可以排除的異常是JobExecutionException。鑑於此,可能會丟擲的異常都應該使用tru/catch捕獲處理。你也因該進一步看下JobExecutionException相關文件,以便更好的處理。