原文連接 | 譯文連接 | 翻譯:nkcoder | 校對:html
本系列教程由quartz-2.2.x官方文檔翻譯、整理而來,但願給一樣對quartz感興趣的朋友一些參考和幫助,有任何不當或錯誤之處,歡迎指正;有興趣研究源碼的同窗,能夠參考我對quartz-core源碼的註釋(進行中)。java
正如在教程二中講到的,Job實現起來很容易,該接口只有一個「execute」方法。本節主要關注:Job的特色、Job接口的execute方法以及JobDetail。git
你定義了一個實現Job接口的類,這個類僅僅代表該job須要完成什麼類型的任務,除此以外,Quartz還須要知道該Job實例所包含的屬性;這將由JobDetail類來完成。github
JobDetail實例是經過JobBuilder類建立的,導入該類下的全部靜態方法,會讓你編碼時有DSL的感受:編程
import static org.quartz.JobBuilder.*;
讓咱們先看看Job的特徵(nature)以及Job實例的生命期。不妨先回頭看看教程一中的代碼片斷:api
// 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的類名傳給了JobDetail,因此scheduler就知道了要執行何種類型的job;每次當scheduler執行job時,在調用其execute(…)方法以前會建立該類的一個新的實例;執行完畢,對該實例的引用就被丟棄了,實例會被垃圾回收;這種執行策略帶來的一個後果是,job必須有一個無參的構造函數(當使用默認的JobFactory時);另外一個後果是,在job類中,不該該定義有狀態的數據屬性,由於在job的屢次執行中,這些屬性的值不會保留。併發
那麼如何給job實例增長屬性或配置呢?如何在job的屢次執行中,跟蹤job的狀態呢?答案就是:JobDataMap,JobDetail對象的一部分。less
JobDataMap
JobDataMap中能夠包含不限量的(序列化的)數據對象,在job實例執行的時候,可使用其中的數據;JobDataMap是Java Map接口的一個實現,額外增長了一些便於存取基本類型的數據的方法。函數
將job加入到scheduler以前,在構建JobDetail時,能夠將數據放入JobDataMap,以下示例:
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部分會講到),在決定JobDataMap中存放什麼數據的時候須要當心,由於JobDataMap中存儲的對象都會被序列化,所以極可能會致使類的版本不一致的問題;Java的標準類型都很安全,若是你已經有了一個類的序列化後的實例,某個時候,別人修改了該類的定義,此時你須要確保對類的修改沒有破壞兼容性;更多細節,參考現實中的序列化問題。另外,你也能夠配置JDBC-JobStore和JobDataMap,使得map中僅容許存儲基本類型和String類型的數據,這樣能夠避免後續的序列化問題。
若是你在job類中,爲JobDataMap中存儲的數據的key增長set方法(如在上面示例中,增長setJobSays(String val)方法),那麼Quartz的默認JobFactory實如今job被實例化的時候會自動調用這些set方法,這樣你就不須要在execute()方法中顯式地從map中取數據了。
在Job執行時,JobExecutionContext中的JobDataMap爲咱們提供了不少的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的並集,可是若是存在相同的數據,則後者會覆蓋前者的值。
下面的示例,在job執行時,從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實現數據的自動「注入」,則示例代碼爲:
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中獲取數據了,因此你實際須要編寫的代碼更少了。當前,如何選擇,由你決定。
Job實例
不少用戶對於Job實例到底由什麼構成感到很迷惑。咱們在這裏解釋一下,並在接下來的小節介紹job狀態和併發。
你能夠只建立一個job類,而後建立多個與該job關聯的JobDetail實例,每個實例都有本身的屬性集和JobDataMap,最後,將全部的實例都加到scheduler中。
好比,你建立了一個實現Job接口的類「SalesReportJob」。該job須要一個參數(經過JobdataMap傳入),表示負責該銷售報告的銷售員的名字。所以,你能夠建立該job的多個實例(JobDetail),好比「SalesReportForJoe」、「SalesReportForMike」,將「joe」和「mike」做爲JobDataMap的數據傳給對應的job實例。
當一個trigger被觸發時,與之關聯的JobDetail實例會被加載,JobDetail引用的job類經過配置在Scheduler上的JobFactory進行初始化。默認的JobFactory實現,僅僅是調用job類的newInstance()方法,而後嘗試調用JobDataMap中的key的setter方法。你也能夠建立本身的JobFactory實現,好比讓你的IOC或DI容器能夠建立/初始化job實例。
在Quartz的描述語言中,咱們將保存後的JobDetail稱爲「job定義」或者「JobDetail實例」,將一個正在執行的job稱爲「job實例」或者「job定義的實例」。當咱們使用「job」時,通常指代的是job定義,或者JobDetail;當咱們提到實現Job接口的類時,一般使用「job類」。
Job狀態與併發
關於job的狀態數據(即JobDataMap)和併發性,還有一些地方須要注意。在job類上能夠加入一些註解,這些註解會影響job的狀態和併發性。
@DisallowConcurrentExecution:將該註解加到job類上,告訴Quartz不要併發地執行同一個job定義(這裏指特定的job類)的多個實例。請注意這裏的用詞。拿前一小節的例子來講,若是「SalesReportJob」類上有該註解,則同一時刻僅容許執行一個「SalesReportForJoe」實例,但能夠併發地執行「SalesReportForMike」類的一個實例。因此該限制是針對JobDetail的,而不是job類的。可是咱們認爲(在設計Quartz的時候)應該將該註解放在job類上,由於job類的改變常常會致使其行爲發生變化。
@PersistJobDataAfterExecution:將該註解加在job類上,告訴Quartz在成功執行了job類的execute方法後(沒有發生任何異常),更新JobDetail中JobDataMap的數據,使得該job(即JobDetail)在下一次執行的時候,JobDataMap中是更新後的數據,而不是更新前的舊數據。和 @DisallowConcurrentExecution註解同樣,儘管註解是加在job類上的,但其限制做用是針對job實例的,而不是job類的。由job類來承載註解,是由於job類的內容常常會影響其行爲狀態(好比,job類的execute方法須要顯式地「理解」其」狀態「)。
若是你使用了@PersistJobDataAfterExecution註解,咱們強烈建議你同時使用@DisallowConcurrentExecution註解,由於當同一個job(JobDetail)的兩個實例被併發執行時,因爲競爭,JobDataMap中存儲的數據極可能是不肯定的。
Job的其它特性
經過JobDetail對象,能夠給job實例配置的其它屬性有:
- Durability:若是一個job是非持久的,當沒有活躍的trigger與之關聯的時候,會被自動地從scheduler中刪除。也就是說,非持久的job的生命期是由trigger的存在與否決定的;
- RequestsRecovery:若是一個job是可恢復的,而且在其執行的時候,scheduler發生硬關閉(hard shutdown)(好比運行的進程崩潰了,或者關機了),則當scheduler從新啓動的時候,該job會被從新執行。此時,該job的JobExecutionContext.isRecovering() 返回true。
JobExecutionException
最後,是關於Job.execute(..)方法的一些額外細節。execute方法中僅容許拋出一種類型的異常(包括RuntimeExceptions),即JobExecutionException。所以,你應該將execute方法中的全部內容都放到一個」try-catch」塊中。你也應該花點時間看看JobExecutionException的文檔,由於你的job可使用該異常告訴scheduler,你但願如何來處理髮生的異常。
原創文章,轉載請註明: 轉載自併發編程網 – ifeve.com本文連接地址: Quartz教程三:Job與JobDetail介紹