在實際的開發中,咱們常常須要向任務傳遞數據參數,在以前的任務建立中,咱們只能以 JobBuilder.newJob(DataJob.class) 的形式向建造器傳遞一個 class,實際上 JobDetail 接口規定了一個方法 getJobDataMap(),用於傳遞數據。 shell
經過閱讀 JobDataMap 的源碼,咱們發現它是一個使用 String 做爲 key 的 Map 的具體實現,同時具備一個 isDirty 字段。它具備 getCharacterFromString() 等方法,方法原理就是從 Map 中獲取 Object,而且強制轉換到 Character 類型,其餘方法也相似。請注意其父類的泛型類別:安全
若是 Job 在執行任務中須要獲取數據,天然是從惟一的運行方法參數 JobExecutionContext 中獲取,JobExecutionContext 的接口規定了 getJobDetail() 方法以獲取 JobDetail,而且在 JobDetail 中存在一個獲取 JobDataMap 的方法 getJobDataMap(), 按照這個思路,咱們天然會想到數據的傳遞很大多是在 JobBuilder 中完成的(在後邊的分析中,咱們發如今 Trigger 中也能夠傳遞,固然這些都是後話)app
usingJobData 方法有多種簽名,有 usingJobData(String dataKey, Boolean value),usingJobData(String dataKey, Double value),usingJobData(JobDataMap newJobDataMap) 不等,咱們先查看其中一個方法ide
各類方法中的 dataKey 字段是 String 類型的,對應 DirtyFlagMap<String, Object> 的第一個泛型類型。測試
實際上 jobDataMap 字段就是 JobDataMap 類,該方法將數據傳進去 jobDataMap 中,並在 build() 方法中將字段 jobDataMap 的值設置給了 JobDetail 中的 jobDataMap:ui
根據上邊的分析,咱們能夠簡單的寫出下邊的測試類:this
1 /** 2 * @author pancc 3 * @version 1.0 4 */ 5 public class DataJobDemo { 6 7 public static void main(String[] args) throws SchedulerException, InterruptedException { 8 JobDetail detail = JobBuilder.newJob(DataJob.class) 9 .withIdentity("data", "group0") 10 .usingJobData("data", "hello") 11 .build(); 12 13 Trigger trigger = TriggerBuilder.newTrigger() 14 .withIdentity("data_trigger") 15 .startNow() 16 .build(); 17 18 Scheduler scheduler = new StdSchedulerFactory().getScheduler(); 19 20 scheduler.start(); 21 scheduler.scheduleJob(detail, trigger); 22 /* 23 * 2 秒鐘後關閉 24 */ 25 Thread.sleep(2_000); 26 scheduler.shutdown(); 27 } 28 29 public static class DataJob implements Job { 30 31 @Override 32 public void execute(JobExecutionContext context) { 33 String data = context.getJobDetail().getJobDataMap().getString("data"); 34 System.out.printf("get data {%s} from map\n", data); 35 36 } 37 } 38 }
這個程序任務成功的打印了以下語句:spa
根據官方描述,在 JobDataMap 中設置的值,會自動映射到 Job 類的字段中,這裏有一個隱性要求,字段與 setter 方法必須遵循 JavaBean 規範。線程
此次咱們不能像上邊同樣經過簡單查看源碼就能理解其中的設計,讓咱們經過一個實例,而且打斷點來查看它的自動注入魔法。debug
咱們規定 Job 有一個 name 字段,而且符合 JavaBean 規範,而且咱們向 JobDetail 傳遞一個 dataKey 爲 name 的值:
1 /** 2 * @author pancc 3 * @version 1.0 4 */ 5 public class InjectDataDemo { 6 7 public static void main(String[] args) throws SchedulerException, InterruptedException { 8 JobDetail detail = JobBuilder.newJob(InjectData.class) 9 .withIdentity("inject", "group0") 10 .usingJobData("name", "Alex") 11 .build(); 12 13 Trigger trigger = TriggerBuilder.newTrigger() 14 .withIdentity("inject_trigger") 15 .startNow() 16 .build(); 17 18 Scheduler scheduler = new StdSchedulerFactory().getScheduler(); 19 20 scheduler.start(); 21 scheduler.scheduleJob(detail, trigger); 22 /* 23 * 2 秒鐘後關閉 24 */ 25 Thread.sleep(2_000); 26 scheduler.shutdown(); 27 } 28 29 public static class InjectData implements Job { 30 31 private String name; 32 33 public void setName(String name) { 34 this.name = name; 35 } 36 37 @Override 38 public void execute(JobExecutionContext context) throws JobExecutionException { 39 System.out.printf("hello, my name is %s \n", name); 40 } 41 } 42 43 }
查看控制檯,咱們成功在 name 屬性獲取到了值:
如今,在 this.name = name; 前打上斷點,咱們經過 debug 的調用棧來查看實際的過程。
分別有四個入口,這是
在第四個入口,咱們經過 debug 的 variables 視圖,能夠觀察到此時 它不斷嘗試從 jobDataMap 獲取 key,嘗試使用 datakey 的值在 InjectData 類中查找對應的 setter 方法,而且在實例上調用該 setter 方法設置值:
在 Trigger 的構建中,咱們還觀察到了與 JobBuilder 一樣的方法 usingJobData ,若是此時往 Trigger 傳遞進去 重複的值與新的字段,會如何?在編寫測試代碼以前,咱們回到上點分析中的 PropertySettingJobFactory 類中,仔細觀察這個方法:
與 DirtyFlagMap 中的 Map 類型:
所以,咱們的數據就像在合併兩個 HashMap 同樣,重複的鍵值對會發生覆蓋,新的值覆蓋舊的,不衝突的則保留
1 /** 2 * @author pancc 3 * @version 1.0 4 */ 5 public class DuplicatedDataDemo { 6 7 public static void main(String[] args) throws SchedulerException, InterruptedException { 8 JobDetail detail = JobBuilder.newJob(DuplicatedData.class) 9 .withIdentity("inject", "group0") 10 .usingJobData("name", "Alex") 11 .build(); 12 13 Trigger trigger = TriggerBuilder.newTrigger() 14 .withIdentity("inject_trigger") 15 .usingJobData("name", "Alice") 16 .usingJobData("age",50) 17 .startNow() 18 .build(); 19 20 Scheduler scheduler = new StdSchedulerFactory().getScheduler(); 21 22 scheduler.start(); 23 scheduler.scheduleJob(detail, trigger); 24 /* 25 * 2 秒鐘後關閉 26 */ 27 Thread.sleep(2_000); 28 scheduler.shutdown(); 29 } 30 31 public static class DuplicatedData implements Job { 32 33 private String name; 34 35 private Integer age; 36 37 public void setName(String name) { 38 this.name = name; 39 } 40 41 public void setAge(Integer age) { 42 this.age = age; 43 } 44 45 @Override 46 public void execute(JobExecutionContext context) throws JobExecutionException { 47 System.out.printf("hello, my name is %s , my age is %d \n", name, age); 48 } 49 } 50 }
能夠觀察到咱們在 JonDetail 中設置的 name 被 Trigger 中的替代掉了,新的由 Trigger 持有的 age 值正確的傳遞到了 age 屬性中:
繼續上邊的代碼,讓咱們在 execute 增長如下語句並打上斷點:
讓咱們步入 org.quartz.core.JobRunShell#initialize 方法,這裏根據 Scheduler,從 JobStore(此時是 RAMJobStore)獲取到的 TriggerFiredBundle 實例與 方法內實例化的 Job 實例建立了一個 JobExecutionContext:
所以,當咱們想要檢測被覆蓋的原有數據,能夠用如下語句:
1 @Override 2 public void execute(JobExecutionContext context) throws JobExecutionException { 3 JobDataMap map = context.getJobDetail().getJobDataMap(); 4 JobDataMap mapMerged = context.getMergedJobDataMap(); 5 List<Map.Entry<String, Object>> duplicates = mapMerged.entrySet().stream().filter(en -> map.getWrappedMap().containsKey(en.getKey())).collect(Collectors.toList()); 6 System.out.printf("hello, my name is %s , my age is %d \n", name, age); 7 }
類型安全性:在喚起對應字段的 setter 方法時,Quartz 經過類檢查會保證數據的類型安全。
不可序列化錯誤:在喚起對應字段的 setter 方法時,Quartz 還檢查了 setter 對應的參數類型是否爲基本類型(Primitive),若是是則會報錯。
數據覆蓋:因爲 JobDataMap 底層本質上使用 HashMap,因此後來的值會覆蓋原來的值。