http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-03.html
As you saw in Lesson 2, Jobs are rather easy to implement, having just a single ‘execute’ method in the interface. There are just a few more things that you need to understand about the nature of jobs, about the execute(..) method of the Job interface, and about JobDetails.
While a job class that you implement has the code that knows how do do the actual work of the particular type of job, Quartz needs to be informed about various attributes that you may wish an instance of that job to have. This is done via the JobDetail class, which was mentioned briefly in the previous section.
JobDetail instances are built using the JobBuilder class. You will typically want to use a static import of all of its methods, in order to have the DSL-feel within your code.
Jobs接口非常容易实现,只有一个execute方法。我们需要再学习一些知识去理解jobs的本质,Job接口的execute方法以及JobDetails接口。
当你实现Job接口类,Quartz需要你提供job实例的各种参数,Job接口实现类中的代码才知道如何去完成指定类型Job的具体工作。这个过程是通过JobDetail类来完成的,该类会在下一个章节作简要介绍。 JobDetail的实例是调用JobBuilder类创建的。通常你可以使用静态导入方式获得该类所有方法的调用,这样可以在你的代码体验DSL的感觉。
import static org.quartz.JobBuilder.*;
Let’s take a moment now to discuss a bit about the ‘nature’ of Jobs and the life-cycle of job instances within Quartz. First lets take a look back at some of that snippet of code we saw in Lesson 1:
现在我们花点时间来讨论一下关于 Jobs 的本质和 job 实例在 Quartz 中的生命周期。首先我们回顾一下第一课提及的代码片断:
// 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);
Now consider the job class “HelloJob” defined as such:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("Hello! HelloJob is executing.");
}
}
Notice that we give the scheduler a JobDetail instance, and that it knows the type of job to be executed by simply providing the job’s class as we build the JobDetail. Each (and every) time the scheduler executes the job, it creates a new instance of the class before calling its execute(..) method. When the execution is complete, references to the job class instance are dropped, and the instance is then garbage collected. One of the ramifications of this behavior is the fact that jobs must have a no-argument constructor (when using the default JobFactory implementation). Another ramification is that it does not make sense to have state data-fields defined on the job class - as their values would not be preserved between job executions.
You may now be wanting to ask “how can I provide properties/configuration for a Job instance?” and “how can I keep track of a job’s state between executions?” The answer to these questions are the same: the key is the JobDataMap, which is part of the JobDetail object.
JobDetail实例对象,我们构建JobDetail对象时仅提供了job的class对象,调度器就知道它要执行的job类型。每次调度器执行job时,它在调用excecute方法前会创建一个新的job实例。当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收。这种调用过程导致的其中一个结果是jobs对象必须要有一个无参数构造器(使用默认的JobFacotry实现时),另外一个结果jobs实现类不能定义状态数据字段,因为这些状态数据字段的值在调用job任务时不会被保留。
你现在可能想问“我要怎样才能为Job实例提供配置参数?在执行任务时我要如何跟踪job对象的状态?”这两个问题的答案都一样:使用JobDataMap,它是JobDetail对象的一部分。
The JobDataMap can be used to hold any amount of (serializable) data objects which you wish to have made available to the job instance when it executes. JobDataMap is an implementation of the Java Map interface, and has some added convenience methods for storing and retrieving data of primitive types.
Here’s some quick snippets of putting data into the JobDataMap while defining/building the JobDetail, prior to adding the job to the scheduler:
可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它。JobDataMap实现了JDK的Map接口,并且添加了一些非常方便的方法用来存取基本数据类型。
下面的代码片断演示了在定义/构建JobDetail对象时,job对象添加到调度器之前,如何将数据存放至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();
Here’s a quick example of getting data from the JobDataMap during the job’s execution:
下面的例子显示了在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);
}
}
If you use a persistent JobStore (discussed in the JobStore section of this tutorial) you should use some care in deciding what you place in the JobDataMap, because the object in it will be serialized, and they therefore become prone to class-versioning problems. Obviously standard Java types should be very safe, but beyond that, any time someone changes the definition of a class for which you have serialized instances, care has to be taken not to break compatibility. Optionally, you can put JDBC-JobStore and JobDataMap into a mode where only primitives and strings are allowed to be stored in the map, thus eliminating any possibility of later serialization problems.
setJobSays(String val)
Triggers can also have JobDataMaps associated with them. This can be useful in the case where you have a Job that is stored in the scheduler for regular/repeated use by multiple Triggers, yet with each independent triggering, you want to supply the Job with different data inputs.
The JobDataMap that is found on the JobExecutionContext during Job execution serves as a convenience. It is a merge of the JobDataMap found on the JobDetail and the one found on the Trigger, with the values in the latter overriding any same-named values in the former.
Here’s a quick example of getting data from the JobExecutionContext’s merged JobDataMap during the job’s execution:
如果你使用持久化的JobStore(将会在教程JobStore部分讨论),你应该要多考虑放在JobDataMap中的数据对象,因为此时的对象会被序列化,因此这更容易出现类版本问题。显然官方版本的类很安全,但是非官方的版本,任何时候有人变更你序例化实例的类的定义,都要注意不要破坏兼容性。更多关于此主题讨论的信息都能在Java Developer Connection 技术贴士中找到:Serialization In The Real World.当然,你可以选择将JDBC-JobStore和JobDataMap设计成只有基本数据类型和String类型才允许存储的map对象,从而从根本上消除序列化问题
如果你在job类中添加setter方法对应JobDataMap的键值(例如setJobSays(String val)方法对应上面例子里的jobSays数据),Quartz框架默认的JobFactory实现类在初始化job实例对象时会自动地调用这些setter方法,从而防止在调用执行方法时需要从map对象取指定的属性值。
触发器也可以关联JobDataMap对象,当存储在调度器中的job对象需要定期/重复执行,被多个触发器共用时,这种场景下使用JobDataMap将非常方便,然而每个独立的触发器,你都可以为job对象提供不同的输入参数。
在进行任务调度时JobDataMap存储在JobExecutionContext中非常方便获取。它整合了JobDetail和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);
}
}
Or if you wish to rely on the JobFactory “injecting” the data map values onto your class, it might look like this instead:
或者如果你想在类中依赖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;
}
}
You’ll notice that the overall code of the class is longer, but the code in the execute() method is cleaner. One could also argue that although the code is longer, that it actually took less coding, if the programmer’s IDE was used to auto-generate the setter methods, rather than having to hand-code the individual calls to retrieve the values from the JobDataMap. The choice is yours.
Many users spend time being confused about what exactly constitutes a “job instance”. We’ll try to clear that up here and in the section below about job state and concurrency.
You can create a single job class, and store many ‘instance definitions’ of it within the scheduler by creating multiple instances of JobDetails - each with its own set of properties and JobDataMap - and adding them all to the scheduler.
For example, you can create a class that implements the Job interface called “SalesReportJob”. The job might be coded to expect parameters sent to it (via the JobDataMap) to specify the name of the sales person that the sales report should be based on. They may then create multiple definitions (JobDetails) of the job, such as “SalesReportForJoe” and “SalesReportForMike” which have “joe” and “mike” specified in the corresponding JobDataMaps as input to the respective jobs.
When a trigger fires, the JobDetail (instance definition) it is associated to is loaded, and the job class it refers to is instantiated via the JobFactory configured on the Scheduler. The default JobFactory simply calls newInstance() on the job class, then attempts to call setter methods on the class that match the names of keys within the JobDataMap. You may want to create your own implementation of JobFactory to accomplish things such as having your application’s IoC or DI container produce/initialize the job instance.
In “Quartz speak”, we refer to each stored JobDetail as a “job definition” or “JobDetail instance”, and we refer to a each executing job as a “job instance” or “instance of a job definition”. Usually if we just use the word “job” we are referring to a named definition, or JobDetail. When we are referring to the class implementing the job interface, we usually use the term “job class”.
Now, some additional notes about a job’s state data (aka JobDataMap) and concurrency. There are a couple annotations that can be added to your Job class that affect Quartz’s behavior with respect to these aspects.
@DisallowConcurrentExecution is an annotation that can be added to the Job class that tells Quartz not to execute multiple instances of a given job definition (that refers to the given job class) concurrently. Notice the wording there, as it was chosen very carefully. In the example from the previous section, if “SalesReportJob” has this annotation, than only one instance of “SalesReportForJoe” can execute at a given time, but it can
@PersistJobDataAfterExecution is an annotation that can be added to the Job class that tells Quartz to update the stored copy of the JobDetail’s JobDataMap after the execute() method completes successfully (without throwing an exception), such that the next execution of the same job (JobDetail) receives the updated values rather than the originally stored values. Like the @DisallowConcurrentExecution
@PersistJobDataAfterExecution annotation, you should strongly consider also using the @DisallowConcurrentExecution
Here’s a quick summary of the other properties which can be defined for a job instance via the JobDetail object:
Durability - if a job is non-durable, it is automatically deleted from the scheduler once there are no longer any active triggers associated with it. In other words, non-durable jobs have a life span bounded by the existence of its triggers.
RequestsRecovery - if a job “requests recovery”, and it is executing during the time of a ‘hard shutdown’ of the scheduler (i.e. the process it is running within crashes, or the machine is shut off), then it is re-executed when the scheduler is started again. In this case, the JobExecutionContext.isRecovering() method will return true.
JobExecutionException
Finally, we need to inform you of a few details of the Job.execute(..) method. The only type of exception (including RuntimeExceptions) that you are allowed to throw from the execute method is the JobExecutionException. Because of this, you should generally wrap the entire contents of the execute method with a ‘try-catch’ block. You should also spend some time looking at the documentation for the JobExecutionException, as your job can use it to provide the scheduler various directives as to how you want the exception to be handled.
你可能会注意到类的整体代码比较长,但execute方法很简洁。有人会认为虽然代码比较长,如果程序员的集成开发平台(IDE)自动生成setter方法的话,可以编写更少的代码,而不必手工编写那些单独的调用方法从JobDataMap中取值。你可以自主选择编写代码的方式。
Job "Instances"
Job实例对象确切的结构是什么疑惑了很长时间,我们将尝试在这为大家解答,并且在下一个板块讲述Job状态和并发机制。
Job实现类,创建多个不同的JobDetails实例,将不同Job实例定义存储在调度器中,每个JobDetails实例都有各自的参数和JobDataMap,并且把这些JobDetails添加到调度器中。
Job接口的实现类,类名为“SalesReportJob”,Job类可以预先传入一些假想的参数(通过JobDataMap)来指定销售报表中业务员的名字。接下来创建多个Job实例的定义(即JobDetails),如“SalesReportForJoe”和“SalesReportForMike”通过“Joe”和“Mike”指定到相应的JobDataMaps中作为参数输入到各自的Job对象中。
JobDetail实例会被加载,通过在调度器中配置的JobFactory会将关联的Job类实例化,默认的JobFactory只是在Job类中调用newInstance方法,然后尝试调用匹配JobDataMap键值的setter方法。你可以开发自己的JobFactory实现类通过应用IOC或DI机制完成Job实例的创建和初始化。
用Quartz框架的话来说,我们将每个存储的JobDetail称为Job定义或JobDetail实例,将每个执行的作业任务(Job)称为Job实例或Job定义实例。通常我们只用“job”单词来对应命名的Job定义或是JobDetail。当我们指Job接口的实现类时,一般使用“job class”术语。
Job状态和并发机制
Job状态值和并发的额外信息。有一对加在Job类上面的注解,可以影响Quartz框架的这些方面的行为。
@DisallowConcurrentExecution注解添加到Job类中,会告知Quartz不要并发执行相同Job定义创建的多个实例对象。注意这里的措辞,要慎重地选择。引用上一章节的例子,如果SalesReportJob添加这个注解,在给定的时间段内只能执行一个SalesReportJobForJoe实例对象,但是可以并发执行一个SalesReportJobForMike实例。然而,在Quartz设计阶段决定在该类中携带注解,因为该注解会影响JobDetail类的编码。
@PersistJobDataAfterExecution注解添加到Job类中,会告知Quartz成功执行完execute方法后(有异常抛出的情况除外)更新JobDetail的JobDataMap中存储的数据。例如同一个JobDetail下一次执行时将接收更新的值而不是初始值。跟@DisallowConcurrentExecution注解类似,@PersistJobDataAfterExecution注解适用于Job定义实例,而不是Job类实例。只是该注解是附着在Job类的成员变量中,因为它不会影响整个类的编码(例如statefulness只需要在execute方法代码内正确使用即可)。
@PersistJobDataAfterExecution注解,强烈建议你也应该考虑使用@DisallowConcurrentExecution注解,为了避免当两个相同JobDetail实例并发执行时可能由于最后存储状态数据不一致导致执行混乱。
Jobs的其它属性
Job实例的其它属性,这些属性是通过JobDetail对象传递给Job实例的。
Durability-如果一个Job是非持久化的,一旦没有任何活跃的触发器关联这个Job实例时,这个实例会自动地从调度器中移除。换句话说,非持久化的jobs的生命周期是以存在的触发器为界限的。
RequestsRecovery-如果一个Job设置了请求恢复参数,并且在调度器强制关闭过程中恰好在执行(强制关闭的情况例如:运行的线程崩溃,或者服务器宕机),当调度器重启时,它会重新被执行。这种情况下,JobExecutionContext的isRecovery方法会返回true。
JobExecutioinException
最后,我们需要告知你Job.execute方法的一些细节。允许你从execute方法抛出的唯一一种异常类型是JobExecutionException(运行时异常除外,可以正常抛出),由于这个限制,你应该在execute方法内的try-catch代码块中包装好要处理的异常。你也可以花些时间查阅一下JobExecutionException的文档,便于你在开发的Job类中需要捕获处理异常时,为调度器提供各种信息。