Quartz Scheduler 开发指南(1)
原文地址:http://www.quartz-scheduler.org/generated/2.2.2/html/qtz-all/
实例化调度程序(Instantiating the Scheduler)
在使用Scheduler调度程序前,它需要被实例化。你可以使用SchedulerFactory实现
一些Quartz用户通过使用JNDI中的Factory实例,还有一些直接使用Factory实例进行实例化(如下方的例子)
一旦Scheduler调度程序被实例化后, 它可以启动,保持准备状态,关闭。注意:一旦关闭了Scheduler,不再次实例化是不能重启的。Trigger不能被触发,直到Scheduler启动。Trigger同样不会触发,当Scheduler处于暂停状态
下方例子:实例化,启动一个Scheduler,并调度一个任务(Job)执行
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "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.java
public class HelloJob implements Job {
public static final Logger _log = LoggerFactory.getLogger(HelloJob.class);
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
_log.info("Hello World! - " + new Date() + "-" + jobExecutionContext.getJobDetail().getJobDataMap().get("key1"));
}
}
关键接口(Key Interfaces)
Quartz API中的关键接口
- Scheduler - 与调度程序交互的主要接口
- Job - 调度程序调度执行的任务需要实现这个接口
- JobDetail - 定义job实例
- Trigger - 调度程序执行哪个任务的触发条件
- JobBuilder - 用来定义/创建JobBuilder实例(指定任务实例)
- TriggerBuilder - 用来定义/创建 Trigger实例
一个调度程序(Scheduler)的生命周期受到他的创建(通过SchedulerFactory创建)和关闭方法(shutdown() Method)控制
一个创建好的调度程序可以用来添加(add)、移除(remove)、列举(list) 任务(jobs)和触发器(triggers),还可以执行其他调度相关的操作(例如暂停触发器)。但是调度程序(Scheduler)实际上不会进行任何触发(执行指定任务),直到它被开启(通过start()方法),就如上面的那个例子
实现上方的例子需要导入如下:
import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;
Jobs and Triggers
任务是一个类,需要实现Job接口。如下方展示的,这个接口只有一个方法
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
当一个任务的触发器触发时,调度程序的一个工作线程将调用Excute()方法。传递给该方法的参数JobExecutionContext 对象提供了任务实例以及该实例运行环境的信息,包括一个执行调度程序的调度处理、一个触发执行的触发器处理、任务的JobDetail对象,以及一些其他信息
在一个任务添加到调度程序中时,JobDetail对象被Quartz创建。这个对象包含很多对Job的属性设定和一个JobDataMap,JobDataMap可以储存给定类实例的状态信息。JobDetail 对象实际上是对Job实例的定义
Trigger对象用来触发任务的执行。当你希望调度一个Job时,你实例化一个触发器并调整它的特性来提供你想要的调度方式
Triggers也许也有一个JobDataMap和它绑定。JobDataMap传递一些特别的触发触发器的任务参数时特别有用。Quartz拥有很多不同类型的触发器,但最常用的类型是SimpleTrigger 和 CronTrigger
- SimpleTrigger十分方便---------你需要单次执行(在一个给定是时间单一的执行),或者你需要在一个给定的时间触发他,每隔一段时间触发一次,或者说触发N次
- CronTrigger十分方便 ---------- 基于日历触发,例如每星期五中午或每个月的第十天的10:15。
为什么同时拥有Job和Trigger?有的任务调度程序没有区分Job和Trigger的概念。一些定义Job是一个简单的执行时间带有一些小的标识,另一些则很像Quartz中Job和Trigger对象的结合。Quartz的设计创建了一个分离将时间进度表和根据该进度表执行的任务分离。这个设计有很多好处。
例如,你可以创建很多任务并把它们放在任务时间表中,独立于触发器。这允许你将相同的任务绑定在不同的触发器中。这种松耦合的另一个好处是在绑定的触发器过期后依旧在调度时间表中的任务可以再次被配置。这将允许你晚点调度它们,而不用再次定义它们。这也允许你修改或者代替触发器(Trigger)而不用重新定义绑定的任务(Job)
Jobs and JobDetails
Job接口实现很简单,只有一个excute()方法。但依旧有一些关于Job的特点,excute()方法,JobDetails需要你了解
当你实现的一个Job类在实际工作中需要执行一些特定类型的任务, Quarzt需要被告知Job实例拥有的很多属性,这个可以通过JobDetail实现。
JobDetail实例通过JobBuilder类创建,通常静态导入他的所有方法
import static org.quartz.JobBuilder.*;
下方的例子定义了一个Job类并执行。
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "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.java
public class HelloJob implements Job {
public static final Logger _log = LoggerFactory.getLogger(HelloJob.class);
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
_log.info("Hello World! - " + new Date() + "-" + jobExecutionContext.getJobDetail().getJobDataMap().get("key1"));
}
}
注意:我们给了Scheduler一个JobDetail实例,它知道将被执行的Job的类型,只要简单的提供Job的类当我们创建JobDetail时。每次Scheduler执行这个Job时,在调用Excute()前它将创建这个类的实例。当执行完成时,对Job类实例的引用将被丢弃,并且该实例会被垃圾收集(即丢弃)。
这一行为的后果是Jobs必须有一个无参的构造函数(使用默认的JobFactory实现)。另一个后果是Job类中没有定义数据,这些值是无用的,在Job执行时这些值不会被保存的
给job实例提供配置信息或者在Job执行时跟踪Job状态,可以使用JobDataMap,即JobDetail对象的一部分。
JobDataMap
JobDataMap可以用来保存任何数量的(序列化)数据对象,这些数据可以在任务实例执行时获得。JobDataMap是Java Map接口的实现,同时还添加了一些方便的方法,用于存储和检索原始数据类型。
下面是在定义创建JobDetail时将数据存入JobDataMap,这个优先于将Job添加到Scheduler中
// 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(在本教程的JobStore节讨论),你应该小心使用JobDataMap,考虑哪些数据要放在JobDataMap中。因为这里面的对象将会被序列化,因此容易产生类版本问题(原文:they therefore become prone to class-versioning problems.)。显然标准的Java类型应该是十分安全的, 但除此之外,任何时间一个人改变了你序列化实例的类的定义,你应该小心确保它的兼容性。或者,你可以把JDBC—JobStore和JobDataMap应用在一个模式中,在这个模式中只有基本类型和String可以被放在Map中,以此排除任何以后的序列化问题的可能性。
如果你在Job类中添加的Setter方法和JobDataMap中key的名字一样,那么Quartz的JobFactory实例将自动获取这些setter方法,在Job实例化时。
Trigger也可以有JobDataMap。这在某些场合十分有用。当你有一个Job在Scheduler中用多个触发器重复执行,根据不同的触发器,你希望给予Job不同的数据输入。
Job执行时通过JobExecutionContext 获取JobDataMap很方便。在Job中和Trigger中都定义了JobDataMap,那么这两个JobDataMap将会合并,对于相同的Key,值取后加载的。
eg1: Job执行时,从JobExecutionContext’s 中获取合并的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);
}
}
eg2: 通过JobFactory注入datamap 数据 可以是这样的
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;
}
}
Job实例(Job Instances)
你可以创建一个Job类,创建多个JobDetail实例存储多个该类的实例定义,每个JobDetail有他机子的属性和JobDataMap,并将它们都添加到Scheduler中。
例如:你可以创建一个类实现了Job接口,名叫SalesReportJob。这个任务希望通过JobDataMap来传递参数来识别销售的名字,销售报告需要基于这个名字。它们可能创建多个job的定义(JobDetail),例如SalesReportForJoe,SalesReportForMike,它们有“joe”和"Mike"在JobDataMap中来输入到各自的Job中。
当一个Trigger触发时,与他相关的JobDetail(Job的实例定义)将被载入,它相关的Job类将通过JobFactory实例化。默认的JobFactory将调用Job类的newInstance(),然后尝试调用Job类中setter方法(和JobDataMap中的key值相同)。你可以创建自定义JobFactory的实现来完成一些功能,例如应用的IoC
每个存储了的JobDetail被认为是Job定义或者JobDetail实例,每个执行Job认为是Job实例或者Job定义实例。通常当属于Job引用时,它通常是指一个Job的定义或者JobDetail。
Job State and Concurrency
有两个注释你可以添加到你的Job类中,这将影响Quartz的行为。
@DisallowConcurrentExecution - 有这个QUartz将不会同时执行多个给定Job定义的实例。在上一个例子中,如果SalesReportJob 有这个注解,那么在一个给定的时间,只有一个SalesReportForJoe 实例可以执行,但可以同时执行SalesReportForMike实例。约束是基于JobDetail,而不是Job类的实例。但注解是在Job类上的,因为行为的不同是Job类的代码造成的。
@PersistJobDataAfterExecution - Quartz在成功执行了execute()方法后,JobDetail中JobDataMap将被更新,下一次执行相同job时,使用的是更新后的数据。和@DisallowConcurrentExecution一样,注解也是定义在Job类上的。
如果你使用了@PersistJobDataAfterExecution注解,你应该考虑同时使用@DisallowConcurrentExecution注解,来避免当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。
Other Attributes Of Jobs
- Durability - 如果一个job是非持久的,它将在与它绑定的Scheduler不存在时同时不存在,也就是说他的生命周期是与它绑定的Trigger是相同的。
- RequestsRecovery - 当一个job在执行时意外关闭,那么job将再次执行当Scheduler再次启动时。
JobExecutionException
execute方法中仅允许抛出一种类型的异常(包括RuntimeExceptions),即JobExecutionException。因此,你应该将execute方法中的所有内容都放到一个”try-catch”块中。你也应该花点时间看看JobExecutionException的文档,因为你的job可以使用该异常告诉scheduler,你希望如何来处理发生的异常。