Quartz学习笔记(二)Job、JobDetail、JobDataMap

Quartz学习笔记(二)Job、JobDetail、JobDataMap

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。
方式有两种

  1. 直接在构建JobDetail时通过JobBuilder的usingJobData方法将数据放入JobDataMap中。
    在这里插入图片描述

     ```
     //构建JobDetail实例
     JobDetail job = JobBuilder.newJob(HelloJob.class)
     		                  .withIdentity("helloJob", "hello")//给job命名并分组
     		                  .usingJobData("jobdd", "hello job")//通过JobBuilder的usingJobData方法给JobDataMap中塞
     		                  .build();
     ```
    
  2. 自己构建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
  • 13
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值