Quartz 教程
Table of Contents | ‹ Lesson 2 | Lesson 4 ›
課程3: 關於 Jobs 和 Job Details
正如你課程2看到的,Jobs十分容易實現,只需要實現接口中的‘execute’ 方法。你還需要了解更多關於jobs的性質,Job接口的execute(..)方法,關於JobDetails。
當你實現job類通過代碼知道怎麼實現不同類型的Job,Quartz需要配置你希望job擁有的不同屬性。這是通過JobDetail類完成的,在上一節簡要提到過。
JobDetail實例的構建使用JobBuilder類。你通常可以使用靜態導入所有方法,這樣可以使你的代碼擁有DSL的感覺。
import static org.quartz.JobBuilder.*;
現在讓我們討論一下關於Jobs的'本質'和在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' Job類定義如下:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("Hello! HelloJob is executing.");
}
}
我們給這個scheduler一個JobDetail實例,它知道這個被執行job的類型,你只需簡單提供job類就可以構造JobDetail。每次調度執行這個job, 都會在調用它的execute(..)方法前,創建一個這個類的新實例。當執行完成後,對job類實例的引用將刪除,實例會被垃圾收集器回收。這種行爲使得Jobs必須有一個無參的構造函數(當使用默認JobFactory實現時). 另一方面在job類中定義數據字段是沒有意義的 - 因爲在job執行期間這些值不會被保存.
你可能會問“那我怎樣能賦予Job實體屬性/配置呢? 和 “我怎樣能在job執行期間追蹤它的狀態?” 這些問題的答案是同一個:關鍵在於JobDataMap, 它是JobDetail對象的一部分.
JobDataMap
JobDataMap能夠用來保存你希望在Job執行時能夠傳遞給Job的任意數量的數據。JobDataMap是Java Map接口的一種是實現, 並且有一些遍歷的方法,來存儲和檢索原始類型的數據。
下面是當定義/構建JobDetail時一些往JobDataMap中放入數據的片段,job添加至調度程序之前:
// 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 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,只有字符串被允許存放到map中,這樣就排除了任何序列化問題的可能。
如果你給job類添加set方法,這個方法的名稱相當於在JobDataMap中的key。(例如上個例子中的數據,setJobSays(String val)方法), 接着當job實例化後,Quartz’s默認的JobFactory實現將會自動調用這些set方法,這樣就避免了在執行方法中顯示的獲取map中的值.
Triggers也可以有與之關聯的JobDataMaps。在你有一個Job在調度程序儲存中,並且有多個觸發器定期/重複使用的情況下是十分有用的,每一個觸發器觸發時,你都可以給Job提供不同的數據輸入。
在Job執行期間通過JobExecutionContext獲得JobDataMap是十分方便的。它的是JobDatail的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向你的類注入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開發工具使用了set方法自動生成,那麼就不需要單獨再從JobDataMap中獲取數據。至於如何選擇在於你了。
Job “Instances”
許多用戶對於花費時間教你如何構建"job 實例"感到困惑。我們將會在下部分講解關於job狀態和併發的事情。
你可以創建一個單獨的job類,然偶胡在調度程序中創建需要JobDetails的實例 - 每個都有自己的屬性和JobDataMap - 並將它們全部添加到調度程序中.
例如,你可以創建一個實現Job接口的類"SalesReportJob"。這個job被用來根據輸入給它的參數(通過JobDataMap),來指定銷售報告根據銷售員來生成。他們可以創建多個job的定義(JobDetails), 例如“SalesReportForJoe” 和“SalesReportForMike” ,在JobDataMaps中將"joe"和"mike"分別輸入給對應的jobs。
當一個觸發觸發時,JobDetail(實例定義) 與加載有關,它所引用的job類是通過在調度程序中的JobFactory配置初始化的。默認的JobFactory會簡單的調用job class的newInstance()方法,然後嘗試在類中調用JobDataMap中與key名稱相匹配的set方法。你可能希望創建你自己的JobFactory讓你應用程序的Ioc或則DI容器生產/初始化job實例。
在“Quartz speak”, 我們將每一個存儲的JobDetail作爲"job definition"或者“JobDetail instance”, 我們將每一個調用的job稱爲"job實例"或者"job定義的實例"。通常如果我們只是使用"job",我們使用的是"job"這個詞,一般我們指"job"這個詞的意思是名稱定義或者JobDetail。當我們提到job接口的實現,我們通常使用"job class"。
Job 狀態和併發
現在, 需要注意job的狀態數據(又稱作JobDataMap)和併發. 有一些註解被添加到你的Job類中,這些註解會影響在這些方面Quartz的行爲。
@DisallowConcurrentExecution 是可以添加到Job類的註釋,告訴Quartz不要同時執行給定job定義的多個實例(指的是給定的做作業類) .
注意這裏的描述,小心的選擇使用。在前一節例子中,如果“SalesReportJob”上有這個註解,在給定時間內只有一個"SalesReportForJoe"實例被執行, 但是能夠同時執行多個"SalesReportForMike"實例。約束基於實例定義(JobDetail),不是工作類的實例。不管怎樣, 它(在Quartz定義中)都明確的指出註釋應加載類本身,因爲它通常會對類的編碼產生影響。
@PersistJobDataAfterExecution 是一個能夠在execute()方法完全執行後,告訴Quartz更新JobDetail的JobDataMap的存儲副本的註解。這樣同一個Job(JobDetail)下一次執行將會收到更新後的值而不是原始存儲的值。就像@DisallowConcurrentExecution 註解, 這適用於作業定義實例,而不是一個作業類實例,儘管決定讓job類攜帶屬性,因爲會對類的編碼產生影響。(例如在執行方法中,"statefulness"需要顯示指定"understood"。
如果你適用@PersistJobDataAfterExecution註解, 你也應該好好思考需要使用@DisallowConcurrentExecution 註解, 因爲爲了避免同一個Job的兩個實例同時執行時,數據可能會混亂的問題。
其他Jobs屬性
下面是能夠通過JobDetail對象定義一個job實例的其他屬性快速總結:
- Durability - 如果job非持久化,一旦job與任何活躍觸發器失去關聯則自動從調度程序中刪除。換句話說,非持久jobs有一個 生命週期,trigger存在是必須的。
- RequestsRecovery - 如果一個任務在"請求中恢復",它在調度程序硬關閉期間執行(例如在運行中崩潰,或者機器被關閉) , 接着當調度程序開始運行它會重新被執行。此時,JobExecutionContext.isRecovering()方法會返回true。
JobExecutionException
最後,我們需要告訴你一些Job.execute(..)方法的一些詳細信息。你可以從execute方法拋出唯一異常(包含運行時異常)是JobExecutionException。因此,你應該在使用execute方法的地方用'try-catch'包裹起來。你可以用些時間來看下JobExecutionException的文檔,你可以通過它知道如何處理這個異常。