Quartz教程 第3課 Job和JobDetail詳解
第3課 Job和JobDetail詳解
正如你在第二課中看到的,Job介面非常容易實現,它只有一個execute方法。我們需要再學習一些知識去理解job的本質,Job介面的execute方法以及JobDetail介面。
當你實現Job介面類,Quartz需要你提供job例項的各種引數,Job介面實現類中的程式碼才知道如何去完成指定型別Job的實際工作。這個過程是通過JobDetail類來完成的,該類會在上一個章節簡單的提到過。
JobDetail的例項是呼叫JobBuilder類建立的。通常你可以使用靜態匯入方式獲得該類所有方法的呼叫,這樣可以在你的程式碼體驗DSL的感覺。
importstatic org.quartz.JobBuilder.*;
讓我們花一些時間來討論一下Quartz中Job以及Job例項的生命週期。首先,我們來回顧一下第1課中看到的程式碼片段:
// 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”類:
publicclassHelloJobimplementsJob { publicHelloJob() { } public voidexecute(JobExecutionContext context) throwsJobExecutionException { System.err.println("Hello! HelloJob is executing."); } } |
請注意我們給scheduler提供了一個JobDetail例項,我們構建JobDetail物件時僅提供了job的class物件,排程器就知道它要執行的job型別。每次排程器執行job時,它在呼叫excecute方法前會建立一個新的job例項。當執行完成時,job類例項的引用就會被丟棄,這個例項就會垃圾回收機制回收。這種呼叫過程導致的其中一個結果是job物件必須要有一個無引數構造器(使用預設的JobFacotry實現時),另外一個結果job實現類不能定義狀態資料欄位,因為這些狀態資料欄位的值在呼叫job任務時不會被保留。
你現在可能想問“我要怎樣才能為Job例項提供配置引數?在執行任務時我要如何跟蹤job物件的狀態?”這兩個問題的答案都一樣:使用JobDataMap,它是JobDetail物件的一部分。
3.1JobDataMap
JobDataMap可以用來持有任何可序列化的資料物件,當job例項物件被執行時這些引數物件會傳遞給它。JobDataMap實現Map介面,並且添加了一些非常方便的方法用來存取基本資料型別。
下面的程式碼片斷演示了在定義/構建JobDetail物件時,job物件新增到排程器之前,如何將資料存放至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(); |
下面的例子顯示了在job執行過程中如何從JobDataMap取資料:
public classDumbJobimplementsJob { publicDumbJob() { } public voidexecute(JobExecutionContext context)throwsJobExecutionException{ JobKey key = context.getJobDetail().getKey(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String jobSays = dataMap.getString("jobSays"); floatmyFloatValue = dataMap.getFloat("myFloatValue"); System.err.println("Instance " + key +" of DumbJob says: " + jobSays +", and val is: " + myFloatValue); } } |
如果你使用持久化的JobStore(將會在教程JobStore部分討論),你應該要多考慮放在JobDataMap中的資料物件,因為此時的物件會被序列化,因此這更容易出現版本問題。顯然標準Java型別是很安全的,但是非標準Java型別,任何時候有人變更你序例化例項的類的定義,都要注意不要破壞相容性。你可以選擇將JDBC-JobStore和JobDataMap設計成只有基本資料型別和String型別才允許儲存的map物件,從而從根本上消除序列化問題。
如果你在job類中新增setter方法,對應JobDataMap的鍵值(例如setJobSays(String val)方法對應上面例子裡的jobSays資料),Quartz框架預設的JobFactory實現類在初始化job例項物件時會自動地呼叫這些setter方法,從而防止在呼叫執行方法時需要從map物件取指定的屬性值。
觸發器也可以關聯JobDataMap物件,當儲存在排程器中的job物件需要定期/重複執行,被多個觸發器共用時,這種場景下使用JobDataMap將非常方便,然而每個獨立的觸發器,你都可以為job物件提供不同的輸入引數。
在進行任務排程時JobDataMap儲存在JobExecutionContext中非常方便獲取。它合併了JobDetail和Trigger裡的JobDataMap資料物件,後面的值會把前面相同鍵值的值覆蓋。
接下來的例子演示了任務執行過程中從JobExecutionContext取合併的JobDataMap資料:
public classDumbJobimplementsJob { publicDumbJob() {} public voidexecute(JobExecutionContext context)throwsJobExecutionException{ JobKey key = context.getJobDetail().getKey(); JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example String jobSays = dataMap.getString("jobSays"); floatmyFloatValue = dataMap.getFloat("myFloatValue"); ArrayList state = (ArrayList)dataMap.get("myStateData"); state.add(newDate()); System.err.println("Instance " + key +" of DumbJob says: " + jobSays +", and val is: " + myFloatValue); } } |
或者如果你想在類中依賴JobFactory注入map資料,它看起來可能是這樣的:
public classDumbJobimplementsJob { String jobSays; floatmyFloatValue; ArrayList state; publicDumbJob() {} public voidexecute(JobExecutionContext context)throwsJobExecutionException { JobKey key = context.getJobDetail().getKey(); JobDataMap dataMap = context.getMergedJobDataMap(); // Note the difference from the previous example state.add(newDate()); System.err.println("Instance " + key +" of DumbJob says: " + jobSays +", and val is: " + myFloatValue); } public voidsetJobSays(String jobSays) { this.jobSays = jobSays; } public voidsetMyFloatValue(floatmyFloatValue) { myFloatValue = myFloatValue; } public voidsetState(ArrayList state) { state = state; } } |
你可能會注意到類的整體程式碼比較長,但execute方法很簡潔。有人會認為雖然程式碼比較長,如果程式設計師的IDE能自動生成setter方法的話,那麼實際只需要編寫更少的程式碼,而不必手工編寫那些單獨的呼叫方法從JobDataMap中取值。你可以自主選擇編寫程式碼的方式。
3.2Job例項
許多使用者對Job例項物件確切的結構是什麼疑惑了很長時間,我們將嘗試在這為大家解答,並且在下一個板塊講述Job狀態和併發機制。
你可以建立一個單獨的Job實現類,建立多個不同的JobDetail例項,將不同Job例項定義儲存在排程器中,每個JobDetail例項都有各自的引數和JobDataMap,並且把這些JobDetail新增到排程器中。
例如:你建立一個Job介面的實現類,類名為“SalesReportJob”,Job類可以預先傳入一些假想的引數(通過JobDataMap)來指定銷售報表中業務員的名字。接下來建立多個Job的定義(即JobDetail),如“SalesReportForJoe”和 “SalesReportForMike”通過“Joe”和“Mike”指定到相應的JobDataMap中作為引數輸入到各自的Job物件中。
當trigger被觸發時,相關的JobDetail例項會被載入,通過在排程器中配置的JobFactory會將關聯的Job類例項化。預設的JobFactory只是簡單的呼叫Job類的newInstance方法,然後嘗試呼叫匹配JobDataMap鍵值的setter方法。你可以開建立自己的JobFactory實現類,以完成一些諸如通過應用IOC或DI機制完成Job例項的建立和初始化的事情。
用Quartz框架的話來說,我們將每個儲存的JobDetail稱為Job定義或JobDetail例項,將每個執行的作業任務(Job)稱為Job例項或Job定義例項。通常我們只用“job”單詞來對應命名的Job定義或是JobDetail。當我們指Job介面的實現類時,一般使用“job class”術語。
3.3Job狀態和併發機制
現在,需要關注一下Job的狀態資料(也叫JobDataMap)和併發機制。有一對加在Job類上面的註解,可以影響Quartz框架的這些方面的行為。
@DisallowConcurrentExecution註解新增到Job類中,會告知Quartz不要併發執行相同Job定義建立的多個例項物件。注意這裡的措辭,要慎重地選擇。引用上一章節的例子,如果SalesReportJob新增這個註解,在給定的時間段內只能執行一個 SalesReportJobForJoe例項物件,但是可以併發執行一個SalesReportJobForMike例項。這個限制是基於例項定義(JobDetail),而不是基於Job類例項。然而,在Quartz設計階段決定在該類中攜帶註解,因為該註解會影響JobDetail類的編碼。
@PersistJobDataAfterExecution註解新增到Job類中,會告知Quartz成功執行完execute方法後(沒有丟擲異常)更新JobDetail的JobDataMap中儲存的資料。例如同一個JobDetail下一次執行時將接收更新後的值而不是初始值。跟@DisallowConcurrentExecution註解類似,@PersistJobDataAfterExecution註解適用於Job定義例項,而不是Job類例項。只是該註解是附著在Job類的成員變數中,因為它不會影響整個類的編碼(例如statefulness只需要在execute方法程式碼內正確使用即可)。
如果你使用@PersistJobDataAfterExecution註解,強烈建議你也應該考慮使用 @DisallowConcurrentExecution註解,避免當兩個相同JobDetail例項併發執行時可能由於最後儲存狀態資料不一致導致執行混亂。
3.4 其它的Job屬性
這裡總結了可以通過JobDetail物件為Job例項定義的其它屬性。
Durability:如果一個Job是非持續的,一旦沒有任何活躍的觸發器關聯這個Job例項時,這個例項會自動地從排程器中移除。換句話說,非持續的job的生命週期受限於觸發器的存在性。
RequestsRecovery:如果一個Job設定了請求恢復引數,並且在排程器強制關閉過程中恰好在執行(強制關閉的情況例如:執行的執行緒崩潰,或者伺服器宕機),當排程器重啟時,它會重新被執行。這種情況下,JobExecutionContext的isRecovering方法會返回true。
3.5JobExecutionException
最後,我們需要告知你Job.execute方法的一些細節。允許你從execute方法丟擲的唯一一種異常型別是JobExecutionException(執行時異常除外,可以正常丟擲),由於這個限制,你應該在execute方法內的try-catch程式碼塊中包裝好要處理的異常。你也可以花些時間查閱一下JobExecutionException的文件,便於你在開發Job類中需要捕獲處理異常時,為排程器提供各種資訊。