原文连接 | 译文连接 | 翻译:nkcoder | 校对:html
本系列教程由quartz-2.2.x官方文档翻译、整理而来,但愿给一样对quartz感兴趣的朋友一些参考和帮助,有任何不当或错误之处,欢迎指正;有兴趣研究源码的同窗,能够参考我对quartz-core源码的注释(进行中)。java
正如在教程二中讲到的,Job实现起来很容易,该接口只有一个“execute”方法。本节主要关注:Job的特色、Job接口的execute方法以及JobDetail。git
你定义了一个实现Job接口的类,这个类仅仅代表该job须要完成什么类型的任务,除此以外,Quartz还须要知道该Job实例所包含的属性;这将由JobDetail类来完成。github
JobDetail实例是经过JobBuilder类建立的,导入该类下的全部静态方法,会让你编码时有DSL的感受:编程
import static org.quartz.JobBuilder.*;
让咱们先看看Job的特征(nature)以及Job实例的生命期。不妨先回头看看教程一中的代码片断:api
// 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."); } }
能够看到,咱们传给scheduler一个JobDetail实例,由于咱们在建立JobDetail时,将要执行的job的类名传给了JobDetail,因此scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法以前会建立该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另外一个后果是,在job类中,不该该定义有状态的数据属性,由于在job的屡次执行中,这些属性的值不会保留。并发
那么如何给job实例增长属性或配置呢?如何在job的屡次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。less
JobDataMap
JobDataMap中能够包含不限量的(序列化的)数据对象,在job实例执行的时候,可使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增长了一些便于存取基本类型的数据的方法。函数
将job加入到scheduler以前,在构建JobDetail时,能够将数据放入JobDataMap,以下示例:
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部分会讲到),在决定JobDataMap中存放什么数据的时候须要当心,由于JobDataMap中存储的对象都会被序列化,所以极可能会致使类的版本不一致的问题;Java的标准类型都很安全,若是你已经有了一个类的序列化后的实例,某个时候,别人修改了该类的定义,此时你须要确保对类的修改没有破坏兼容性;更多细节,参考现实中的序列化问题。另外,你也能够配置JDBC-JobStore和JobDataMap,使得map中仅容许存储基本类型和String类型的数据,这样能够避免后续的序列化问题。
若是你在job类中,为JobDataMap中存储的数据的key增长set方法(如在上面示例中,增长setJobSays(String val)方法),那么Quartz的默认JobFactory实如今job被实例化的时候会自动调用这些set方法,这样你就不须要在execute()方法中显式地从map中取数据了。
在Job执行时,JobExecutionContext中的JobDataMap为咱们提供了不少的便利。它是JobDetail中的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实现数据的自动“注入”,则示例代码为:
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中获取数据了,因此你实际须要编写的代码更少了。当前,如何选择,由你决定。
Job实例
不少用户对于Job实例到底由什么构成感到很迷惑。咱们在这里解释一下,并在接下来的小节介绍job状态和并发。
你能够只建立一个job类,而后建立多个与该job关联的JobDetail实例,每个实例都有本身的属性集和JobDataMap,最后,将全部的实例都加到scheduler中。
好比,你建立了一个实现Job接口的类“SalesReportJob”。该job须要一个参数(经过JobdataMap传入),表示负责该销售报告的销售员的名字。所以,你能够建立该job的多个实例(JobDetail),好比“SalesReportForJoe”、“SalesReportForMike”,将“joe”和“mike”做为JobDataMap的数据传给对应的job实例。
当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类经过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,而后尝试调用JobDataMap中的key的setter方法。你也能够建立本身的JobFactory实现,好比让你的IOC或DI容器能够建立/初始化job实例。
在Quartz的描述语言中,咱们将保存后的JobDetail称为“job定义”或者“JobDetail实例”,将一个正在执行的job称为“job实例”或者“job定义的实例”。当咱们使用“job”时,通常指代的是job定义,或者JobDetail;当咱们提到实现Job接口的类时,一般使用“job类”。
Job状态与并发
关于job的状态数据(即JobDataMap)和并发性,还有一些地方须要注意。在job类上能够加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来讲,若是“SalesReportJob”类上有该注解,则同一时刻仅容许执行一个“SalesReportForJoe”实例,但能够并发地执行“SalesReportForMike”类的一个实例。因此该限制是针对JobDetail的,而不是job类的。可是咱们认为(在设计Quartz的时候)应该将该注解放在job类上,由于job类的改变常常会致使其行为发生变化。
@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解同样,尽管注解是加在job类上的,但其限制做用是针对job实例的,而不是job类的。由job类来承载注解,是由于job类的内容常常会影响其行为状态(好比,job类的execute方法须要显式地“理解”其”状态“)。
若是你使用了@PersistJobDataAfterExecution注解,咱们强烈建议你同时使用@DisallowConcurrentExecution注解,由于当同一个job(JobDetail)的两个实例被并发执行时,因为竞争,JobDataMap中存储的数据极可能是不肯定的。
Job的其它特性
经过JobDetail对象,能够给job实例配置的其它属性有:
- Durability:若是一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;
- RequestsRecovery:若是一个job是可恢复的,而且在其执行的时候,scheduler发生硬关闭(hard shutdown)(好比运行的进程崩溃了,或者关机了),则当scheduler从新启动的时候,该job会被从新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。
JobExecutionException
最后,是关于Job.execute(..)方法的一些额外细节。execute方法中仅容许抛出一种类型的异常(包括RuntimeExceptions),即JobExecutionException。所以,你应该将execute方法中的全部内容都放到一个”try-catch”块中。你也应该花点时间看看JobExecutionException的文档,由于你的job可使用该异常告诉scheduler,你但愿如何来处理发生的异常。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文连接地址: Quartz教程三:Job与JobDetail介绍