参考链接http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-03.html
看了前面的两篇,创建Jobs是非常简单的,只要implements Job
的execute()
方法即可。只需要理解Job的原理,Job interface
的execute()方法,还有JobDetail
类。
通过JobDetail类可以传递一些属性给你自己实现的Jobs instance, JobBuilder创建JobDetail instances
,通过下面的代码导入JobBuilder
的 static methods
:
import static org.quartz.JobBuilder.*;
调度器(scheduler)的生命周期是从其start到shutdown的一段时间,下面来看Jobs的本质以及quartz里的job instance
的生命周期。看代码:
// 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);
实现了execute()
方法的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类的时候提供了HelloJob.class
。scheduler在执行HelloJob的时候,会先创建一个HelloJob实例去执行execute()方法,当execute()方法执行完,对HellJob实例的引用也消失了,之后就等待垃圾回收机制去处理这个对象实例了。这样的后果是Jobs必须要由一个无参数的构造器(当使用默认的JobFactory去实现的时候);另外一个结果是在Job class里定义字段就没有意义了,因为在Job执行期间这些值不会被保存下来。
那怎样去给Job instance提供properties和configuration呢?
在Job执行期间,怎样去跟踪一个Job的state?为了解决这些问题,JobDetail里有一个厉害角色: JobDataMap
JobDataMap
当Job instances
在执行的时候可以获得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();
在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里放数据的时候就要十分小心了,因为数据会被serialized,就会存在类的版本的问题。标准的Java类型是安全的,如果某人改了你已经系列化好的类,那应该做些其他操作去保证兼容性。另外,可以让JDBC-JobStore
和JobDataMap
处于一个只能保存原生类型和字符串的模式,以预防序列化对象可能出现的类版本的问题。
如果在你实现的Job里有一个setter
方法,和JobDataMap的键名对应起来。比如对应上面的代码,在Job实例类里增加一个setJobSays(String val)
方法,quartz默认的JobFactory实现会在Job被初始化的时候自动的去调用那些setters方法,这样就不用在Job实现类的execute
方法中通过JobDataMap
去获取值了,可看下面的例子加深理解。
Triggers也有和他们绑定在一起的JobDataMaps,这对于在scheduler里的一个Job被多个Trigger绑定,这些Trigger里有定期、或重复的去触发Job的,每一个独立的Trigger要为自己绑定的Job提供不同的数据输入。
可以从JobExecutionContext
获取到JobDataMap
, 这个JobDataMap
是JobDetail和Trigger里的JobDataMap的并集,存在相同的键名的话,Trigger会覆盖JobDetail的。
下面来看一个在Job执行期间从JobExecutionContext
里获取 merged 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在实例化Job类时,将JobDataMap中的值给注入进我们的Job instances:
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();
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方法要比先前更cleaner了,所有的setter方法都可以通过IDE自动生成,不用手动编写代码从JobDataMap
去获取值,直接在execute()
中访问字段、获取被注入进字段中的值。
Job “Instances”
许多人很困惑一个Job instance
由什么组成,下面的内容是关于job state
和concurrency
的。
假如有现在创建了一个Job实例,由这个Job实例创建多个JobDetail instances,每个JobDetail设置各自的属性以及JobDataMap
, 然后把它们都扔到scheduler中。
举个栗子,创建一个job类去实现SalesReportJob
, 通过JobDataMap传递参数给这个Job, 指定比如销售商的名字、需要分析的销售报表。如此一来,我们就要通过JobDetails
去定义多个Job, 比如SalesReportForJoe
、SalesReportForMike
, 在它们各自的Job里通过JobDataMap传递joe
和mike
。
当Trigger触发Job时,在scheduler容器里,JobDetail instances 将被加载,具体的Job类被配置在scheduler中的JobFactory给实例化。默认的JobFactory只是简单的调用Job类的newInstance()
去创建实例,然后尝试去调用setter方法(和JobDataMap里的键名的对应)。可以创建自己的JobFactory实现,通过在系统应用里面的IOC或DI容器去生产和实例化Job。
关于上文名词的澄清,对于保存在scheduler中的中JobDetail, 行文中的描述是”job definition” 或者 “JobDetail instance”。当提到可执行的job使用的是”job instance”或者”instance of a job definition”。 使用比较多的job,指的是
name definition
或者“JobDetail”。实现了Job接口的类,通常使用job class
去描述。(这是官方文档的对于名词的解释,我这里文章的翻译可能有出入,大伙按着官网来理解就好)
Job State and Concurrency
开始叙述job的状态(aka JobDataMap)和并发了,可以在你的Job class上加一些注解去影响quartz的行为。
@DisallowConcurrentExecution
这个注解告诉quartz不要并发的去执行一个Job class的多个job 实例(quartz not to execute multiple instances of a given job definition (that refers to the given job class) concurrently)。
比如前面所说的SalesReportJob
类上有这个注解,那么只能是只有一个SalesReportJob 实例在特定的时间点执行, 不能是两个,但此时SalesReportForMike
可以并发的去执行。这是基于JobDetail实例的去实现的,而不是job class 实例。
@PersistJobDataAfterExecution
这个注解加在job class上告诉quartz在成功执行execute()方法后(没有抛出异常),更新保存在JobDetail的JobDataMap里的备份数据,这样在下次执行同一个job(JobDetail)时接收的是上一个Job更新后的值,而不是先前保存的值。跟@DisallowConcurrentExecution一样,是对JobDetail起作用的,不是job class instance。即使这个注解是定义在job class上的,但对于在编写job class类的代码时意义还是蛮大的,对于job中execute()
能有一个更直观的理解。
当使用@PersistJobDataAfterExecution
这个注解的时候,同时也应该使用@DisallowConcurrentExecution
这个注解,避免同一个job(JobDetail)的多个instances在并发的执行时从JobDataMap里获取数据时出现不一致。
Other Attributes Of Jobs
其他的一些通过JobDetail对象定义的属性,
Durability
如果一个Job不是持久化的,在没有活跃的Trigger和其绑定时,将会自动的从scheduler里被删除。换句话说,非持久化的Job的生命周期是依赖于Trigger的存在的。
RequestsRecovery
如果一个job “requests recovery”, 当job在执行的时候,scheduler被hard shutdown了(运行过程中崩溃,或者机器被shut off了),而后在scheduler重新启动后,job会继续执行。这样的话,
JobExecutionContext.isRecovering()
方法将会返回true。
JobExecutionException
来看Job.execute(..)
的一些细节吧,在execute()
方法中会抛出JobExecutionException
这个异常。由此,使用try-catch
将execute()方法里的代码 包裹,当出现异常后告诉scheduler如何进行后续处理。