Quartz
Quartz 可以满足更多更复杂的调度需求,首先让我们看看如何用 Quartz 实现每星期二 16:38 的调度安排:
清单1:
package com.ibm.scheduler; import java.util.Date; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerFactory; import org.quartz.Trigger; import org.quartz.helpers.TriggerUtils; public class QuartzTest implements Job { @Override //该方法实现需要执行的任务 public void execute(JobExecutionContext arg0) throws JobExecutionException { System.out.println("Generating report - " + arg0.getJobDetail().getFullName() + ", type =" + arg0.getJobDetail().getJobDataMap().get("type")); System.out.println(new Date().toString()); } public static void main(String[] args) { try { // 创建一个Scheduler SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler sched = schedFact.getScheduler(); sched.start(); // 创建一个JobDetail,指明name,groupname,以及具体的Job类名, //该Job负责定义需要执行任务 JobDetail jobDetail = new JobDetail("myJob", "myJobGroup", QuartzTest.class); jobDetail.getJobDataMap().put("type", "FULL"); // 创建一个每周触发的Trigger,指明星期几几点几分执行 Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38); trigger.setGroup("myTriggerGroup"); // 从当前时间的下一秒开始执行 trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date())); // 指明trigger的name trigger.setName("myTrigger"); // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务 sched.scheduleJob(jobDetail, trigger); } catch (Exception e) { e.printStackTrace(); } } }
输出结果:
/** 输出结果: Generating report - myJobGroup.myJob, type =FULL Tue Feb 8 16:38:00 CST 2011 Generating report - myJobGroup.myJob, type =FUL Tue Feb 15 16:38:00 CST 2011 */
清单 1非常简洁地实现了一个上述复杂的任务调度。Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。
Job
使用者只需要创建一个 Job 的继承类,实现 execute 方法。JobDetail 负责封装 Job 以及 Job 的属性,并将其提供给 Scheduler 作为参数。每次 Scheduler 执行任务时,首先会创建一个 Job 的实例,然后再调用 execute 方法执行。Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。JobDataMap 可以存储任意数量的 Key,Value 对,例如:
jobDetail.getJobDataMap().put("myDescription", "my job description"); jobDetail.getJobDataMap().put("myValue", 1998); ArrayList<String> list = new ArrayList<String>(); list.add("item1"); jobDetail.getJobDataMap().put("myArray", list);
JobDataMap 中的数据可以通过下面的方式获取:
public class JobDataMapTest implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { //从context中获取instName,groupName以及dataMap String instName = context.getJobDetail().getName(); String groupName = context.getJobDetail().getGroup(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); //从dataMap中获取myDescription,myValue以及myArray String myDescription = dataMap.getString("myDescription"); int myValue = dataMap.getInt("myValue"); ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray"); System.out.println(" Instance =" + instName + ", group = " + groupName + ", description = " + myDescription + ", value =" + myValue + ", array item0 = " + myArray.get(0)); } }
输出结果:
/** 输出结果: Instance = myJob, group = myJobGroup, description = my job description, value =1998, array item0 = item1 */
Trigger
Trigger 的作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。
SimpleTrigger 适用于在某一特定的时间执行一次,或者在某一特定的时间以某一特定时间间隔执行多次。上述功能决定了 SimpleTrigger 的参数包括 start-time, end-time, repeat count, 以及 repeat interval。
Repeat count 取值为大于或等于零的整数,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。
Repeat interval 取值为大于或等于零的长整型。当 Repeat interval 取值为零并且 Repeat count 取值大于零时,将会触发任务的并发执行。
Start-time 与 dnd-time 取值为 java.util.Date。当同时指定 end-time 与 repeat count 时,优先考虑 end-time。一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY。
以下是 SimpleTrigger 的构造方法:
public SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval)
举例如下:
创建一个立即执行且仅执行一次的 SimpleTrigger:
SimpleTrigger trigger= new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);
创建一个半分钟后开始执行,且每隔一分钟重复执行一次的 SimpleTrigger:
SimpleTrigger trigger= new SimpleTrigger("myTrigger", "myGroup", new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);
创建一个 2011 年 6 月 1 日 8:30 开始执行,每隔一小时执行一次,一共执行一百次,一天之后截止的 SimpleTrigger:
Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, 2011); calendar.set(Calendar.MONTH, Calendar.JUNE); calendar.set(Calendar.DAY_OF_MONTH, 1); calendar.set(Calendar.HOUR, 8); calendar.set(Calendar.MINUTE, 30); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); Date startTime = calendar.getTime(); Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000); SimpleTrigger trigger=new SimpleTrigger("myTrigger", "myGroup", startTime, endTime, 100, 60*60*1000);
上述最后一个例子中,同时设置了 end-time 与 repeat count,则优先考虑 end-time,总共可以执行二十四次。
CronTrigger 的用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,CronTrigger 主要适用于基于日历的调度安排。例如:每星期二的 16:38:10 执行,每月一号执行,以及更复杂的调度安排等。
CronTrigger 同样需要指定 start-time 和 end-time,其核心在于 Cron 表达式,由七个字段组成:
Seconds Minutes Hours Day-of-Month Month Day-of-Week Year (Optional field)
举例如下:
创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:
0 0 0/3 * * ?
创建一个每十分钟执行的 CronTrigger,且从每小时的第三分钟开始执行:
0 3/10 * * * ?
创建一个每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时执行一次的 CronTrigger:
0 0/30 20-23 ? * MON-WED,SAT
创建一个每月最后一个周四,中午 11:30-14:30,每小时执行一次的 trigger:
0 30 11-14/1 ? * 5L
解释一下上述例子中各符号的含义:
首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值为 0 到 59,Hours 取值为 0 到 23,Day-of-Month 取值为 0-31, Month 取值为 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值为 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每个字段可以取单个值,多个值,或一个范围,例如 Day-of-Week 可取值为“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。
通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。
/ 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。
? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。适用于需要对这两个字段中的其中一个指定值,而对另一个不指定值的情况。一般情况下,这两个字段只需对一个赋值。
L 仅适用于 Day-of-Month 和 Day-of-Week。L 用于 Day-of-Month 表示该月最后一天。L 单独用于 Day-of-Week 表示周六,否则表示一个月最后一个星期几,例如 5L 或者 THUL 表示该月最后一个星期四。
W 仅适用于 Day-of-Month,表示离指定日期最近的一个工作日,例如 Day-of-Month 赋值为 10W 表示该月离 10 号最近的一个工作日。
# 仅适用于 Day-of-Week,表示该月第 XXX 个星期几。例如 Day-of-Week 赋值为 5#2 或者 THU#2,表示该月第二个星期四。
CronTrigger 的使用如下:
CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup"); try { cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT"); } catch (Exception e) { e.printStackTrace(); }
Job 与 Trigger 的松耦合设计是 Quartz 的一大特点,其优点在于同一个 Job 可以绑定多个不同的 Trigger,同一个 Trigger 也可以调度多个 Job,灵活性很强。
Listener
除了上述基本的调度功能,Quartz 还提供了 listener 的功能。主要包含三种 listener:JobListener,TriggerListener 以及 SchedulerListener。当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。最常见的情况是,当任务被执行时,系统发生故障,Listener 监听到错误,立即发送邮件给管理员。下面给出 JobListener 的实例:
清单 2. JobListener 的实现
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.SchedulerException; public class MyListener implements JobListener{ @Override public String getName() { return "My Listener"; } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { if(jobException != null){ try { //停止Scheduler context.getScheduler().shutdown(); System.out.println(" Error occurs when executing jobs, shut down the scheduler "); // 给管理员发送邮件… } catch (SchedulerException e) { e.printStackTrace(); } } } }
从清单 2 可以看出,使用者只需要创建一个 JobListener 的继承类,重载需要触发的方法即可。当然,需要将 listener 的实现类注册到 Scheduler 和 JobDetail 中:
sched.addJobListener(new MyListener()); jobDetail.addJobListener("My Listener"); // listener 的名字
使用者也可以将 listener 注册为全局 listener,这样便可以监听 scheduler 中注册的所有任务 :
sched.addGlobalJobListener(new MyListener());
为了测试 listener 的功能,可以在 job 的 execute 方法中强制抛出异常。清单 7 中,listener 接收到异常,将 job 所在的 scheduler 停掉,阻止后续的 job 继续执行。scheduler、jobDetail 等信息都可以从 listener 的参数 context 中检索到。
清单 2的输出结果为:
Generating report - myJob.myJob, type =FULL Tue Feb 15 18:57:35 CST 2011 2011-2-15 18:57:35 org.quartz.core.JobRunShell run 信息 : Job myJob.myJob threw a JobExecutionException: org.quartz.JobExecutionException at com.ibm.scheduler.QuartzListenerTest.execute(QuartzListenerTest.java:22) at org.quartz.core.JobRunShell.run(JobRunShell.java:191) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:516) 2011-2-15 18:57:35 org.quartz.core.QuartzScheduler shutdown 信息 : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down. Error occurs when executing jobs, shut down the scheduler
TriggerListener、SchedulerListener 与 JobListener 有类似的功能,只是各自触发的事件不同,如 JobListener 触发的事件为:
Job to be executed, Job has completed execution 等
TriggerListener 触发的事件为:
Trigger firings, trigger mis-firings, trigger completions 等
SchedulerListener 触发的事件为:
add a job/trigger, remove a job/trigger, shutdown a scheduler 等
读者可以根据自己的需求重载相应的事件。