Quartz指南
Quartz是一个作业调度系统(ajob scheduling system),Quartz不但可以集成到其他的软件系统中,而且也可以独立运行的;在本文中“job scheduler”的意思是:一个负责在约定的时间到达时执行(或通知)其他软件控件的方法。
Quartz是非常灵活的,为了实现我们的需求Quartz包含了许多可以独立或被集成使用的典型范例,同时使我们编写项目中的代码也觉得很简单自然(natural)。
Quartz是很轻量级的,只需要简单的安装或配置就可以在项目中使用;如果只是相对简单的使用实际上可以直接地使用(out-of-the-box)。
Quartz具有容错性,能够持久化所调度的作业,即在系统重启后也可以继续执行原先未完成的作业。
虽然Quartz对给定的计划可以简单地运行一些系统的处理是很适用的,但只有当我们学习如何使用Quartz去驱动我们的应用业务处理流程时,才能真正的认识到Quartz的全部潜能。
Quartz是以.jar文件的形式发布的,Quartz java库中包含了Quartz所有的核心功能,该功能的主要接口(API)是Scheduler接口,Scheduler接口提供了一些简单的操作,如:schedulering/unschedulingjobs,starting/stopping/pausingthe scheduler。
如果我们要调度自定义的作业,该作业必须实现Job接口,Job接口包含了一个必须实现的方法:execute(…);如果我们需要实现当到达被调度的时间能够得到通知,我们必须实现TriggerListener或JobListener接口。
Quartz主要程序不但能够作为独立的应用(带有RMI 接口)运行,也可以作为J2EE组件资源在J2EE应用服务器运行。
使用Scheduler前必须实例化Scheduler,需要由SchedulerFactory类来创建Scheduler,Factory的实例可以通过在JNDI存储中的Factory的序列化的方式来获取,实例化Factory后直接使用该实例也是很容易的,如下面的例子。
Scheduler被实例化后就可以启动、暂停或关闭,但是Scheduler一旦关闭就不能再次启动该Scheduler,只有必须再次实例化后才可以;Trigger只有与之对应的Scheduler启动后才能触发与之相关的Job,否则一直处于暂停状态。
下面是一个实例化、启动Scheduler和调度执行作业的例子:
//创建一个SchedulerFactory类的实例
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
//创建一个Scheduler类的实例
Scheduler sched = schedFact.getScheduler();
//启动上面创建的Scheduler
sched.start();
//创建一个JobDetail
JobDetail jobDetail = new JobDetail("myJob",
sched.DEFAULT_GROUP,
DumbJob.class);
//创建一个SimpleTrigger
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
new Date(),
null,
0,
0L);
//把JobDetail和SimpleTrigger所创建的各自实例关联到一个Scheduler中
sched.scheduleJob(jobDetail, trigger);
上面已经提及到了,我们可以使Scheduler简单地执行一个实现了Job接口的Java组件,Job接口定义如下:
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
当Job的Trigger执行触发后,Scheduler将调用执行execute(…)方法,JobExecutionContext对象是该方法的参数,为Job实例提供了一个执行该Job的Scheduler的handle、一个触发该execute方法的Trigger的handle、Job的JobDetail对象和一些其他运行时的环境变量值。
JobDetail对象是在Quartz的客户端(我们的程序)在Job被关联加入到Scheduler时创建的,JobDetail包含了Job的各种属性值和JobDataMap,JobDataMap中存放了与之相关的Job类实例的状态信息。
Trigger对象是用于触发Job的执行,为了调度一个作业,我们需要实例化一个Trigger,并根据作业的需求设置该Trigger的属性,Trigger有两种:SimpleTrigger和CronTrigger。
SimpleTrigger只适用于在规定的时刻触发Job的执行,或在规定的期限内按一定的时间间隔重复触发Job的执行;当我们需要基于日期类型(如:每星期5中午或每个月的第10天10:15)的作业调度时,需要用到CronTrigger。
在Quartz中Job与Trigger之间是相互独立的,可以对Job和Trigger分别进行各自的定义设置,然后根据需要进行组合,这样就可以实现一个Job可以与许多不同的Trigger相关联,一个Trigger也可以关联到不同的Job上,达到了松散耦合的目的,当需要更改一个Job的Trigger时,只需要修改或替换对应的Trigger就可以了,而无须重新定义一个与该Job关联的Scheduler。
注册进QuartzScheduler中的Job和Trigger是通过name来标识的,为了后期的维护,Job和Trigger能够按类划分为group,在一个group中每个Job和Trigger的name必须为唯一的,即Job和Trigger的标识是由各自的name+group组成的。
Jobs的实现相对来说比较容易,只需要理解Job的性质、Job接口中的execute(…)方法和JobDetails。
我们真正需要实现的类实际上是Job的内容,通过JobDetail类向Quartz传递有关该Job的各种属性值。
接下来介绍有关Job的性质和Job在Quartz中的生命周期,以下是前面提到的代码:
JobDetail jobDetail = new JobDetail("myJob", // job name
sched.DEFAULT_GROUP, // job group
DumbJob.class); // the java class to execute
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
new Date(),
null,
0,
0L);
sched.scheduleJob(jobDetail, trigger);
有关DumbJob.class的代码如下:
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("DumbJob is executing.");
}
}
可以看到我们向Scheduler的实例sched中加入了JobDetail类的实例jobDetail,同时在jobDetail中注入了我们需要执行的类DumbJob.class。sched每次在调用执行execute(…)方法前都会对所调用的类DumbJob.class创建一个新的实例,这样就使得Job必须有一个没有参数的构造函数,并且在Job类中定义的成员变量每次都会被初始化。
哪我们如何为Job类提供属性或配置信息呢?如何跟踪两个执行任务之间的状态呢?Quartz提供了JobDataMap来实现,JobDataMap是JobDetail对象的一部分。
在Job实例运行期间对应的JobDataMap中能够容纳任意多的对象,JobDataMap是对Java Map接口的实现,并增加一些对于原始类型存储和检索的方法。
下列是向JobDataMap对象存储数据的一些代码:
jobDetail.getJobDataMap().put("jobSays", "Hello World!");
jobDetail.getJobDataMap().put("myFloatValue", 3.141f);
jobDetail.getJobDataMap().put("myStateData", new ArrayList());
下列是在Job执行过程中获取JobDataMap有关数据的例子:
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException
{
String instName = context.getJobDetail().getName();//获取Job实例的name
String instGroup = context.getJobDetail().getGroup();//获取Job实例的group
JobDataMap dataMap = context.getJobDetail().getJobDataMap();//获取JobDataMap对象
String jobSays = dataMap.getString("jobSays");//获取JobDataMap中对象jobSays的值
float myFloatValue = dataMap.getFloat("myFloatValue");//获取JobDataMap中对象myFloatValue的值
ArrayList state = (ArrayList)dataMap.get("myStateData");
state.add(new Date());//获取JobDataMap中对象myStateData的值
System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
}
}
这部分介绍Job的状态,即有关JobDataMap内容,Job实例可以分为:有状态和无状态;无状态Job只在注册进Scheduler时才有JobDataMap数据,无状态Job运行结束后不保存JobDataMap数据,这样意味着在无状态Job运行过程中修改过的JobDataMap数据在无状态Job再次运行时将无法得到;而有状态Job与之相反,有状态Job每次运行结束时将存储有状态Job的JobDataMap数据,但是有状态Job为了保证JobDataMap的数据的一致性,有状态Job无法并行运行,当Trigger需要再次触发执行一个正在运行的Job时,Trigger会自动的延迟触发,直到正在运行的Job运行结束后才再次触发该Job。
我们需要有状态Job时必须实现StatefulJob接口,而不是上面的例子中的Job接口。
以下对Job实例的其他属性进行介绍:
l 持久性(Durability):如果Job是非持久性的,一旦没有活动的Trigger与之相关联时,该Job会自动从Scheduler中删除掉;
l 易挥发性(Volatility):如果Job是易挥发的,当与之相关联的Schuduler停止之后,不会通过JobStore对该Job进行保存;
l 可恢复性(RequestsRecovery):如果Job是可恢复的,该Job运行期间与之关联的Scheduler非正常停止(由于进程停止或机器关闭等引起)时,当该Scheduler再次启动时,该Job会重新执行一次;
l JobListener:一个Job可以被关联到一个或多个JobListener,当该Job执行时,与之关联的JobListener会得到通知;
Execute方法只允许有一种类型的异常(包括RuntimeException)可以抛出,该异常就是JobExecutionException;因此我们必须把execute方法的所有内容放在try-catch语句中。当需要了解如何处理异常时,我们还必须阅读有关JobExecutionException的JavaDoc文档。
与Job相比,Trigger相对来说比较简单容易,但是要能完全的掌握使用Quartz,使其包含各种自定义的时间表选项,我们必须先知道和理解Trigger。
Quartz Calendar 对象(不是java.util.Calendar对象)能够与被注册进Scheduler的Trigger关联。Calendar对排除Trigger的时间段是很有用的,例如,我们可以创建一个在每个工作日上午9:30触发Job的Trigger,就在其中增加一个排除所有工作假期的Calendar。
Calendar可以是任何实现Calendar接口的可序列化对象,如下所示:
package org.quartz;
public interface Calendar {
public boolean isTimeIncluded(long timeStamp);
public long getNextIncludedTime(long timeStamp);
}
该接口的参数都是long类型的,以毫秒为单位的timestamp;所以Calendar定义的时间可以准确到毫秒。很可能,我们需要排除一整天,为了方便,Quartz包含了实现该功能的类ori.quartz.impl.HolidayCalendar。
Calendar必须实例化并且通过addCalendar(…)方法注册到Scheduler中,如果使用HolidayCalendar,在实例化之后,我们需要使用方法addExcludedDate(Date date)排除我们计划日程中不需要的日期,一个Calendar实例可以用到不同的Trigger中,比如:
HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
sched.addCalendar("myHolidays", cal, false);
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
new Date(),
null,
SimpleTrigger.REPEAT_INDEFINITELY,
60L * 1000L);
trigger.setCalendarName("myHolidays");
// .. schedule job with trigger
SimpleTrigger trigger2 = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
new Date(),
null,
5,
5L * 24L * 60L * 60L * 1000L);
trigger2.setCalendarName("myHolidays");
// .. schedule job with trigger2
上面代码,我们创建了两个Trigger:一个是每1分钟执行触发一次,没有次数限制;另一个是每5天执行触发一次,共执行5次触发;然而,任何在Calendar中被排除的时间段的触发执行都将被取消。
Trigger另一个重要的属性是“Misfire Instruction”。过时触发发生在持久Trigger失去了触发时间,由于对应的Scheduler被关闭的原因;不同的Trigger类型有不同的过时触发指令,默认情况下是使用“smart policy”指令,该指令根据Trigger类型和配置具有动态的行为。当scheduler启动时,它将搜索所有过时触发的持久Trigger,并同时根据它们各自所配置的过时触发指令进行更新;当我们在项目中要使用Quartz时,我们必须熟悉所要使用的Trigger类型的过时触发指示,在对应的JavaDOC中有对其进行说明。可以通过方法setMisfireInstruction(…)来设置Trigger的过时触发指示。
提供了方便,可以使我们不TriggerUtils类(在org.quartz.helpers包中)为我们创建Trigger和触发时间用java.util.Calendar对象。使用辅助类能够很容易地创建基于每分钟、小时、天、周和月等触发的触发器,也可以创建各种各样的日期――对设置触发器的启动时间很有用。
Trigger能够像Job一样,可以把监听器注册到Trigger中,实现了接口TriggerListener的对象就可以接收到Trigger触发时的通知。
当我们需要在规定的时间执行一次或在规定的时间段以一定的时间间隔重复触发执行Job时,SimpleTrigger就可以满足上述要求。
SimpleTrigger的属性有:开始时间、结束时间、重复次数和重复的时间间隔,重复次数属性的值可以为0、正整数、或常量SimpleTrigger.REPEAT_INDEFINITELY,重复的时间间隔属性值必须为0或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为0时,意味着与Trigger同时触发执行(或几乎与Scheduler开始时同时触发执行)。
如果有指定结束时间属性值,则结束时间属性优先于重复次数属性,这样的好处在于:当我们需要创建一个每间隔10秒钟触发一次直到指定的结束时间的Trigger,而无需去计算从开始到结束的所重复的次数,我们只需简单的指定结束时间和使用REPEAT_INDEFINITELY作为重复次数的属性值即可(我们也可以指定一个比在指定结束时间到达时实际执行次数大的重复次数)。
SimpleTrigger有几个不同的构造方法,我们只对下面这个进行分析:
public SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval)
SimpleTrigger例1――创建一个在当前之后10秒钟触发的,执行一次的Trigger
long startTime = System.currentTimeMillis() + 10000L;
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
new Date(startTime),
null,
0,
0L);
SimpleTrigger例2――创建一个立即触发的,并每间隔60秒钟重复触发执行一次的Trigger
SimpleTrigger trigger = newSimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
newDate(),
null,
SimpleTrigger.REPEAT_INDEFINITELY,
60L* 1000L);
SimpleTrigger例3――创建一个立即触发,每间隔10秒钟重复触发执行一次,开始时间为当前,结束时间为40秒钟后的Trigger
long endTime = System.currentTimeMillis() + 40000L;
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
new Date(),
new Date(endTime),
SimpleTrigger.REPEAT_INDEFINITELY,
10L * 1000L);
SimpleTrigger例4――创建一个在2005年5月8日早上10:30触发的,每间隔30秒钟重复触发执行一次,并且重复执行5次(总共触发执行6次)的Trigger
java.util.Calendar cal = new java.util.GregorianCalendar(2005, cal.MAY, 8);
cal.set(cal.HOUR, 10);
cal.set(cal.MINUTE, 30);
cal.set(cal.SECOND, 0);
cal.set(cal.MILLISECOND, 0);
Data startTime = cal.getTime()
SimpleTrigger trigger = new SimpleTrigger("myTrigger",
sched.DEFAULT_GROUP,
startTime,
null,
5,
30L * 1000L);
SimpleTrigger有几个用于当过时触发发生时向Quartz通知如何执行的指令,这些指令作为常量定义在SimpleTrigger类中,分别如下:
MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
我们也可以使用前面讨论的Trigger.MISFIRE_INSTRUCTION_SMART_POLICY,该指令是所有Trigger的默认值。
如果使用“smart policy”,SimpleTrigger将会根据实例中的配置进行动态的选择过时触发指令,JavaDOC中的SimpleTrigger.updateAfterMisfire()方法详细解析了动态选择的内容。
CronTrigger的功能比SimpleTrigger强大,它可以实现基于类似于日历概念的作业调度,而不是单单基于一定的时间间隔的。
使用CronTrigger,我们能够定义如下类型的日历型的Schedule:每个星期五的中午,每个周末的早上9:30,或者每周一、周三和周五的早上9:00到10:00每隔5分钟。
Cron-Expressions用于配置CronTrigger实例,Cron-Expressions是一串字符串,实际上它由六个子字符串组成,子字符串间采用空格分离,从左到右分别代表:Seconds Minutes Hours Day-of-Month Month Day-of-Week。
字符串“0 0 12 ? * WED”是一个完整的Cron-Expressions例子,它所表达的意思是:每周三早上12:00。
各个子表达式的值可以是一个范围或者列表,比如,上个例子中的Day-of-Week域的值“WED”可以用“MON-FRI”、“MON,WED,FRI”或者“MON-WED,SAT”来替代。
所有子表达式都有指定各自的取值范围,下面对Cron-Expressions的各个子表达式和取值范围进行说明:
子表达式 |
允许的值 |
允许的特殊字符 |
Seconds |
0-59 |
- * / |
Minutes |
0-59 |
- * / |
Hours |
0-23 |
- * / |
Day-of-Month |
1-31 |
- * ? / L W |
Month |
1-12或JAN-DEC |
- * / |
Day-of-Week |
1-7或SUN-SAT |
- * ? / L # |
Years(Optional) |
为空或1970-2099 |
- * / |
‘-’字符表示:值的范围,10-12在Hours域中表示为:10、11和12;
‘*’字符表示:可以为任意值,‘*’在Minutes域中表示为:每分钟;
‘/’字符表示:一个左边的数值是右边基数的递增值,‘0/15’在Seconds域中表示为:第0、15、30和45秒,‘5/15’在Seconds域中表示为:第5、20、35和50;
‘?’字符表示:没有对该域指定值,可以为任意值,不对该域进行限制,‘?’只能用在Day-of-Month和Day-of-Week域中;
‘L’字符表示:‘L’是取‘Last’的第一个字母,也只能用在Day-of-Month和Day-of-Week域中,在Day-of-Month域中的意思是:月份的最后一天,在Day-of-Week域中的意思是:‘7’或‘SAT’,但是在Day-of-Week域中用在另一个值后的意思是:该月的最后一个星期几,如:‘6L’为:该月的最后一个星期五;
‘W’字符表示:‘W’是取‘Weekday’的第一个字母,只能用在Day-of-Month域中,‘W’代表了最接近给定那一天的工作日,如:在Day-of-Month域中把值设为:15W,表示的意思是:该月的第15日最接近的工作日,所以当第15日是周六时,Trigger将在第14日的周五执行触发,当第15日是周天时,Trigger将在第16日的周一执行触发,当第15日是工作日时,Trigger将在第15日的当天执行触发;然而在Day-of-Month域中把值设为:1W,当第1日是周六时,Trigger将在第3日的周一执行触发,因为它不会跨越月份的范围,‘W’字符只能是该月中的一天,而不是一个范围或天数的列表;在Day-of-Month域中可以结合使用‘L’和‘W’字符‘LW’,‘LW’表示:月份的最后一个工作日;
‘#’字符表示:月份的第几个星期几,只能用在Day-of-Week域中,如:在Day-of-Week域中把值设为:6#3,表示的意思是:该月的第3个星期5(day6=Friday and “#3”=the 3rd one in the month);2#1表示的意思是:该月的第1个星期1;4#5表示的意思是:该月的第5个星期3;需要注意:不能把值设为“#5”,因为一个月中不可能有第5个星期3,如果这样设置,将导致该月无法执行触发;
下面举一些完整的Cron- Expressions例子:
表达式 |
意思 |
0 0 12 * * ? |
每天12:00执行触发 |
0 15 10 ? * * |
每天10:15执行触发 |
0 15 10 * * ? |
每天10:15执行触发 |
0 15 10 * * ? * |
每天10:15执行触发 |
0 15 10 * * ? 2005 |
2005年的每天10:15执行触发 |
0 * 14 * * ? |
每天从14:00到14:59每隔1分钟执行一次触发 |
0 0/5 14 * * ? |
每天从14:00到14:59每个5分钟执行一次触发 |
0 0/5 14,18 * * ? |
每天从14:00到14:59和18:00到18:59每隔5分钟执行一次触发 |
0 0-5 14 * * ? |
每天从14:00到14:05每隔1分钟执行一次触发 |
0 10,44 14 ? 3 WED |
3月的每个星期3的14:10和14:44分别执行一次触发 |
0 15 10 15 * ? |
每月的第15日10:15执行一次触发 |
0 15 10 L * ? |
每月最后一天的10:15执行一次触发 |
0 15 10 ? * 6L |
每月的最后一个星期5的10:15执行一次触发 |
0 15 10 ? * 2002-2005 |
2002、2003、2004、2005年的每个月的最后一个星期5的10:15执行一次触发 |
0 15 10 ? * 6#3 |
每月的第3个星期5的10:15执行一次触发 |
当Schedule中要求比较复杂,采用单个的Trigger无法实现时,可以配置多个的Trigger来实现,比如:每天的9:00到10:00之间每隔5分钟执行一次触发,并且在每天的13:00到22:00之间每隔20分钟执行一次触发,配置2个Trigger并把2配置好的2个Trigger注册到相同的Job中就可以很简单的实现上述情况。
Listener是我们创建用于监听Scheduler中关于事件发生情况的对象,其中TriggerListener接收涉及Trigger事件的情况,JobListener接收涉及Job事件的情况。
l Trigger的事件: Trigger触发、Trigger过时触发和Trigger触发的完成。
l Job的事件:Job的执行通知和Job的完成通知。
要创建Listener,只需简单的实现org.quartz.TriggerListener或org.quartz.JobListener接口,接着运行时把Listener注册到Scheduler中即可,在注册过程中需要为创建的Listener命名,Listener可以注册为全局的和非全局的,全局的Listener接收所有Trigger/Job的事件,非全局的Listener只接收指定设置的Trigger/Job事件,把JobListener注册到Scheduler的方法如下:scheduler.addGlobalJobListener(myJobListener)或scheduler.addJobListener(myJobListener);
SchedulerListener与TriggerListener和JobListener类似,SchedulerListener只接收涉及Scheduler自己的事件通知,而与指定设置的Trigger/Job无关;
有关Scheduler的事件包括:Job/Trigger的增加、Job/Trigger的删除、Scheduler中一系列严重的事件和Scheduler的关闭等事件;
SchedulerListener的创建和注册与其他类型的Listener很相似,但是它没有区分全局和非全局的情况,只要实现了org.quartz.SchedulerListener接口的对象都是。
JobStore负责跟踪我们对Scheduler进行配置的有关Job、Trigger和Calendar等信息的数据;选择适当的JobStore对我们的Quartz Scheduler实例是很重要的,幸运的是,当我们理解了不同类型JobStore时,就可以很容易地对JobStore进行选择,我们可以通过对SchedulerFactory创建Scheduler实例所引用的配置文件中的属性进行声明,采用哪种JobStore。
RAMJobStore是使用JobStore最简单的一种方式,它也是性能最高效的,顾名思义,JobStore是把它的数据都存储在RAM中,这也是它的快速和简单配置的原因;RAMJobStore的缺点是:当我们的应用系统因为系统的崩溃而丢失了所有有关Scheduler的信息时,采用RAMJobStore方式将无法恢复原先Scheduler中有关Job和Trigger的设置;对一些应用来说,这是可以接收的,但是对其他一些应用来说,这就不是所期望的。
JDBCJobStore也是一种相当有名的JobStore,它通过JDBC把数据都保存到数据库中,所以在配置上会比RAMJobStore复杂一些,而且不像RAMJobStore那么快,但是当我们对数据库中的表的主键创建索引时,性能上的缺点就不是很关键的了。
JDBCJobStore几乎可以连接所有的数据库,广泛使用的数据库基本上有Oracle、MySQL、MS SQLServer 2000、HSQLDB、PostreSQL和DB2。使用JDBCStore,我们必须首选创建一系列的数据库表,为了提供给Quartz使用;我们可以在docs/dbTables目录中找到创建表SQL脚本文件,需要注意的是:在这些脚本中所创建的表都以“QRTZ_”为表名的前缀,如:QRTZ_TRIGGERS和QRTZ_JOB_DETAIL;我们可以对表名的前缀进行任意的修改,只要配置文件中有关前缀的属性值与实际的数据库表的前缀一致即可;采用前缀的方式可以在同一个数据库中保存多个Scheduler实例的数据,通过不同的前缀进行区分;
我们一旦完成了数据库表的创建,在我们配置和启用JDBCJobStore之前,需要决定在我们的应用中采用哪一种类型的事务;如果我们在执行Scheduling命令(如:增加和删除Trigger)时不需要关联到其他的事务中,我们就可以采用JobStoreTX的方式让Quartz管理这个事务,一般情况下都是采用JobStoreTX的方式来管理事务的。
如果我们应用中需要Quartz与其他事务一起运行(如:J2EE应用服务器),就需要采用JobStoreCMT的事务管理方式,这样Quartz将会让应用服务器容器管理事务;
最后一个难题是JDBCJobStore中有关连接数据库的DataSource的设置,Quartz属性中包含了Datasource的几种配置方法,一种是采用Quartz创建和管理DataSource的方式,提供了数据库连接的所有信息;另一种是Quartz使用应用服务器(Quartz运行当中的应用服务器)管理的DataSource,通过采用提供JDBCJobStore的JNDI名字的方式;有关配置属性的详细信息,可以参考“docs/config”目录中的配置文件。
为了使用JDBCJobStore,我们首选需要设置Quartz配置文件中JobStore类属性值为org.quartz.impl.jdbcjobstore.JobStoreTX或为org.quartz.impl.jdbcjobstore.JobStoreCMT,具体设为哪个值可根据我们的应用的需要及结合上面对JobStoreTX和JobStoreCMT的解析;比如:org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX;
下一步我们需要JobStore的DriverDelegate属性,DriverDelegate是负责执行所有与JDBC有关的处理,需要选择与指定数据库对应的Delegate;StdJDBCDelegate是使用一般的JDBC代码(和SQL语句)来执行处理的Delegate,当我们所使用的数据库没有对应的delegate时,可以试着使用StdJDBCDelegate;在org.quartz.impl.jdbcjobstore包或子包中可以找到其他的delegate,其他的delegate大概包含:DB2v6Delegate(用于DB2version 6和更低version的)、HSQLDBDelegate(用于HSQLDB)、MSSQLDelegate(用于MicrosoftSQLServer 2000)、PostgreSQLDelegate(用于PostgreSQL 7.x)、WeblogicDelegate(用于使用Weblogic的JDBC驱动程序)和OracleDelegate(用于Oracle8i和9i);
一旦选定delegate后,需要对driverDelegateClass属性进行设置:org.quartz.jobStore.driverDelegate =org.quartz.impl.jdbcjobstore.StdJDBCDelegate;
接着我们需要向JobStore声明数据库表名的前缀,通过tablePrefix进行设置:org.quartz.jobStore.tablePrefix = QRTZ_;
最后我们需要设置JobStore将使用哪个DataSource,在Quartz属性配置文件中也需要定义DataSource属性,如:org.quartz.jobStore.dataSource= myDS。
Spring提供了支持时序调度(Scheduling)的整合类.现在,时序调度器通过FactoryBean建立,保持着可选的对Timers或者Triggers的引用。更进一步的, 对于Quartz Scheduler和Timer两者存在一个方便的类允许我们调用目标对象(类似于通常的MethodInvokingFactoryBeans)上的某个方法。
JobDetail 对象包括了运行一个job所需要的所有信息。 于是Spring提供了一个所谓的JobDetailBean使得JobDetail拥有了一个真实的,有意义的默认值。让我们来看个例子:
<bean name="exampleJob"class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>example.ExampleJob</value>
</property>
<propertyname="jobDataAsMap">
<map>
<entrykey="timeout"><value>5</value></entry>
</map>
</property>
</bean>
Job detail bean拥有所有运行job(ExampleJob)的必要信息。通过job的data map来制定timeout。Job的data map可以通过JobExecutionContext(在运行时刻传递给你)来得到, 但是JobDetailBean也把从job的data map中得到的属性映射到实际job中的属性中去。 所以,如果ExampleJob中包含一个名为timeout的属性,JobDetailBean将自动为它赋值:
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob isinstantiated
* with the value from theJobDetailBean (5)
*/
public void setTimeout(inttimeout) {
this.timeout = timeout;
}
protected voidexecuteInternal(JobExecutionContext ctx)
throws JobExecutionException {
// do the actual work
}
}
所有Job detail bean中的一些其他的设定对你来说也是可以同样设置的.
注意:使用name和group属性,你可以修改job在哪一个组下运行和使用什么名称。 默认情况下,job的名称等于job detai bean的名称(在上面的例子中为exampleJob)。
通常情况下,你只需要调用特定对象上的一个方法。你可以使用MethodInvokingJobDetailFactoryBean准确的做到这一点:
<bean id="methodInvokingJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<propertyname="targetObject"><refbean="exampleBusinessObject"/></property>
<propertyname="targetMethod"><value>doIt</value></property>
</bean>
上面例子将导致exampleBusinessObject中的doIt方法被调用(如下):
public class BusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject"class="examples.ExampleBusinessObject"/>
使用MethodInvokingJobDetailFactoryBean你不需要创建只有一行代码且只调用一个方法的job,你只需要创建真实的业务对象来包装具体的细节的对象。
默认情况下,Quartz Jobs是无状态的,可能导致jobs之间互相的影响。如果你为相同的JobDetail指定两个触发器, 很可能当第一个job完成之前,第二个job就开始了。如果JobDetail对象实现了Stateful接口,就不会发生这样的事情。 第二个job将不会在第一个job完成之前开始。为了使得jobs不并发运行,设置MethodInvokingJobDetailFactoryBean中的concurrent标记为false。
<bean id="methodInvokingJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject"><refbean="exampleBusinessObject"/></property>
<propertyname="targetMethod"><value>doIt</value></property>
</bean>
注意:默认情况下,jobs在并行的方式下运行。
我们已经创建了job details,jobs。我们回顾了允许你调用特定对象上某一个方法的便捷的bean。当然我们仍需要调度这些jobs。这需要使用triggers和SchedulerFactoryBean来完成。 Quartz自带一些可供使用的triggers。Spring提供两个子类triggers,分别为CronTriggerBean和SimpleTriggerBean。
Triggers也需要被调度。Spring提供SchedulerFactoryBean来暴露一些属性来设置triggers。SchedulerFactoryBean负责调度那些实际的triggers。
两个例子:
<bean id="simpleTrigger"class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<propertyname="jobDetail">
<!-- see the example ofmethod invoking job above -->
<refbean="methodInvokingJobDetail"/>
</property>
<propertyname="startDelay">
<!-- 10 seconds -->
<value>10000</value>
</property>
<propertyname="repeatInterval">
<!-- repeat every 50 seconds-->
<value>50000</value>
</property>
</bean>
<bean id="cronTrigger"class="org.springframework.scheduling.quartz.CronTriggerBean">
<propertyname="jobDetail">
<refbean="exampleJob"/>
</property>
<propertyname="cronExpression">
<!-- run every morning at 6 am -->
<value>0 6 * *1</value>
</property>
</bean>
现在我们创建了两个triggers,其中一个开始延迟10秒以后每50秒运行一次,另一个每天早上6点钟运行。 我们需要创建一个SchedulerFactoryBean来最终实现上述的一切:
beanclass="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<propertyname="triggers">
<list>
<reflocal="cronTrigger"/>
<reflocal="simpleTrigger"/>
</list>
</property>
</bean>
更多的一些属性可以通过SchedulerFactoryBean来设置,例如job details使用的Calendars,用来订制Quartz的一些属性以及其它。可以看相应的JavaDOC(http://www.springframework.org/docs/api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html)来了解进一步的信息。