介绍一下job的生命周期,每当trigger触发器出发的时候都会创建一个新的job实例,也就是在上一个博客中的HelloJob的实例每执行一次调度就会实例化一次,等实例化完成之后就会被垃圾回收。我们可以这样做一下实验,以验证这个:
public class HelloJob implements Job { static int i = 0; public HelloJob(){ i++; } public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("现在的i是: " + i); } }
然后我们执行定时任务后发现i是会不停的增长的,也就是说每一次执行都是调用了构造方法,创建了新的实例。
我们在上一博客中写了将JobDetail的组成是由job的,也就是执行的代码,但是我们传入的是一个.class文件,
JobDetail job = newJob(HelloJob.class).withIdentity("myJob", Scheduler.DEFAULT_GROUP).xxxx
很容易就会想到,一定是用的反射,也就是newInstance的方法,这个方法要求有一个无参数的构造方法。现在就存在这样的一个问题,如果我要求执行的job的execute方法中含有依赖于其他内容的代码呢,也就是说execute中执行的方法不是定死的,是在运行中产生的,因为采用的是反射,我们无法向job的实现类的的实例通过setter方法或者是构造方法传入值,所以这是一个问题。
那么如何解决呢,文档中给了我们答案:使用JobDataMap进行传值。
文档中给出的在创建JobDetail时使用JobDataMap进行传值的代码:
JobDetail job = newJob(DumbJob.class) .withIdentity("myJob", "group1") // name "myJob", group "group1" .usingJobData("jobSays", "Hello World!") .usingJobData("myFloatValue", 3.141f) .build();
我通过实验发现,也可以在创建完jobDetail之后设置,代码如下:
//创建job任务 JobDetail job = newJob(HelloJob.class).withIdentity("myJob", Scheduler.DEFAULT_GROUP).build(); job.getJobDataMap().put("name1", "jobDetail"); job.getJobDataMap().put("name2", "job2"); //使用的方法和map一样。
在Job的实现类中,我们这里是HelloJob,可以通过传入的参数JobExecutionContext获得JobDetail和Trigger,来自官网的代码:
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); }
如果我们在Job的实现类中设置了setter方法,那么在执行execute方法之前会先执行那些setter方法,但是执行的方法是和从JobDataMap相关的,之后在JobDataMap中存在的key的属性的setter方法才能执行,我们看官网的例子,他设置了jobSays、myFloatValue这两个属性,那么会执行setJobSays和setMyFloatValue这两个方法(如果这两个方法存在的话),但是不会执行其他setter方法。也就是说通过JobDataMap传入的值可以通过在Job中设置setter属性来传入到通过反射设置的job实例中,然后我们就可以通过在execute方法中调用调用getter方法来获取这些值了,这样就免去了必须从传入的JobExecutionContext中获取值了。
不光是是有JobDetail含有JobDataMap,在Trigger中也可以设置,设置的方法和JobDetail一样,我们也可以在JoBExecutionContext中获得设置的值。文档中还提到我们可以通过JobExecutionContext获得一个JobDataMap,这里面的值是合并了JobDetail中的map和Trigger中的map,后者会覆盖前者的值,如果key相同的话。通过这个Map我们就可以获得所有要传入的值了,获得方法:JobDataMap map = context.getMergedJobDataMap()
现在有个疑问,传入到execute方法中的参数是复制了一份参数呢,还是就是把原来放入到JobDataMap中参数不复制的传入呢?官网上的文档说的是复制一份,我们做一个实验:
如果不是复制的话,如果我们在execute方法中改变了JobDetail中的map,那么我们在主动调度中也能获得改变后的值,测试下能不能获得。代码如下:
import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.impl.StdScheduler; import org.quartz.impl.StdSchedulerFactory; import static org.quartz.TriggerBuilder.newTrigger; import job.CheckJobDataMapCopyOrNotJob; /** * 检查传入到job中的参数是不是复制 * @author wuguohua * */ public class CheckJobDataMapCopyOrNot { public static void main(String[] args) throws SchedulerException, InterruptedException { StdSchedulerFactory fac = new StdSchedulerFactory(); StdScheduler scheduler = (StdScheduler) fac.getScheduler(); scheduler.start(); JobDetail jobDetail = JobBuilder.newJob(CheckJobDataMapCopyOrNotJob.class) .withIdentity("CheckJobDataMapCopyOrNotJobDetail", Scheduler.DEFAULT_GROUP).build(); jobDetail.getJobDataMap().put("name", "1"); Trigger trigger = newTrigger().withIdentity("CheckJobDataMapCopyOrNotTrigger", Scheduler.DEFAULT_GROUP).startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(100,3)).build();//这个表示每3秒执行一次,一共执行100次。 trigger.getJobDataMap().put("age", 1); scheduler.scheduleJob(jobDetail, trigger); for(;true;){ System.out.println("xxxxx " + jobDetail.getJobDataMap().get("name") + " " + trigger.getJobDataMap().get("age")); Thread.sleep(1000*2); } } }
Job的代码:
import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class CheckJobDataMapCopyOrNotJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap map1 = context.getJobDetail().getJobDataMap(); JobDataMap map2 = context.getTrigger().getJobDataMap(); System.out.println("yyyy " + map1.get("name") + " " + map2.get("age")); map1.put("name", "2"); map2.put("age",2); } }
我们在job中改变了name和age的值,如果不是复制的话,在xxxx开头的打印中也会出现2才对。
结果如下:
xxxxx 1 1 yyyy 1 1 xxxxx 1 1 yyyy 1 1 xxxxx 1 1 yyyy 1 1 xxxxx 1 1 xxxxx 1 1 yyyy 1 1 xxxxx 1 1 .......
结果显示无论是xxxx还是yyyy都是打印的1,证明真的是将参数也复制了一份。
我证明这个原因是文档中提到了可以通过某个方法来实现在执行execute之后将改变的值回传给JobDetail中的JobDataMap,方法是在Job的实现类上加上这个注解:
@PersistJobDataAfterExecution,我在CcheckJobDataCopyOrNotJob加上之后,做的测试结果如下:
xxxxx 1 1 yyyy 1 1 xxxxx 1 1 yyyy 2 1 xxxxx 1 1 yyyy 2 1 xxxxx 1 1 xxxxx 1 1 yyyy 2 1 xxxxx 1 1 yyyy 2 1 xxxxx 1 1 xxxxx 1 1
结果证明了JobDetail中的map的确是被更新了,但是Trigger中的map并没有更新,文档中并没有提及Trigger也会被更新。
文档中还提及了一个概念,如果一个job的执行时间大于调度周期怎么办?也就是说如果完成一次调度需要2秒,而调度周期只有1秒,怎么办。quartz提供了可以配置的方案,也就是可以并发,也可以防止并发。默认情况下是可以并发的,可以通过在Job的实现类上加 @DisallowConcurrentExecution来实现。
我得测试代码如下: 调度类:
import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.impl.StdScheduler; import org.quartz.impl.StdSchedulerFactory; import job.HelloJob; public class Demo1 { public static void main(String[] args) throws InterruptedException { try { StdSchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); StdScheduler sched = (StdScheduler) schedFact.getScheduler(); //开始调度器 sched.start(); //创建job任务 JobDetail job = newJob(HelloJob.class).withIdentity("myJob", Scheduler.DEFAULT_GROUP).build(); //创建trigger,触发器 Trigger trigger = newTrigger().withIdentity("myTrigger",Scheduler.DEFAULT_GROUP).startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(100,3)).build();//3秒调度一次 //将任务和触发器绑定到调度器 sched.scheduleJob(job, trigger); Thread.sleep(1000000); sched.shutdown(); } catch (SchedulerException se) { se.printStackTrace(); } } }
工作类的execute方法:
public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("helloJob begin"); try { Thread.sleep(5*1000); //睡眠五秒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("helloJob end"); }
如果可以并发执行的话,那么一开始会出现两个"helloJob begin",因为5秒之后才会输出"helloJob end"。我们不加@DisallowConcurrentExecution时的结果如下:
helloJob begin helloJob begin helloJob end helloJob begin helloJob end helloJob begin helloJob end helloJob begin .....
加了之后的结果是:
helloJob begin helloJob end helloJob begin helloJob end helloJob begin helloJob end helloJob begin
并且执行的速度要慢得多。这里也验证了官网的文档。
官网还推荐,这两个一定要配合使用,当使用@persistJobDataAfterExecution时,一定要使用@DisallowConcurrentExecution,因为要修改JobDetail中的map,如果不加同步限制的话会造成对JobDetail的map的并发修改。