原文地址: http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/tutorials/tutorial-lesson-03.html
第三课: Jobs 与 Job Details 更多相关
正如你在第二课所见,任务实现起来相当简单,仅仅有一个 'execute' 方法在接口。这里仅有少数东西你需要明白关于jobs的本质,关于Job 接口 execute(...) 方法,还有 JobDetails。
当你实现一个任务类,有知道如何做特定类型工作的代码,Quartz 需要了解关于各种你希望任务实例拥有的属性。这个就通过JobDetail完成,在上一节已经简单提过。
JobDetail 实例是通过 JobBuilder 类构建的。你通常可以用一个静态导入全部它的方法,为了让你的代码看起来有领域规范语言的感觉。
import static org.quartz.JobBuilder.*;
让我们花一点时间讨论一点 Jobs的本性和 job实例的在Quartz的生命周期。首先让我们回头看一眼我们在第一课的代码片段:
// 定义任务并绑定我们的 HelloJob 类
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// 触发任务立刻运行,并且妹40秒有也行
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// 告诉 quartz 执行调度使用的触发器
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.");
}
}
注意我们为任务调度指定一个JobDetail实例,并且在构建JobDetail时,只需提供作业的类即可执行的作业类型。每一次任务调度执行任务,它会在被调用 execute(...)方法前被创建新的实例。当执行结束,任务类实例的引用被丢弃,并且实例会被回收。这种行为的一个后果就是任务必要有一个无参构造器(当使用默认的 JobFactoy 实现时)。另一个结果就是在作业类上定义状态数据字段是没有意义的-他们的值不会在任务执行期间被保存。
你可能会想问“我怎样才能为一个任务实例保存属性/配置?” 还有 “我怎样才能跟踪一个任务的状态在执行之间?”他们的答案都是一样的,关键是JobDataMap, JobDetail对象的一部分。
JobDataMap
JobDataMap 可以用来持有任意数量(序列化)数据对象那些你在任务实例执时可用的。JobDataMap
是Java Map接口的一个实现,并且加了一些方便的方法用于存储和恢复原始类型的数据。
这里是关于在定义/构建 JobDetail 存放数据到 JobDataMap
的代码片段,在一个任务加入调度任务之前:
// 定义任务并且绑定到DumbJob类
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章节讨论过) 你应该谨慎决定放在 JobDataMap
的地方,因为对象会被序列化,因此,它们很容易出现类版本控制问题。显然,标准的Java类型应该是很安全的,但超过这个,任何时候某人改变一个你已经实现的类实例的定义,必须小心不要破坏兼容性。另一个选择是,你可以放 JDBC_JobStore 和 JobDataMap 进入只允许原语和字符串存储在映射中的模式,从而消除了以后出现序列化问题的可能性。
如果你增加对应于 JobDataMap中键名的 setter 方法到你的任务类(好像下面例子的 data 中 setJobSay(String val)) 方法,然后Qurartz 的默认 JobFactory 实例会自动调用他的 setter 在 Job 实例化后,因此,无需在你的execute方法内从映射中显式获取值。
触发器同样可以 JodDataMaps关联。这样在当你有一个任务被存储在调度程序中给多个触发器定期/重复使用的情况下很有用,然而,对于每个独立的触发,你都为任务提供不同的数据输入。
JobDataMap 在任务执行的时候,很方便在JobExeccutionContext找到。它是JobDetail上的JobDataMap和Trigger上的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 注入 数据Map值通过你自己类,它看起来会像这样:
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检索值。由你选择。
任务"实例"
很多用户花时间搞不清什么是任务实例。我们尝试在以下的章节里面说清楚任务状态和并发性。
你可以创建一个单独的任务类,并且存储多个“实例定义”通过创建JobDetails的多个实例在调度程序中-每个都有其自己的属性和JobDataMap-并将它们全部添加到调度程序中。
举例,你可以创建一个名字叫 "SalesReportJob"的任务实例。这个任务可能被期望的参数发送(好像 JobDataMap) 到指定销售报告所依据的销售人员的姓名。他们可能在任务创建多个定义(JobDetails) ,好像"SalesReportForJbe" 和 "SaleReportForMike" 用"joe" 和 "mike" 在相应的JobDataMaps中指定为各个作业的输入。
当一个触发器点燃,JobDetail(实例定义) 关联被加载,并且 任务类实例被 JobFactory 引用 配置到 执行调度。默认 JobFactory 调用 job 类的 newInstance() 方法,意图调用在类上与JobDataMap的键名匹配的方法。你可能想创建你自己的 JobFactory 实例去完成你应用的 IOC 或者 DI 容器 生产/初始化任务实例。
在 "Quartz speak" , 我们提及到每个存储的JobDetail 好像 "job definiton" 或 "JobDetail instance" ,然后我们提及到每一个执行的任务好似"job instance" 或者 "instance of a job definition"。通常,如果我们仅仅使用 “job”这个词,我们是提及一个命名定义,或者 JobDetail。当我们提及到 job 类实例,我们通常用术语 "job class"。
任务状态和并行
现在,一些关于任务状态数据(也就是 JobDataMap)和并行)的附加说明。这里是有一对注解可以在加在你的任务类上,影响 Quzrtz 的行为。
@DisallowConcurrentExecution 是一个可以加在任务类上的注解,告诉 Quartz不可以在指定的任务定义(这里指指定的任务类)并行执行多个实例。
注意这里的用词,是非常谨慎选择的。在上一节的例子,如果 "SalesReportJob" 有这个注解,那么仅有一个 "SalesReportForJoe"的实例可以在指定时间执行,但可以并行执行一个"SaleReportForMike"的实例。约束是基于实例定义(JobDetail),不是在任务的实例。然而,它是取决于(在Quartz设计期间)注解是否对本身进行了处理,因为它经常对类的编码方式产生影响。
@PersistJobDataAfterExecution 是一个可以加在任务类上的注解,告诉Quzrtz 在执行 execute() 方法成功后(没有抛出异常)更新JobDetail的 JobDataMap存储,这样下一次执行同样的任务(JobDetail) 接收到更新值而不是存储的值好像@DisallowConcurrentExcetion 一样,适用于一个任务的实例,不是一个任务类是实例,取决于任务类的属性因为它通常对类的编码方式产生影响。(好像"statefulness" 会需要明确的"understood"通过在execute 方法里的代码)。
如果你用@PersistJobDataAfterExecution 这个注解,你需要慎重考虑同时用
@DisallowConcurrentExecution 注解,为了避免一个相同的任务(JobDetail)并行时存储的数据导致可能的混乱(速度混乱)。
其他的任务属性
这里是一个快速总结定义一个任务实例即 JobDetail 对象的其他属性:
- 持久性 - 如果一个任务是非持久的,他会从任务调度执行一旦没有任何的触发器关联,非持久任务的生命周期绑定在触发器的存在。
- 可恢复性- 如果一个任务“可恢复”,并它执行的时候被“硬关闭”(也就是进程在跑的过程中奔溃,或者机器关机),那么当任务调度再次开始时,会重新执行。在这种情况下, JobExecutionContext.isRecovering() 方法返回 true。
JobExecutionException
最后,我们需要告知你 Job.execute(...)
的一些细节。唯一的一种异常类型(包含RuntimeEcxeption) 你可以从执行方法中抛出的异常是 JobExecutionException。 正是因为这样,你应该通常包装全部内容在“try-catch”块中。你还应该花点时间阅读文档中,关于JobExecutionExcetion的部分,为调度程序提供各种指令,以确定您希望如何处理异常。
本文由博客一文多发平台 OpenWrite 发布!
网友评论