第3課:更多關於工作和工作詳情
正如你在第2課中看到的,Jobs很容易實現,在接口中只有一個“execute”方法。 您只需要了解有關作業性質,Job界面的execute(..)方法以及JobDetails的更多內容。
雖然您實現的作業類具有知道如何做特定類型作業的實際工作的代碼,但Quartz需要被通知您可能希望該作業的實例具有的各種屬性。 這通過JobDetail類完成,在上一節中簡要介紹。
JobDetail實例使用JobBuilder類構建。 您通常會使用靜態導入其所有方法,以便在您的代碼中具有DSL感覺。
import static org.quartz.JobBuilder.*;
現在讓我們稍等一點,討論一下喬布斯的“自然”和“石英”中的工作實例的生命周期。 首先讓我們回顧一下我們在第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”:
public class HelloJob implements Job { public HelloJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { System.err.println("Hello! HelloJob is executing."); } }
請注意,我們給調度程序一個JobDetail實例,並且通過在構建JobDetail時簡單地提供作業的類,它知道要執行的作業的類型。 調度程序執行作業的每一個(和每個)時間,它在調用其execute(..)方法之前創建一個新的類的實例。 執行完成后,對作業類實例的引用將被刪除,然后將該實例進行垃圾回收。 這種行為的一個后果是,作業必須具有無參數構造函數(使用默認JobFactory實現時)。 另一個結論是,在作業類上定義狀態數據字段是沒有意義的 - 因為它們的值不會在作業執行之間保留。
您可能現在想要問“如何為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(在本教程的JobStore部分中討論過),則應該謹慎地決定您在JobDataMap中的位置,因為其中的對象將被序列化,因此它們容易出現類版本問題。 顯然,標准的Java類型應該是非常安全的,但除此以外,有人更改一個類的定義,你已經對其進行了序列化的實例,必須注意不要破壞兼容性。 或者,您可以將JDBC-JobStore和JobDataMap放入只允許將圖元和字符串存儲在地圖中的模式,從而消除后續序列化問題的任何可能性。
如果將Jobter方法添加到與JobDataMap中的鍵名稱相對應的作業類(例如上述示例中的數據的setJobSays(String val)方法),則Quartz的默認JobFactory實現將在作業被實例化,從而防止在執行方法中明確地將值從地圖中取出。
觸發器也可以與其相關聯的JobDataMaps。 這在您有一個Job存儲在調度程序中以供多次觸發器常規/重復使用的情況下可用,但是對於每個獨立觸發,您希望為Job提供不同的數據輸入。
在Job執行期間在JobExecutionContext上找到的JobDataMap用於方便。 這是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將數據映射值“注入”到您的類中,那么它可能看起來像這樣:
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”的Job接口的類。 該作業可能被編碼,以期望發送給它的參數(通過JobDataMap)來指定銷售人員應該基於的銷售人員的名稱。 然后,他們可以創建作業的多個定義(JobDetails),例如“SalesReportForJoe”和“SalesReportForMike”,它們在相應的JobDataMaps中指定的“joe”和“mike”作為相應作業的輸入。
當觸發器觸發時,與其相關聯的JobDetail(實例定義)被加載,並且通過在Scheduler上配置的JobFactory來實例引用它引用的作業類。 默認的JobFactory只是在作業類上調用newInstance(),然后嘗試在與JobDataMap中的鍵名匹配的類上調用setter方法。 您可能需要創建自己的JobFactory實現,以完成諸如讓應用程序的IoC或DI容器生成/初始化作業實例之類的操作。
在“Quartz talk”中,我們將每個存儲的JobDetail稱為“作業定義”或“JobDetail實例”,我們將每個執行的作業稱為“作業實例”或“作業定義的實例”。 通常,如果我們使用“job”這個詞,我們指的是一個命名定義,或者JobDetail。 當我們指的是實現作業界面的類時,通常使用術語“作業類”。
作業狀態和並發性
現在,關於作業的狀態數據(又名JobDataMap)和並發的一些附加說明。 有幾個注釋可以添加到Job類中,這些注釋會影響Quartz關於這些方面的行為。
@DisallowConcurrentExecution是一個注釋,可以添加到Job類中,該類可以告訴Quartz不要同時執行給定作業定義(引用給定作業類)的多個實例。
注意那里的措辭,因為它被非常仔細地選擇。 在上一節的示例中,如果“SalesReportJob”具有此注釋,則只能在給定時間執行一個“SalesReportForJoe”實例,但它可以與“SalesReportForMike”實例同時執行。 約束基於實例定義(JobDetail),而不是作業類的實例。 然而,決定(在Quartz的設計期間)將注釋載入類本身,因為它通常會對類的編碼方式產生影響。
@PersistJobDataAfterExecution是一個注釋,可以添加到Job類中,該類可以告訴Quartz在execute()方法成功完成后(而不會拋出異常)更新JobDetail的JobDataMap的存儲副本,以便下次執行相同的作業JobDetail)接收更新的值而不是原始存儲的值。 像@DisallowConcurrentExecution注釋一樣,這適用於作業定義實例,而不是作業類實例,盡管它決定讓作業類攜帶屬性,因為它通常會對類的編碼有所不同(例如“狀態” '需要被execute方法中的代碼明確地“理解”)。
如果您使用@PersistJobDataAfterExecution注釋,則應強烈考慮使用@DisallowConcurrentExecution注釋,以避免同時執行同一作業(JobDetail)的兩個實例時可能存在什么數據的可能混淆(競爭條件)。
工作的其他屬性
以下是通過JobDetail對象為作業實例定義的其他屬性的快速摘要:
- 耐用性 - 如果作業是非持久性的,則在不再有任何與之相關聯的活動觸發器時,它將自動從調度程序中刪除。 換句話說,非耐用工作的生命周期是由觸發器的存在所限制的。
- RequestsRecovery - 如果作業“請求恢復”,並且它在調度程序的“嚴重關機”(即,在崩潰中運行的進程,或者機器被關閉)的時間內執行,則重新執行當調度程序再次啟動時。 在這種情況下,JobExecutionContext.isRecovering()方法將返回true。
JobExecutionException
最后,我們需要通知你幾個Job.execute(..)
方法的細節。 允許從execute方法拋出的唯一類型的異常(包括RuntimeExceptions)是JobExecutionException。 因此,您通常應該使用“try-catch”塊來包裝execute方法的全部內容。 您還應該花一些時間查看JobExecutionException的文檔,因為您的工作可以使用它為調度程序提供有關如何處理異常的各種指令。