
Java版
2014年5月13日星期二
What is Quartz?
Job Scheduling Library
Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for JTA transactions and clustering.
Quartz is freely usable, licensed under the Apache 2.0 license.
什么是Quartz?
作业调度图书馆
Quartz是一个功能丰富的,开源的作业调度库,可以在几乎任何Java应用程序集成 - 从最小的的应用程序,以最大的电子商务系统。 Quartz可以用来创建简单或复杂的调度执行几十,几百,甚至是数万名成千上万的就业机会; job被定义为标准的Java组件可以执行几乎任何你可以编程他们做。 Quartz调度包括了许多企业级特性,如JTA事务和集群支持。
Quartz是可自由使用的,基于Apache2.0许可协议发布。
1.1 Quartz手册java版-(一)使用Quartz
使用scheduler之前应首先实例化它。使用SchedulerFactory可以完成scheduler的实例化。
用户可直接地实例化这个工厂类并且直接使用工厂的实例(例如下面的例子)。
一旦一个scheduler被实例化,它就可以被启动(start),并且处于驻留模式,直到被关闭(shutdown)。
注意,一旦scheduler被关闭(shutdown),则它不能再重新启动,除非重新实例化它。
除非scheduler 被启动或者不处于暂停状态,否则触发器不会被触发(任务也不能被执行)。
下面是一个代码片断,这个代码片断实例化并且启动了一个scheduler,接着将一个要执行的任务纳入了进程。
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
JobDetail jobDetail = new JobDetail("myJob
null,
DumbJob.class);
Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every hour
trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date())); // start on the next even hour
trigger.setName("myTrigger");
sched.scheduleJob(jobDetail, trigger);
如您所见,使用quartz相当简单,在第二课中,我们将给出一个Job和Trigger的快速预览,这样就能够充分理解这个例子。
1.2 Quartz手册java版-(二)Jobs And Triggers
正如前面所提到的那样,通过实现Job接口来使你的.NET组件可以很简单地被scheduler执行。下面是Job接口:
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
这样,你会猜想出,当Job触发器触发时(在某个时刻),Execute (..)就被scheduler所调用。
JobExecutionContext对象被传递给这个方法,它为Job实例提供了它的“运行时”环境-一个指向执行这个IJob实例的Scheduler句柄,
一个指向触发该次执行的触发器的句柄,IJob的JobDetail对象以及一些其他的条目。
JobDetail对象由Quartz客户端在Job被加入到scheduler时创建。
它包含了Job的各种设置属性以及一个JobDataMap对象,这个对象被用来存储给定Job类实例的状态信息。
Trigger对象被用来触发jobs的执行。你希望将任务纳入到进度,要实例化一个Trigger并且“调整”它的属性以满足你想要的进度安排。
Triggers也有一个JobDataMap与之关联,这非常有利于向触发器所触发的Job传递参数。
Quartz打包了很多不同类型的Trigger,但最常用的Trigge类是SimpleTrigger和CronTrigger。
SimpleTrigger用来触发只需执行一次或者在给定时间触发并且重复N次且每次执行延迟一定时间的任务。
CronTrigger按照日历触发,例如“每个周五”,每个月10日中午或者10:15分。
为什么要分为Jobs和Triggers?很多任务日程管理器没有将Jobs和Triggers进行区分。
一些产品中只是将“job”简单地定义为一个带有一些小任务标识的执行时间。其他产品则更像Quartz中job和trigger的联合。
而开发Quartz的时候,我们决定对日程和按照日程执行的工作进行分离。(从我们的观点来看)这有很多好处。
例如:jobs可以被创建并且存储在job scheduler中,而不依赖于trigger,而且,很多triggers可以关联一个job.
另外的好处就是这种“松耦合”能使与日程中的Job相关的trigger过期后重新配置这些Job,
这样以后就能够重新将这些Job纳入日程而不必重新定义它们。这样就可以更改或者替换trigger而不必重新定义与之相连的job标识符。
当向Quartz scheduler中注册Jobs 和Triggers时,它们要给出标识它们的名字。Jobs 和Triggers也可以被放入“组”中。
“组”对于后续维护过程中,分类管理Jobs和Triggers非常有用。Jobs和Triggers的名字在组中必须唯一,
换句话说,Jobs和Triggers真实名字是它的名字+组。如果使Job或者Trigger的组为‘null’,
这等价于将其放入缺省的Scheduler.DEFAULT_GROUP组中。
1.3 Quartz手册java版-(三)更多关于Jobs和JobDetails
如你所见,Job相当容易实现。这里只是介绍有关Jobs本质, Job接口的Execute(..)方法以及JobDetails中需要理解的内容。
在所实现的类成为真正的“Job”时,期望任务所具有的各种属性需要通知给Quartz。
通过JobDetail类可以完成这个工作,这个类在前面的章节中曾简短提及过。
现在,我们花一些时间来讨论Quartz中Jobs的本质和Job实例的生命周期。首先让我们回顾一下第一课中所看到的代码片断
JobDetail jobDetail = new JobDetail("myJob", // job name
sched.DEFAULT_GROUP, // job group (you can also specify 'null' to use the default group)
DumbJob.class); // the java class to execute
Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);
trigger.setStartTime(new Date());
trigger.setName("myTrigger");
sched.scheduleJob(jobDetail, trigger);
现在定义一个DumbJob类
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
System.err.println("DumbJob is executing.");
}
}
注意我们传递给scheduler一个JobDetail实例,JobDetail关联一个job,提供job的class,每次scheduler执行job时,在执行execute(...)
这前会创建一个实例.job必须有一个无参构造方法.
你可能想问如何提供配置 job实例.或者保存job状态在执行过程中.答案是JobDataMap.它是JobDetail的一部分.
JobDataMap
JobDataMap被用来保存一系列的(序列化的)对象,这些对象在Job执行时可以得到。
JobDataMap是Map接口的一个实现,而且还增加了一些存储和读取主类型数据的便捷方法。
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();
String instGroup = context.getJobDetail().getGroup();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
ArrayList state = (ArrayList)dataMap.get("myStateData");
state.add(new Date());
System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);
}
}
如果使用一个持久的JobStore(在本指南的JobStore章节中讨论),那么必须注意存放在JobDataMap中的内容。
因为放入JobDataMap中的内容将被序列化,而且容易出现类型转换问题。很明显,标准.NET类型将是非常安全的,
但除此之外的类型,任何时候,只要有人改变了你要序列化其实例的类的定义,就要注意是否打破了程序的兼容性。
另外,你可以对JobStore和JobDataMap采用一种使用模式:就是只把主类型和String类型存放在Map中,
这样就可以减少后面序列化的问题。
Triggers也可以有JobDataMaps与之相关联。当scheduler中的Job被多个有规律或者重复触发的Triggers所使用时非常有用。
对于每次的触发,你可为Job提供不同的输入数据。
从Job执行时的JobExecutionContext中取得JobDataMap是惯用手段,它融合了从JobDetail和从Trigger中获的JobDataMap,
当有相同名字的键时,它用后者的值覆盖前者值。
下面给一个例子取数据从JobExecutionContext 关联JobDatMap在job执行中
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
String instName = context.getJobDetail().getName();
String instGroup = context.getJobDetail().getGroup();
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 " + instName + " of DumbJob says: " + jobSays);
}
}
StatefulJob——有状态任务
现在,一些关于Job状态数据的附加论题:一个Job实例可以被定义为“有状态的”或者“无状态的”。
“无状态的”任务只拥有它们被加入到scheduler时所存储的JobDataMap。这意味着,
在执行任务过程中任何对Job Data Map所作的更改都将丢失而且任务下次执行时也无法看到。
你可能会猜想出,有状态的任务恰好相反,它在任务的每次执行之后重新存储JobDataMap。
有状态任务的一个副作用就是它不能并发执行。换句话说,如果任务有状态,
那么当触发器在这个任务已经在执行的时候试图触发它,这个触发器就会被阻塞(等待),直到前面的执行完成。
想使任务有状态,它就要实现StatefulJob 接口而不是实现Job接口.
Job 'Instances' 任务“实例”
这个课程的最终观点或许已经很明确,可以创建一个单独的Job类,
并且通过创建多个JobDetails实例来将它的多个实例存储在scheduler中,
这样每个JobDetails对象都有它自己的一套属性和JobDataMap,而且将它们都加入到scheduler中。
当触发器被触发的时候,通过Scheduler中配置的JobFactory来实例化与之关联的Job类。
缺省的JobFactory只是简单地对Job类调用newInstance()方法。
创建自己JobFactory可以利用应用中诸如Ioc或者DI容器所产生或者初始化的Job实例。
jobs的其它属性
这里简短地总结一下通过JobDetail对象可以定义Job的其它属性。
Durability(持久性)-如果一个Job是不持久的, 一旦没有触发器与之关联,它就会被从scheduler 中自动删除。
Volatility(无常性)-如果一个Job是无常的,在重新启动Quartz i scheduler 时它不能被保持。
RequestsRecovery(请求恢复能力) -如果一个Job具备“请求恢复”能力,当它在执行时遇到scheduler “硬性的关闭”
(例如:执行的过程崩溃,或者计算机被关机),那么当scheduler重新启动时,这个任务会被重新执行。
这种情况下,JobExecutionContext.isRecovering() 属性将是true。
JobListeners(任务) -一个Job如果有0个或者多个JobListeners与之相关联,当这个Job执行时,被会被通知。
更多有关JobListeners的讨论见TriggerListeners & JobListeners章节。
JobExecutionException 任务执行异常
最后,需要告诉你一些关于Job.Execute(..)方法的细节。在Execute方法被执行时,仅允许抛出一个JobExecutionException类型异常。
因此需要将整个要执行的内容包括在一个'try-catch'块中。应花费一些时间仔细阅读JobExecutionException文档,
因为Job能够使用它向scheduler提供各种指示,你也可以知道怎么处理异常。
1.4 Quartz手册java版-(四)关于Triggers更多内容
同Job一样,trigger非常容易使用,但它有一些可选项需要注意和理解,同时,trigger有不同的类型,要按照需求进行选择。
Calendars——日历
Quartz Calendar对象在trigger被存储到scheduler时与trigger相关联。Calendar对于在trigger触发日程中的采用批量世间非常有用。
例如:你想要创建一个在每个工作日上午9:30触发一个触发器,那么就添加一个排除所有节假日的日历。
Calendar可以是任何实现Calendar接口的序列化对象。看起来如下;
package org.quartz;
public interface Calendar {
public boolean isTimeIncluded(long timeStamp);
public long getNextIncludedTime(long timeStamp);
}
注意,这些方法的参数都是DateTime型,你可以猜想出,它们的时间戳是毫秒的格式。这意味日历能够排除毫秒精度的时间。
最可能的是,你可能对排除整天的时间感兴趣。为了提供方便,Quartz提供了一个Quartz.Impl.Calendar.HolidayCalendar,
这个类可以排除整天的时间。
Calendars必须被实例化,然后通过addCalendar (..)方法注册到scheduler中。如果使用HolidayCalendar,在实例化之后,
你可以使用它的AddExcludedDate (DateTime excludedDate))方法来定义你想要从日程表中排除的时间。
同一个calendar实例可以被用于多个trigger中,如下:
HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
sched.addCalendar("myHolidays", cal, false);
Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every one hour interval
trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date())); // start on the next even hour
trigger.setName("myTrigger1");
trigger.setCalendarName("myHolidays");
// .. schedule job with trigger
Trigger trigger2 = TriggerUtils.makeDailyTrigger(8, 0); // fire every day at 08:00
trigger.setStartTime(new Date()); // begin immediately
trigger2.setName("myTrigger2");
trigger2.setCalendarName("myHolidays");
// .. schedule job with trigger2
传入SimpleTrigger构造函数的参数的细节将在下章中介绍。但是,任何在日历中被排除的时间所要进行的触发都被取消。
Priority
有时,当有多个Triggers时,Quartz没有足够的资源来同时立即处理scheduled的trigger.所以你可能要控制哪个先执行.
在这种情况下,你要设置Trigger的priority,如果N个triggers不会同时触发.但此时只有有限的几个线程可用.这时会先处
理级别高的trigger.如果你没有设置级别,默认为5,下面为一个例子:
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, 5);
Trigger trig1 = new SimpleTrigger("T1", "MyGroup", cal.getTime());
Trigger trig2 = new SimpleTrigger("T2", "MyGroup", cal.getTime());
Trigger trig3 = new SimpleTrigger("T3", "MyGroup", cal.getTime());
JobDetail jobDetail = new JobDetail("MyJob", "MyGroup", NoOpJob.class);
// Trigger1 does not have its priority set, so it defaults to 5
sched.scheduleJob(jobDetail, trig1);
// Trigger2 has its priority set to 10
trig2.setJobName(jobDetail.getName());
trig2.setPriority(10);
sched.scheduleJob(trig2);
// Trigger2 has its priority set to 1
trig3.setJobName(jobDetail.getName());
trig2.setPriority(1);
sched.scheduleJob(trig3);
// Five minutes from now, when the scheduler invokes these three triggers
// they will be allocated worker threads in decreasing order of their
// priority: Trigger2(10), Trigger1(5), Trigger3(1)
Misfire Instructions——未触发指令
Trigger的另一个重要属性就是它的“misfire instruction(未触发指令)”。
如果因为scheduler被关闭而导致持久的触发器“错过”了触发时间,这时,未触发就发生了。不同类型的触发器有不同的未触发指令。
缺省情况下,他们会使用一个“智能策略”指令——根据触发器类型和配置的不同产生不同动作。
当scheduler开始时,它查找所有未触发的持久triggers,然后按照每个触发器所配置的未触发指令来更新它们。
开始工程中使用Quartz的时,应熟悉定义在各个类型触发器上的未触发指令。
关于未触发指令信息的详细说明将在每种特定的类型触发器的指南课程中给出。
可以通过MisfireInstruction属性来为给定的触发器实例配置未触发指令。
TriggerUtils - Triggers Made Easy(TriggerUtils——使Triggers变得容易)
TriggerUtils类包含了创建触发器以及日期的便捷方法。使用这个类可以轻松地使触发器在每分钟,小时,日,星期,月等触发。
使用这个类也可以产生距离触发最近的妙、分或者小时,这对设定触发开始时间非常有用。
TriggerListeners
最后,如同job一样,triggers可以注册,实现TriggerListener接口的对象将可以收到触发器被触发的通知。
1.5 Quartz手册java版-(五) SimpleTrigger
如果需要让任务只在某个时刻执行一次,或者,在某个时刻开始,然后按照某个时间间隔重复执行,
简单地说,如果你想让触发器在2007年8月20日上午11:23:54秒执行,然后每个隔10秒钟重复执行一次,并且这样重复5次。
那么SimpleTrigger 就可以满足你的要求。
通过这样的描述,你可能很惊奇地发现SimpleTrigger包括这些属性:开始时间,结束时间,重复次数,重复间隔。
所有这属性都是你期望它所应具备的,只有end-time属性有一些条目与之关联。
重复次数可能是0,正数或者一个常量值SimpleTrigger.REPEAT_INDEFINITELY。重复间隔时间属性可能是0,
正的long型,这个数字以毫秒为单位。注意:如果指定的重复间隔时间是0,
那么会导致触发器按照‘重复数量’定义的次数并发触发(或者接近并发)。
org.quartz.helpers.TriggerUtils 类对处理这样的循环也提供了很多支持。
endTime(如果这个属性被设置)属性会覆盖重复次数属性,这对创建一个每隔10秒就触发一次直到某个时间结束的触发器非常有用,
这就可以不计算开始时间和结束时间之间的重复数量。也可以指定一个结束时间,然后使用REPEAT_INDEFINITELY作为重复数量。
(甚至可以指定一个大于结束时间之前实际重复次数的整数作为重复次数)。一句话,endTime属性控制权高于重复次数属性。
SimpleTrigger有几个不同的构造函数,下面我们来看看这结果构造函数:
public SimpleTrigger(String name, String group,Date startTime,Date endTime,int repeatCount,long repeatInterval)
SimpleTrigger Example 1 - Create a trigger that fires exactly once, ten seconds from now
long startTime = System.currentTimeMillis() + 10000L;
SimpleTrigger trigger = new SimpleTrigger("myTrigger", null,new Date(startTime),null,0,0L);
SimpleTrigger Example 2 - Create a trigger that fires immediately, then repeats every 60 seconds, forever
SimpleTrigger trigger = new SimpleTrigger("myTrigger", null, new Date(),null,SimpleTrigger.REPEAT_INDEFINITELY,60L * 1000L);
SimpleTrigger Example 3 - Create a trigger that fires immediately, then repeats every 10 seconds until 40 seconds from now
long endTime = System.currentTimeMillis() + 40000L;
SimpleTrigger trigger = new SimpleTrigger("myTriggerDate(),new Date(endTime), SimpleTrigger.REPEAT_INDEFINITELY,10L * 1000L);
SimpleTrigger Example 4 - Create a trigger that fires on March 17 of the year 2002 at precisely 10:30 am, and repeats 5 times (for a total of 6 firings) - with a 30 second delay between each firing
java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);
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", null, startTime,null,5,30L * 1000L);
SimpleTrigger Misfire Instructions——SimpleTrigger的未触发指令
“未触发”发生时,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指令可用,
并且,这个指令对于每个类型的触发器都是缺省的。
如果使用 "smart policy" 指令,SimpleTriger会基于配置和SimpleTrigger实例的状态动态的选择上面的指令.
SimpleTrigger.updateAfterMisfire() 会获取动态行为的详细信息.
1.6 Quartz手册java版-(六)CronTrigger
如果你需要像日历那样按日程来触发任务,而不是像SimpleTrigger 那样每隔特定的间隔时间触发,
CronTriggers通常比SimpleTrigger更有用。
使用CronTrigger,你可以指定诸如“每个周五中午”,或者“每个工作日的9:30”或者“从每个周一、周三、周五的上午
9:00到上午10:00之间每隔五分钟”这样日程安排来触发。甚至,象SimpleTrigger一样,
CronTrigger也有一个StartTime以指定日程从什么时候开始,也有一个(可选的)EndTime以指定何时日程不再继续。
Cron表达式被用来配置CronTrigger实例。Cron表达式是一个由7个子表达式组成的字符串。
每个子表达式都描述了一个单独的日程细节。这些子表达式用空格分隔,分别表示:
1. Seconds 秒
2. Minutes 分钟
3. Hours 小时
4. Day-of-Month 月中的天
5. Month 月
6. Day-of-Week 周中的天
7. Year (optional field) 年(可选的域)
一个cron表达式的例子字符串为"0 0 12 ? * WED",这表示“每周三的中午12:00”。
单个子表达式可以包含范围或者列表。例如:前面例子中的周中的天这个域(这里是"WED")可以被替换为"MON-FRI", "MON, WED, FRI"或者甚至"MON-WED,SAT"。
通配符('*')可以被用来表示域中“每个”可能的值。因此在"Month"域中的*表示每个月,而在Day-Of-Week域中的*则表示“周中的每一天”。
所有的域中的值都有特定的合法范围,这些值的合法范围相当明显,例如:秒和分域的合法值为0到59,小时的合法范围是0到23,
Day-of-Month中值得合法凡范围是0到31,但是需要注意不同的月份中的天数不同。
月份的合法值是0到11。或者用字符串JAN,FEB MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 及DEC来表示。
Days-of-Week可以用1到7来表示(1=星期日)或者用字符串SUN, MON, TUE, WED, THU, FRI 和SAT来表示.
'/'字符用来表示值的增量,例如, 如果分钟域中放入'0/15',它表示“每隔15分钟,从0开始”,
如果在份中域中使用'3/20',则表示“小时中每隔20分钟,从第3分钟开始”或者另外相同的形式就是'3,23,43'。
'?'字符可以用在day-of-month及day-of-week域中,它用来表示“没有指定值”。
这对于需要指定一个或者两个域的值而不需要对其他域进行设置来说相当有用。
'L'字符可以在day-of-month及day-of-week中使用,这个字符是"last"的简写,但是在两个域中的意义不同。
例如,在day-of-month域中的"L"表示这个月的最后一天,即,一月的31日,非闰年的二月的28日。
如果它用在day-of-week中,则表示"7"或者"SAT"。但是如果在day-of-week域中,这个字符跟在别的值后面,则表示"当月的最后的周XXX"。
例如:"6L" 或者 "FRIL"都表示本月的最后一个周五。当使用'L'选项时,最重要的是不要指定列表或者值范围,否则会导致混乱。
'W' 字符用来指定距离给定日最接近的周几(在day-of-week域中指定)。
例如:如果你为day-of-month域指定为"15W",则表示“距离月中15号最近的周几”。
'#'表示表示月中的第几个周几。例如:day-of-week域中的"6#3" 或者 "FRI#3"表示“月中第三个周五”。
例1 – 一个简单的每隔5分钟触发一次的表达式
"0 0/5 * * * ?" CronTrigger
例2 – 在每分钟的10秒后每隔5分钟触发一次的表达式(例如. 10:00:10 am, 10:05:10等.)。
"10 0/5 * * * ?" CronTrigger
例3 – 在每个周三和周五的10:30,11:30,12:30触发的表达式。
"0 30 10-13 ? * WED,FRI" CronTrigger
例4 – 在每个月的5号,20号的8点和10点之间每隔半个小时触发一次且不包括10点,只是8:30,9:00和9:30的表达式。
"0 0/30 8-9 5,20 * ?"
注意,对于单独触发器来说,有些日程需求可能过于复杂而不能用表达式表述,
例如:9:00到10:00之间每隔5分钟触发一次,下午1:00到10点每隔20分钟触发一次。这个解决方案就是创建两个触发器,两个触发器都运行相同的任务。
1.7 Quartz手册java版-(七)TriggerListeners和JobListeners
是在scheduler事件发生时能够执行动作的对象。可以看出,TriggerListeners接收与triggers相关的事件,
而JobListeners则接收与Job相关的事件。
Trigger相关的事件包括:trigger触发、trigger未触发,以及trigger完成(由trigger触发的任务被完成)。
public interface TriggerListener {
public String getName();
public void triggerFired(Trigger trigger, JobExecutionContext context);
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
public void triggerMisfired(Trigger trigger);
public void triggerComplete(Trigger trigger, JobExecutionContext context,
int triggerInstructionCode);
}
任务相关的事件包括:即将被执行的任务的通知和任务已经执行完毕的通知。
public interface JobListener {
public String getName();
public void jobToBeExecuted(JobExecutionContext context);
public void jobExecutionVetoed(JobExecutionContext context);
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException);
}
使用你自定义的
创建很简单,创建一个实现org.quartz.TriggerListener and/or org.quartz.JobListener 的接口。
然后在执行的时候注册到scheduler中,而且必须给定一个名字(或者,它们必须通过他们的Name属性来介绍自己)。
可以被注册为“全局”的或者“非全局”。“全局”接收所有triggers/jobs产生的事件,
而“非全局”只接受那些通过getTriggerListenerNames() 或 getJobListenerNames()方法显式指定名的triggers/jobs所产生的事件。
正如上面所说的那样,在运行时向scheduler注册,并且不被存储在jobs 和triggers的JobStore中。
Jobs和Trigger只存储了与他们相关的的名字。因此,每次应用运行的时候,都需要向scheduler重新注册。
scheduler.addGlobalJobListener(myJobListener);
or
scheduler.addJobListener(myJobListener);
Quartz的大多数用户不使用,但是当应用需要创建事件通知而Job本身不能显式通知应用,则使用非常方便。
1.8 Quartz手册java版-(八)SchedulerListeners
SchedulerListeners同TriggerListeners及JobListeners非常相似,SchedulerListeners只接收与特定trigger 或job无关的Scheduler自身事件通知。
Scheduler相关的事件包括:增加job或者trigger,移除Job或者trigger, scheduler内部发生的错误,scheduler将被关闭的通知,以及其他。
public interface SchedulerListener {
public void jobScheduled(Trigger trigger);
public void jobUnscheduled(String triggerName, String triggerGroup);
public void triggerFinalized(Trigger trigger);
public void triggersPaused(String triggerName, String triggerGroup);
public void triggersResumed(String triggerName, String triggerGroup);
public void jobsPaused(String jobName, String jobGroup);
public void jobsResumed(String jobName, String jobGroup);
public void schedulerError(String msg, SchedulerException cause);
public void schedulerShutdown();
}
除了不分“全局”或者“非全局”外,SchedulerListeners创建及注册的方法同其他类型十分相同。
所有实现Quartz.ISchedulerListener接口的对象都是SchedulerListeners。
1.9 Quartz手册java版-(九)JobStores
JobStore负责保持对所有scheduler “工作数据”追踪,这些工作数据包括:job(任务),trigger(触发器),calendar(日历)等。为你的Quartz scheduler选择合适的JobStore是非常重要的一步,幸运的是,如果你理解了不同的JobStore之间的差别,那么选择就变得非常简单。在提供产生scheduler 实例的SchedulerFactory的属性文件中声明scheduler所使用的JobStore(以及它的配置)。
注:不要在代码中直接使用JobStore实例,处于某些原因,很多人试图这么做。JobStore是由Quartz自身在幕后使用。你必须告诉(通过配置)Quartz使用哪个JobStore,而你只是在你的代码中使用Scheduler接口完成工作。
RAMJobStore
RAMJobStore是最简单的JobStore,也是性能最好的(根据CPU时间)。从名字就可以直观地看出,RAMJobStore将所有的数据都保存在RAM中。
这就是为什么它闪电般的快速和如此容易地配置。缺点就是当应用结束时所有的日程信息都会丢失,
这意味着RAMJobStore不能满足Jobs和Triggers的持久性(“non-volatility”)。对于有些应用来说,
这是可以接受的,甚至是期望的行为。但是对于其他应用来说,这将是灾难。
配置
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
Qiartz.net缺省使用的就是RAMJobStore
JDBCJobStore
JDBCJobStore通过jdbc保存所有数据到数据库,它的配置要比RAMJobStore稍微复杂,同时速度也没有那么快。
但是性能的缺陷不是非常差,尤其是如果你在数据库表的主键上建立索引。
JDBCJobStore几乎可以在任何数据库上工作,它广泛地使用Oracle, MySQL, MS SQLServer2000, HSQLDB, PostreSQL 以及 DB2。
要使用JDBCJobStore,首先必须创建一套Quartz使用的数据库表,可以在Quartz 的docs/dbTables找到创建库表的SQL脚本。
如果没有找到你的数据库类型的脚本,那么找到一个已有的,修改成为你数据库所需要的。
需要注意的一件事情就是所有Quartz库表名都以QRTZ_作为前缀(例如:表"QRTZ_TRIGGERS",及"QRTZ_JOB_DETAIL")。
实际上,可以你可以将前缀设置为任何你想要的前缀,只要你告诉JDBCJobStore那个前缀是什么即可(在你的Quartz属性文件中配置)。
对于一个数据库中使用多个scheduler实例,那么配置不同的前缀可以创建多套库表,十分有用。
一旦数据库表已经创建,在配置和启动JDBCJobStore之前,就需要作出一个更加重要的决策。你要决定在你的应用中需要什么类型的事务。
如果不想将scheduling命令绑到其他的事务上,那么你可以通过对JobStore使用JobStoreTX来让Quartz帮你管理事务(这是最普遍的选择)。
如是你需要Quartz使用其它事务(例如j2ee 应用服务器),你需要使用JobStoreStoreCMT(这时应用服务器容器会管理事务)
最后的疑问就是如何建立获得数据库联接的数据源(DataSource)。Quartz属性中定义数据源是通过提供所有联接数据库的信息,
让Quartz自己创建和管理数据源。这里有很多种不同的方式,一种是Quartz提供所有数据库连接信息自己创建管理DataSource.
另一种方式是Quartz使用数据源来管理应用服务器上运行的Quartz.具体的信息例子在"docs/config"
要使用JDBCJobStore(假定使用StdSchedulerFactory),首先需要设置Quartz配置中的quartz.jobStore.type属性为
org.quartz.impl.jdbcjobstore.JobStoreTX or org.quartz.impl.jdbcjobstore.JobStoreCMT
配置
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
下一步,需要为JobStore 选择一个DriverDelegate , DriverDelegate负责做指定数据库的所有JDBC工作。
StdJDBCDelegate 是一个使用vanilla" JDBC代码(以及SQL语句)来完成工作的代理。
如果数据库没有其他指定的代理,那么就试用这个代理。只有当使用StdJDBCDelegate 发生问题时,
我们才会使用数据库特定的代理(这看起来非常乐观。其他的代理可以在org.quartz.impl.jdbcjobstore包找到。)。
其他的代理包括PostgreSQLDelegate ( 专为PostgreSQL 7.x)。
一旦选择好了代理,就将它的名字设置给JDBCJobStore 。
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
接下来,需要为JobStore指定所使用的数据库表前缀(前面讨论过)。
org.quartz.jobStore.tablePrefix = QRTZ_
最后,你需要设计数据源使用的jobStore,这个数据源的名字必须在Quartz.properties中定义.
org.quartz.jobStore.dataSource = myDS
如果Scheduler非常忙(比如,执行的任务数量差不多和线程池的数量相同,
那么你需要正确地配置DataSource的连接数量为线程池数量+1。
为了指示JDBCJobStore所有的JobDataMaps中的值都是字符串,
并且能以“名字-值”对的方式存储而不是以复杂对象的序列化形式存储在BLOB字段中,
应设置 org.quartz.jobStore.useProperties配置参数的值为"true"(这是缺省的方式)。
这样做,从长远来看非常安全,这样避免了对存储在BLOB中的非字符串的序列化对象的类型转换问题。
1.10 Quartz手册java版-(十)配置、资源使用以及SchedulerFactory
Quartz以模块方式构架,因此,要使它运行,几个组件必须很好的咬合在一起。幸运的是,已经有了一些现存的助手可以完成这些工作。
在Quartz进行工作之前需要被配置的组件主要有:
ThreadPool 线程池
JobStore
DataSources (如果需要)
Scheduler本身
ThreadPool(线程池)为Quartz运行任务时提供了一些线程。池中的线程越多,那么并发运行的任务数就越多。
但是,过多的线程会降低系统的运行速度。大多数用户发现5个或者相近的线程就已经足够了,
因为任何给定的时间段内都不超过100个任务要运行,而且这些任务不会在同一时刻运行,同时任务活动时间很短(很快就结束了)。
其他的用户发现需要10,15,50,甚至100个线程,因为每个schedules都有成千上万的触发器,
并且在给定的时刻会有平均10到100个任务在运行。确定schedule的线程池中的线程数量的合理值取决于用scheduler来做什么。
除了尽可能少地设置线程数量,使得任务执行时线程够用外(由于计算机资源的有限性),没有其他实用的准则。
注意:如果触发器触发的时间到了,却没有可用的线程,那么Quartz将会让这个任务等待,直到有线程可用。
这样,任务的执行将比它因该执行的时间晚一些毫秒。如果scheduler的配置的“未触发极限”时限中仍然没有线程可用,
这甚至会导致“未触发(misfire)”。
ThreadPool接口定义在org.quartz.spi中,你也可以创建一个自己的ThreadPool(线程池)实现,
Quartz打包了一个简单(但非常满意的)的线程池,名为:org.quartz.simpl.SimpleThreadPool,
这个线程池只是简单地在它的池中保持固定数量的线程,不增长也不缩小。但是它非常健壮且经过良好的测试,
差不多每个Quartz用户都使用这个池。
JobStores和DataSrouces在(九)中已经讨论过,值得注意的一个事实是所有的JobStores都实现了org.quartz.spi.JobStore 接口,
如果捆绑的JobStores不能满足你的要求,你可以自己开发一个。
最后你需要创建自己的Scheduler实例。Scheduler本身需要给定一个名字告诉RMI设置,处理的JobStore和ThreadPool实例
StdSchedulerFactory
StdSchedulerFactory是对org.quartz.SchedulerFactory接口的一个实现。
是使用一套属性(java.util.Properties)来创建和初始化Quartz Scheduler。
这些属性通常在文件中存储和加载。也可以通过编写程序来直接操作工厂。
简单地调用工厂的getScheduler()就可以产生一个scheduler,初始化(以及它的ThreadPool、JobStore和DataSources),
并且返回一个公共的接口。
这里有许多例子在的配置在"docs/config"
DirectSchedulerFactory
DirectSchedulerFactory是SchedulerFactory的另一个实现。它对于那些希望用更加程序化的方式创建Scheduler非常有用。
不鼓励使用它的原因如下:
(1) 它需要用户非常了解他们想要干什么。
(2) 它不允许声明式的配置。换句话说,它使用硬编码的方式设置scheduler。
Logging 日志
Quartz用org.apache.commons.logging framework架来满足它所有的日志需要。Quartz不会产生太多的日志信息,
通常只是一些初始化信息以及只有在任务执行时发生的一些严重问题的信息。要“调整”日志设置(例如输出量以及在哪输出),
需要理解Common.Logging framework框架,这不在本文档的讨论范围内。
1.11 Quartz手册java版-(十一)高级(企业级)属性
Clustering 集群
目前,集群只能用在使用JDBC-Jobstore (JobStoreTX or JobStoreCMT)的情况。
特新包括负载均衡和容错(如果JobDetail的"request recovery"标记被设置为true)。
设置" org.quartz.jobStore.isClustered"属性为true才可以集群,集群中的每个实例都使用quartz.properties的相同拷贝。
集群所使用属性文件的例外是一致的,下面是允许的例外:不同的线程池数量,"org.quartz.scheduler.instanceId"的不同属性值。
集群中的每个节点必须有唯一的instanceId,通过替换这个属性的值为"AUTO"就可以轻松做到(不要不同的属性文件)。
除非使用某些运行非常有整齐(时钟必须同步在一秒之内)的时间同步服务来同步不同计算机的时钟外,
不要将集群运行在不同的计算机三行。
不要在一套数据库表上运行未集群的实例。这会导致严重的数据冲突,及不可预知的行为。
JTA Transactions
像(九)中一样,JobStroeCMT 允许Quartz 大量 JTA 事务
jobs也能执行JTA事务通过设置org.quartz.scheduler.wrapJobExecutionInUserTransaction=true,设置后JTA事务begin()于job execute()调用
commit()于execute()结束
1.12 Quartz手册java版-(十二)Quartz 的其他特性
Plug-Ins 插件
Quartz提供了一个接口(org.quartz.spi.SchedulerPlugin)来插入附加的功能。
随Quartz打包儿来的插件有很多有用的功能,它们在org.quartz.plugins 包中找到。他们提供了诸如自动安排任务的日程,
将任务和触发器事件的历史记入日志以及虚拟机退出时确保干净地关闭scheduler等的功能。
JobFactory
当触发器触发时,与之相关联的任务被Scheduler中配置的JobFactory所实例华。缺省的JobFactory只是简单调用newInstance()地创建一个Job实例。
你也许想创建自己的JobFactory实现,以完成诸如让应用的IoC 或者 DI容器产生/初始化job实例的功能。
查看 org.quartz.spi.JobFactory 接口及与之相关的Scheduler.setJobFactory(fact)方法。
'Factory-Shipped' Jobs
Quartz也提供了一些可以在你的应用中使用的实用的Jobs,比如,发邮件、调用远程对象。
这些外来的Job可以在org.quartz.jobs 命名空间里中找到。
