Quartz官方教程翻譯系列-Lesson 3
原文地址: http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/tutorials/tutorial-lesson-03.html
第三課: Jobs 與 Job Details 更多相關
正如你在第二課所見,任務實現起來相當簡單,僅僅有一個 'execute' 方法在介面。這裡僅有少數東西你需要明白關於jobs的本質,關於Job 介面 execute(...) 方法,還有 JobDetails。
當你實現一個任務類,有知道如何做特定型別工作的程式碼,Quartz 需要了解關於各種你希望任務例項擁有的屬性。這個就通過JobDetail完成,在上一節已經簡單提過。
JobDetail 例項是通過 JobBuilder 類構建的。你通常可以用一個靜態匯入全部它的方法,為了讓你的程式碼看起來有領域規範語言的感覺。
import static org.quartz.JobBuilder.*;複製程式碼
讓我們花一點時間討論一點 Jobs的本性和 job例項的在Quartz的生命週期。首先讓我們回頭看一眼我們在第一課的程式碼片段:
// 定義任務並繫結我們的 HelloJob 類
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob","group1") // name "myJob",group "group1"
.build();
// 觸發任務立刻執行,並且妹40秒有也行
Trigger trigger = newTrigger()
.withIdentity("myTrigger","group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// 告訴 quartz 執行排程使用的觸發器
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.");
}
}複製程式碼
注意我們為任務排程指定一個JobDetail例項,並且在構建JobDetail時,只需提供作業的類即可執行的作業型別。每一次任務排程執行任務,它會在被呼叫 execute(...)方法前被建立新的例項。當執行結束,任務類例項的引用被丟棄,並且例項會被回收。這種行為的一個後果就是任務必要有一個無參構造器(當使用預設的 JobFactoy 實現時)。另一個結果就是在作業類上定義狀態資料欄位是沒有意義的-他們的值不會在任務執行期間被儲存。
你可能會想問“我怎樣才能為一個任務例項儲存屬性/配置?” 還有 “我怎樣才能跟蹤一個任務的狀態在執行之間?”他們的答案都是一樣的,關鍵是JobDataMap, JobDetail物件的一部分。
JobDataMap
JobDataMap 可以用來持有任意數量(序列化)資料物件那些你在任務例項執時可用的。JobDataMap
是Java Map介面的一個實現,並且加了一些方便的方法用於儲存和恢復原始型別的資料。
這裡是關於在定義/構建 JobDetail 存放資料到 JobDataMap
的程式碼片段,在一個任務加入排程任務之前:
// 定義任務並且繫結到DumbJob類
JobDetail job = newJob(DumbJob.class)
.withIdentity("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 進入只允許原語和字串儲存在對映中的模式,從而消除了以後出現序列化問題的可能性。
如果你增加對應於 JobDataMap中鍵名的 setter 方法到你的任務類(好像下面例子的 data 中 setJobSay(String val)) 方法,然後Qurartz 的預設 JobFactory 例項會自動呼叫他的 setter 在 Job 例項化後,因此,無需在你的execute方法內從對映中顯式獲取值。
觸發器同樣可以 JodDataMaps關聯。這樣在當你有一個任務被儲存在排程程式中給多個觸發器定期/重複使用的情況下很有用,然而,對於每個獨立的觸發,你都為任務提供不同的資料輸入。
JobDataMap 在任務執行的時候,很方便在JobExeccutionContext找到。它是JobDetail上的JobDataMap和Trigger上的JobDataMap的結合,使用後者中的值覆蓋前者中的任何同名值。
這裡是一個簡短的例子,演示怎樣從 JobExecutionContext 和 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 注入 資料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() 方法裡面的程式碼更清晰。有人可能會說,雖然程式碼更長,單實際用到了更少的程式碼,如果程式設計的 IDE 有自己建立 setter 方法,而不必手動編寫各個呼叫的程式碼來從JobDataMap檢索值。由你選擇。
任務"例項"
很多使用者花時間搞不清什麼是任務例項。我們嘗試在以下的章節裡面說清楚任務狀態和併發性。
你可以建立一個單獨的任務類,並且儲存多個“例項定義”通過建立JobDetails的多個例項在排程程式中-每個都有其自己的屬性和JobDataMap-並將它們全部新增到排程程式中。
舉例,你可以建立一個名字叫 "SalesReportJob"的任務例項。這個任務可能被期望的引數傳送(好像 JobDataMap) 到指定銷售報告所依據的銷售人員的姓名。他們可能在任務建立多個定義(JobDetails) ,好像"SalesReportForJbe" 和 "SaleReportForMike" 用"joe" 和 "mike" 在相應的JobDataMaps中指定為各個作業的輸入。
當一個觸發器點燃,JobDetail(例項定義) 關聯被載入,並且 任務類例項被 JobFactory 引用 配置到 執行排程。預設 JobFactory 呼叫 job 類的 newInstance() 方法,意圖呼叫在類上與JobDataMap的鍵名匹配的方法。你可能想建立你自己的 JobFactory 例項去完成你應用的 IOC 或者 DI 容器 生產/初始化任務例項。
在 "Quartz speak",我們提及到每個儲存的JobDetail 好像 "job definiton" 或 "JobDetail instance" ,然後我們提及到每一個執行的任務好似"job instance" 或者 "instance of a job definition"。通常,如果我們僅僅使用 “job”這個詞,我們是提及一個命名定義,或者 JobDetail。當我們提及到 job 類例項,我們通常用術語 "job class"。
任務狀態和並行
現在,一些關於任務狀態資料(也就是 JobDataMap)和並行)的附加說明。這裡是有一對註解可以在加在你的任務類上,影響 Quzrtz 的行為。
@DisallowConcurrentExecution 是一個可以加在任務類上的註解,告訴 Quartz不可以在指定的任務定義(這裡指指定的任務類)並行執行多個例項。注意這裡的用詞,是非常謹慎選擇的。在上一節的例子,如果 "SalesReportJob" 有這個註解,那麼僅有一個 "SalesReportForJoe"的例項可以在指定時間執行,但可以並行執行一個"SaleReportForMike"的例項。約束是基於例項定義(JobDetail),不是在任務的例項。然而,它是取決於(在Quartz設計期間)註解是否對本身進行了處理,因為它經常對類的編碼方式產生影響。
@PersistJobDataAfterExecution 是一個可以加在任務類上的註解,告訴Quzrtz 在執行 execute() 方法成功後(沒有丟擲異常)更新JobDetail的 JobDataMap儲存,這樣下一次執行同樣的任務(JobDetail) 接收到更新值而不是儲存的值好像@DisallowConcurrentExcetion 一樣,適用於一個任務的例項,不是一個任務類是例項,取決於任務類的屬性因為它通常對類的編碼方式產生影響。(好像"statefulness" 會需要明確的"understood"通過在execute 方法裡的程式碼)。
如果你用@PersistJobDataAfterExecution 這個註解,你需要慎重考慮同時用@DisallowConcurrentExecution 註解,為了避免一個相同的任務(JobDetail)並行時儲存的資料導致可能的混亂(速度混亂)。
其他的任務屬性
這裡是一個快速總結定義一個任務例項即 JobDetail 物件的其他屬性:
- 永續性 - 如果一個任務是非持久的,他會從任務排程執行一旦沒有任何的觸發器關聯,非持久任務的生命週期繫結在觸發器的存在。
- 可恢復性- 如果一個任務“可恢復”,並它執行的時候被“硬關閉”(也就是程式在跑的過程中奔潰,或者機器關機),那麼當任務排程再次開始時,會重新執行。在這種情況下, JobExecutionContext.isRecovering() 方法返回 true。
JobExecutionException
最後,我們需要告知你 Job.execute(...)
的一些細節。唯一的一種異常型別(包含RuntimeEcxeption) 你可以從執行方法中丟擲的異常是 JobExecutionException。 正是因為這樣,你應該通常包裝全部內容在“try-catch”塊中。你還應該花點時間閱讀檔案中,關於JobExecutionExcetion的部分,為排程程式提供各種指令,以確定您希望如何處理異常。
本文由部落格一文多發平臺 OpenWrite 釋出!