项目地址:
https://github.com/yuleiqq/quartz_example/tree/master/quartz_study
目录
JobDataMap
Job “Instances” (作业实例)
作业状态和并发性
Jobs 的其他属性
JobExecutionException
虽然您实现的作业类(实现Job 接口的实现类)的代码知道如何完成特定类型作业的实际工作,但是需要告知Quartz您可能希望该作业的实例具有的各种属性。这是通过JobDetail类完成的,在前一节中简单地提到过。
JobDetail实例是使用JobBuilder类构建的。您通常希望使用其所有方法的静态导入,以便在代码中具有DSL的感觉。
import static org.quartz.JobBuilder.*;
现在让我们花一点时间来讨论一下作业的“性质”和Quartz中作业实例的生命周期。首先让我们回顾一下我们在第一课中看到的一些代码片段:
// 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);
现在考虑这样定义的job类“HelloJob”:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("Hello! HelloJob is executing.");
}
}
现在考虑这样定义的作业类“HelloJob”:注意,我们给调度器一个JobDetail实例,它知道要执行的作业的类型,只需在构建JobDetail时提供作业的类.
注意,我们给调度器一个JobDetail实例,它知道要执行的作业的类型,只需在构建JobDetail时提供作业的类。每次调度程序执行作业时,它都会在调用其execute(..)方法之前创建一个类的新实例。执行完成后,将删除对作业类实例的引用,然后对实例进行垃圾收集。这种行为的后果之一是,作业必须有一个无参数构造函数(在使用默认的JobFactory实现时)。另一个分歧是,在作业类上定义状态数据字段没有意义——因为它们的值在作业执行期间不会保留。
您现在可能想问“如何为作业实例提供属性/配置?”以及“如何跟踪作业执行之间的状态?”这些问题的答案是相同的:关键是JobDataMap,它是JobDetail对象的一部分。
JobDataMap可用于保存任意数量的(可序列化的)数据对象,您希望在作业实例执行时将这些对象提供给作业实例。JobDataMap是Java Map接口的一个实现,它增加了一些方便的方法来存储和检索基本类型的数据。
在将作业添加到调度程序之前,在定义/构建JobDetail时,下面是一些将数据放入JobDataMap的快速片段:
// 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();
下面是一个在作业执行期间从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置于只允许原始类型和字符串存储在映射中的模式中,从而消除了以后出现序列化问题的可能性。
如果在job 类中添加setter 方法,名字和JobDataMap中的key相对应(如 setJobSays(String val) 方法在上面的示例中),然后当job被实例化,Quartz的默认JobFactory实现将自动调用这些setter,从而可以防止在你的execute方法中显示的获取map 的值.
触发器还可以与jobdatamap相关联。如果您的作业存储在调度器中,供多个触发器定期/重复使用,但是每个触发器都有独立的触发机制,您希望为作业提供不同的数据输入,那么这种方法非常有用。
作业执行期间在JobExecutionContext上找到的JobDataMap提供了一种便利。它是JobDetail上的JobDataMap和触发器上的JobDataMap的合并,后者中的值覆盖了前者中任何同名的值。
下面是一个在作业执行期间从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 类,并在调度器中存储它的许多“实例定义”,方法是创建多个JobDetails实例——每个实例都有自己的一组属性和JobDataMap——并将它们全部添加到调度器中。
例如,您可以创建一个实现名为“SalesReportJob”的作业接口的类。作业可能被编码为期望发送给它的参数(通过JobDataMap)来指定销售报告应该基于的销售人员的名称。然后,它们可以创建作业的多个定义(JobDetails),例如“SalesReportForJoe”和“SalesReportForMike”,它们将相应的JobDataMaps中指定的“joe”和“mike”作为各自作业的输入。
当触发器触发时,将加载与之关联的JobDetail(实例定义),并通过调度器上配置的JobFactory实例化它所引用的作业类。默认的JobFactory只是在job类上调用newInstance(),然后尝试调用与JobDataMap中的键名匹配的类的setter方法。您可能希望创建自己的JobFactory实现来完成一些事情,比如让应用程序的IoC或DI容器生成/初始化作业实例。
在“Quartz语言”中,我们将每个存储的JobDetail称为“作业定义”或“JobDetail实例”,并将每个正在执行的作业称为“作业实例”或“作业实例的定义”。通常,如果我们只使用“job”这个词,我们指的是一个命名定义或JobDetail。当我们引用实现job接口的类时,我们通常使用术语“作业类”。
现在,介绍一些关于作业状态数据(又称JobDataMap)和并发性的附加说明。可以将一些注解添加到您的Job类中,这些注释会影响Quartz在这些方面的行为。
@DisallowConcurrentExecution 是一个可以添加到作业类的注解,它告诉Quartz不要同时执行给定作业定义的多个实例(该实例引用给定的作业类)。
注意这里的措辞,因为它是经过精心挑选的。在上一节的示例中,如果“SalesReportJob”具有此注解,则在给定时间只能执行一个“SalesReportForJoe”实例,但它可以与“SalesReportForMike”实例并发执行。约束基于实例定义(JobDetail),而不是基于job类的实例。但是,决定(在Quartz的设计期间)在类本身上进行注解,因为注解通常会影响类的编码方式。
@PersistJobDataAfterExecution 是一个注解,可以添加到Job类告诉Quartz 当Job的execute 方法执行完成之后(没有抛出异常) , 更新JobDetail的JobDataMap的存储副本。这样下一个执行同样的工作(JobDetail)收到更新后的值而不是原先存储的值。像@DisallowConcurrentExecution注释, 这适用于Job实例定义, 而不是一个job 类的实例,虽然这是决定工作类的属性,因为它经常改变类是如何编码的(例如,“有状态性”需要显式地“理解”的代码在执行方法)。
如果使用@PersistJobDataAfterExecution注释,那么也应该考虑使用@DisallowConcurrentExecution注释,以避免在同时执行同一作业的两个实例(JobDetail)时,可能会混淆(竞态条件)所存储的数据。
下面是其他属性的快速总结,这些属性可以通过JobDetail对象为作业实例定义:
最后,我们需要通知您一些工作的细节。允许从execute方法抛出的惟一类型的异常(包括RuntimeExceptions)是JobExecutionException。因此,通常应该用“try-catch”块包装execute方法的全部内容。您还应该花一些时间查看JobExecutionException的文档,因为您的作业可以使用它来为调度器提供关于如何处理该异常的各种指令。