Job
定义一个实现了Job的类,这个类表名job需要完成那些业务。
当一个Job被trigger被触发触发时,execute()方法会被scheduler的一个工作线程调用;传递给execute()方法的JobExecutionContext对象中保存着该job运行时的一些信息 ,比如执行job的scheduler的引用,触发job的trigger的引用,JobDetail对象引用,以及一些其它信息。
例如:
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println(context.getJobDetail().getKey());// JobDetail的key又他的name和group组成
System.err.println(context.getTrigger().getKey());// Trigger的key又他的name和group组成
System.err.println("hello,quartz");
}
}
hello.helloJob
hello.helloTrigger
hello,quartz
JobDetail
JobDetail对象是在将job加入scheduler时,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap。
在创建JobDetail时,我们需要将要执行的job的类名传给了JobDetail,这样scheduler就知道了要执行何种类型的job。
我们还可以给通过JobDetail给job设置name和group。
每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
JobDataMap
JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap。
方式有两种
-
直接在构建JobDetail时通过JobBuilder的usingJobData方法将数据放入JobDataMap中。
``` //构建JobDetail实例 JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("helloJob", "hello")//给job命名并分组 .usingJobData("jobdd", "hello job")//通过JobBuilder的usingJobData方法给JobDataMap中塞 .build(); ```
-
自己构建JobDataMap,然后塞入JobDetail中。或者从JobDetail中获取往里面put数据。
//构建JobDetail实例 JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("jobcc", "哈哈"); JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("helloJob", "hello")//给job命名并分组 .usingJobData("jobdd", "hello job")//通过JobBuilder的usingJobData方法给JobDataMap中塞 .usingJobData(jobDataMap) .build(); job.getJobDataMap().put("jobaa", "呵呵");
在job的执行过程中,可以从JobDataMap中取出数据。如
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println(context.getJobDetail().getKey());// JobDetail的key又他的name和group组成
System.err.println(context.getTrigger().getKey());// Trigger的key又他的name和group组成
System.err.println(context.getJobDetail().getJobDataMap().get("jobcc"));
System.err.println(context.getJobDetail().getJobDataMap().get("jobdd"));
System.err.println(context.getJobDetail().getJobDataMap().get("jobaa"));
System.err.println("hello,quartz");
}
}
hello.helloJob
hello.helloTrigger
哈哈
hello job
呵呵
hello,quartz
如果你在job类中,为JobDataMap中存储的数据的key增加set方法,那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。
public class HelloJob implements Job {
private String jobcc;
private String jobdd;
private String jobaa;
public void setJobcc(String jobcc) {
this.jobcc = jobcc;
}
public void setJobdd(String jobdd) {
this.jobdd = jobdd;
}
public void setJobaa(String jobaa) {
this.jobaa = jobaa;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println(context.getJobDetail().getKey());// JobDetail的key又他的name和group组成
System.err.println(context.getTrigger().getKey());// Trigger的key又他的name和group组成
System.err.println(jobcc);
System.err.println(jobdd);
System.err.println(jobaa);
System.err.println("hello,quartz");
}
}
hello.helloJob
hello.helloTrigger
哈哈
hello job
呵呵
hello,quartz
值得注意的是Trigger中也有一个JobDataMap,塞值和取值与JobDetail中的操作差不多。在Job执行时,JobExecutionContext中的JobDataMap虽然为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。----->通过测试,如果在job运行中,从JobExecutionContext分别获取JobDetail和Trigger,然后再分别获取它们中的JobDataMap,就算有相同的key,也是没有什么影响的,只有当调用getMergedJobDataMap()方法时才会获取它们的JobDataMap合并后的数据,或者用set方法获取也是获取到覆盖后的值
例如
public class HelloQuartz {
public static void main(String[] args) throws SchedulerException {
//构建SchedulerFactory实例
SchedulerFactory schedFact = new StdSchedulerFactory();
//获取Scheduler实例
Scheduler scheduler = schedFact.getScheduler();
//构建JobDetail实例
JobDataMap jobDataMap = new JobDataMap();
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("helloJob", "hello")
//通过JobBuilder的usingJobData方法给JobDetail的JobDataMap中塞值
.usingJobData("data", "hello jobDetail")
.usingJobData(jobDataMap)
.build();
//构建Trigger实例
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("helloTrigger", "hello")
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule().withIntervalInSeconds(5).repeatForever())
//通过JobBuilder的usingJobData方法给Trigger的JobDataMap中塞值
.usingJobData("data", "hello trigger")
.build();
//将JobDetail实例和Trigger实例加入到调度容器
scheduler.scheduleJob(job, trigger);
//启动容器
scheduler.start();
}
}
public class HelloJob implements Job {
private String data;
public void setData(String data) {
this.data = data;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println("通过set方法获取的data: " + data);
System.err.println("获取JobDetail的JobDataMap中的data:" + context.getJobDetail().getJobDataMap().get("data"));
System.err.println("获取Trigger的JobDataMap中的data:" + context.getTrigger().getJobDataMap().get("data"));
System.err.println("通过getMergedJobDataMap()方法获取到的data: " + context.getMergedJobDataMap().get("data"));
System.err.println("hello,quartz");
}
}
结果如下
20:59:32.871 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
20:59:32.873 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.0
20:59:32.884 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
20:59:32.885 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
20:59:32.890 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'hello.helloJob', class=com.quartz.test.HelloJob
20:59:32.896 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
20:59:32.896 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job hello.helloJob
通过set方法获取的data: hello trigger
获取JobDetail的JobDataMap中的data:hello jobDetail
获取Trigger的JobDataMap中的data:hello trigger
通过getMergedJobDataMap()方法获取到的data: hello trigger
hello,quartz
Job实例
关于Job实例,Quartz官方文档里面的描述如下
你可以只创建一个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类创建多个实例(JobDetail),然后这些实例采用不同的作业参数来运行。
如下例子
public class HelloQuartz {
public static void main(String[] args) throws SchedulerException {
//构建SchedulerFactory实例
SchedulerFactory schedFact = new StdSchedulerFactory();
//获取Scheduler实例
Scheduler scheduler = schedFact.getScheduler();
JobDetail jobA = JobBuilder.newJob(HelloJob.class)
.withIdentity("A", "hello_A")
.usingJobData("jobAdata", "hello A")
.build();
JobDetail jobB = JobBuilder.newJob(HelloJob.class)
.withIdentity("B", "hello_B")
.usingJobData("jobBdata", "hello B")
.build();
//构建Trigger实例
SimpleTrigger triggerA = TriggerBuilder.newTrigger()
.withIdentity("helloTriggerA", "helloA")
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
SimpleTrigger triggerB = TriggerBuilder.newTrigger()
.withIdentity("helloTriggerB", "helloB")
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule().withIntervalInSeconds(10).repeatForever())
.build();
//将JobDetail实例和Trigger实例加入到调度容器
scheduler.scheduleJob(jobA, triggerA);
scheduler.scheduleJob(jobB, triggerB);
//启动容器
scheduler.start();
}
}
job类
public class HelloJob implements Job {
private String jobAdata;
private String jobBdata;
public void setJobAdata(String jobAdata) {
this.jobAdata = jobAdata;
}
public void setJobBdata(String jobBdata) {
this.jobBdata = jobBdata;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println("当前运行的job实例的key:" + context.getJobDetail().getKey().toString());
System.err.println("jobA中的数据:" + jobAdata);
System.err.println("----------------------------------");
System.err.println("jobB中的数据:" + jobBdata);
System.err.println("hello,quartz");
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}
结果
当前运行的job实例的key:hello_A.A
jobA中的数据:hello A
----------------------------------
jobB中的数据:null
hello,quartz
当前运行的job实例的key:hello_B.B
jobA中的数据:null
----------------------------------
jobB中的数据:hello B
hello,quartz
09:21:02.178 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
09:21:02.178 [DefaultQuartzScheduler_Worker-7] DEBUG org.quartz.core.JobRunShell - Calling execute on job hello_A.A
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
进行以上测试的遇到一个坑
Exception in thread "main" org.quartz.SchedulerException: Trigger does not reference given job!
at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:838)
at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:249)
at com.quartz.test.HelloQuartz.main(HelloQuartz.java:44)
原因:多个Job绑定一个Trigger
关于Job与Trigger的关系:
一个Job可以有多个Trigger,但多个Job不能对应同一个Trigger
关于上诉说到的
只创建一个job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中
这会涉及到Job的并发,接下来我们就来了解Job状态与并发。
Job状态与并发
关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。该限制是针对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类,Quartz的建议是加上@DisallowConcurrentExecution注解,因为当有两个或多个有状态的 JobDetail 实例(针对的是同一个Job类,即JobBuilder.newJob(Class <? extends Job> jobClass) 方法中的参数是同一个)放入Scheduler 容器时,如果这时,你还建立了两个 Trigger 来触发这个 Job:一个每五分钟触发,另一个也是每五分钏触发。假如这两个 Trigger 试图在同一时刻触发 Job,框架是不允许这种事情发生的,因为这会有并发数据问题,JobDataMap中存储的数据很可能是不确定的。
Job的其它特性
通过JobDetail对象,可以给job实例配置的其它属性有:
- Durability:如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;
- RequestsRecovery:如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true