在实际的开发中,咱们常常须要向任务传递数据参数,在以前的任务建立中,咱们只能以 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,因此后来的值会覆盖原来的值。