正如上篇文章所说的,Job很容易实现,只需要接口中唯一的execute方法。除此之外,你还需要稍微了解下Job、execute、Job interface和JobDetail的一些东西。
当你写的Job类执行特定任务时,Quartz需要知道这个类应该具有的各种属性。这就是前面所提到的JobDetail类完成的。
JobDetail实例是由JobBuilder创建的,可以这样引入:
import static org.quartz.JobBuilder.*;
让我们花一点时间来看看Job的原理和Quartz中Job的生命周期。首先看一下在第一篇文章中提及的一块代码片段:
// 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 class,它就知道知道所执行的任务类型。每当scheduler执行任务的时候,总是创建一个新的类实例,在执行其execute方法。执行完毕后,会释放对该实例的引用,稍后将会被垃圾回收。这种做法要求Job类必须有一个无参的构造方法,还有就是静态域是无效的,因为无法在多个执行实例中共享。
你可能想问,如何对Job实例配置属性呢?或者换句话说,如何跟踪多个实例的执行呢?答案是使用JobDataMap,它属于JobDetail对象的一部分。
JobDataMap
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,必须注意放入JobDataMap中的数据类型,因为这个对象会被序列化,可能会有版本问题。显而易见,java的基本类型是没问题的,但是如果是用户定义的类型发生了变化,而存在之前序列化的对象,就必须要考虑兼容性问题了。当然,也可以限定JDBC-JobStore和JobDataMap只能存储基本类型和String,这样就排除了序列化带来的潜在问题。
如果你在Job类中定义了与JobDataMap中key对应的setter方法,Quartz的就会在job实例化是调用这些setter方法,所以应避免定义类似的方法,以保证map中数据的准确性。
Triigers也有对应的JobDataMap。当定义一个任务,关联到多分数据、多个触发器,定期、重复执行时,JobDataMap会很有用。
JobDataMap定义在Job执行过程中的JobExecutionContext中,由JobDetail中的JobDataMap和Trigger中的JobDataMap合并而成,如果有重名的变量,前面的将会被后面的所覆盖。
下面是从合并后的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将data 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中的代码却变短了。有人会说虽然总代码量长,但是setter方法都可以用IDE自动生成,所以“手工”的代码实际是更短的,而且不必手动的从JobDataMap中取数据。怎么做,你决定!
Job “Instances”
很多用户花很长时间纠结于是谁创建了job实例。这里我们将就任务的状态和并发等问题,弄个明白。
你可以定义一个job类,然后创建多个JobDetail实例,每个实例都有自己的属性和JobDataMap,然后把它们关联到一个scheduler。
例如,你可以创建一个实现了Job interface的类“SalesReportJob”,这个job需要根据传进来的销售人员的姓名来确定销售报告。接着需要定义多个JobDetail,例如“SalesReportForJoe” and “SalesReportForMike”,并将“joe” and “mike”存入个子的JobDataMap中。
当触发器启动是,与之关联的JobDetail将会被加载,对应的job class会通过scheduler中配置的JobFactory实例化。默认的JobFactory会调用job class的newInstance方法,然后根据JobDataMap中的key值调用对应的setter方法进行变量赋值。你可能想创建自己的JobFactory实现来完成注入自己的IoC(控制反转)或者DI(依赖注入)。
在“Quartz speak”中,我们将存储的JobDetail看做“job definition”或者“JobDetail 实例”。通常来说,提到“job”,我们指的是一个具体的实现,即JobDetail;提到实现job interface的类,通常指的是“job class”。
Job State and Concurrency
接下来再看看任务的状态数据和并发问题。以下是Job类中可以使用的注解,这些注解会影响Quartz的行为。
@DisallowConcurrentExecution 添加到Job class,Quartz就不会并发的执行多个任务实例。
注意这里的用词。在前面的例子中,如果“SalesReportJob”添加了这个注解,那么同时只会有一个“SalesReportForJoe”实例运行,但这并不影响并发的执行“SalesReportForJoe”任务实例。这个约束是在JobDetail维度的,不是在Job class维度。
@PersistJobDataAfterExecution 添加了这个注解的job class,在执行完JobDetail后,会将其JobDataMap备份,以便于下次执行是使用,而不是使用默认的初始化数据。和第一个一样,这个约束也是在JobDetail维度,而不是Job class维度。
如果你使用@PersistJobDataAfterExecution注解,你需要慎重考虑是否要使用@DisallowConcurrentExecution注解,以避免当一个JobDetail并发执行时数据是否会相互影响。
Other Attributes Of Jobs
下面是其他可以通过JobDetail对象定义给job实例的属性归类:
- Durability - 如果job是非持久的,与其相关的所有trigger触发执行完成之后将会自动删除,换计划说,所有job都是以与其相关的trigger结束而结束的。
- RequestsRecovery - 如果job是“请求恢复”的,如果任务执行过程中schduler强制关闭了,那么当scheduler重新启动时,它将再次执行。在这种情况下JobExecutionContext.isRecovering()将返回TRUE;
JobExecutionException
最后,我们需要讲下Job.execute(..)的细节。在这个方法内部唯一可以排除的异常是JobExecutionException。鉴于此,可能会抛出的异常都应该使用tru/catch捕获处理。你也因该进一步看下JobExecutionException相关文档,以便更好的处理。
原文链接:
http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-03.html