作业调度(Quartz)技术总结(二)
1.触发器(Triggers)与Job相比,Trigger相对来说比较简单容易,但是要能完全的掌握使用Quartz,使其包含各种自定义的时间表选项,我们必须先知道和理解Trigger。 1.1日历(Calendars)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中被排除的时间段的触发执行都将被取消。 1.2 过时触发指令(Misfire Instructions)Trigger另一个重要的属性是“Misfire Instruction”。过时触发发生在持久Trigger失去了触发时间,由于对应的Scheduler被关闭的原因;不同的Trigger类型有不同的过时触发指令,默认情况下是使用“smart policy”指令,该指令根据Trigger类型和配置具有动态的行为。当scheduler启动时,它将搜索所有过时触发的持久Trigger,并同时根据它们各自所配置的过时触发指令进行更新;当我们在项目中要使用Quartz时,我们必须熟悉所要使用的Trigger类型的过时触发指示,在对应的JavaDOC中有对其进行说明。可以通过方法setMisfireInstruction(…)来设置Trigger的过时触发指示。 1.3 触发器的辅助类(TriggerUtils)TriggerUtils类(在org.quartz.helpers包中)为我们创建Trigger和触发时间提供了方便,可以使我们不用java.util.Calendar对象。使用辅助类能够很容易地创建基于每分钟、小时、天、周和月等触发的触发器,也可以创建各种各样的日期――对设置触发器的启动时间很有用。 1.4触发器的监听器(TriggerListeners)Trigger能够像Job一样,可以把监听器注册到Trigger中,实现了接口TriggerListener的对象就可以接收到Trigger触发时的通知。 2.Simple触发器(SimpleTrigger)当我们需要在规定的时间执行一次或在规定的时间段以一定的时间间隔重复触发执行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 = new SimpleTrigger(”myTrigger”, sched.DEFAULT_GROUP, new Date(), 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); 2.1 Simple触发器的过时触发指令(SimpleTrigger Misfire Instructions)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()方法详细解析了动态选择的内容。 3.Cron触发器(CronTrigger)CronTrigger的功能比SimpleTrigger强大,它可以实现基于类似于日历概念的作业调度,而不是单单基于一定的时间间隔的。 使用CronTrigger,我们能够定义如下类型的日历型的Schedule:每个星期五的中午,每个周末的早上9:30,或者每周一、周三和周五的早上9:00到10:00每隔5分钟。 3.1 Cron表达式(Cron Expressions)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的各个子表达式和取值范围进行说明:
‘-’字符表示:值的范围,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(day 6=Friday and “#3”=the 3rd one in the month);2#1表示的意思是:该月的第1个星期1;4#5表示的意思是:该月的第5个星期3;需要注意:不能把值设为“#5”,因为一个月中不可能有第5个星期3,如果这样设置,将导致该月无法执行触发; 下面举一些完整的Cron- Expressions例子:
当Schedule中要求比较复杂,采用单个的Trigger无法实现时,可以配置多个的Trigger来实现,比如:每天的9:00到10:00之间每隔5分钟执行一次触发,并且在每天的13:00到22:00之间每隔20分钟执行一次触发,配置2个Trigger并把2配置好的2个Trigger注册到相同的Job中就可以很简单的实现上述情况。 |