转载:https://blog.csdn.net/jiangliubang/article/details/78508101
五、SimpleTrigger
0、有状态的job与无状态的job:无状态任务在执行时,拥有自己的JobDataMap拷贝,对JobData的更改不会影响下次的执行。而有状态任务共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改都会保存下来,后面的执行可以看到这个更改。也就是每次执行任务后都会对后面的执行发生影响。正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。
1、SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。根据描述,你可能已经发现了,SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔。这些属性的含义与你所期望的是一致的,只是关于结束时间有一些地方需要注意。重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。注意,如果重复间隔为0,trigger将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。如果你还不熟悉DateBuilder,了解后你会发现使用它可以非常方便地构造基于开始时间(或终止时间)的调度策略。endTime属性的值会覆盖设置重复次数的属性值;比如,你可以创建一个trigger,在终止时间之前每隔10秒执行一次,你不需要去计算在开始时间和终止时间之间的重复次数,只需要设置终止时间并将重复次数设置为REPEAT_INDEFINITELY(当然,你也可以将重复次数设置为一个很大的值,并保证该值比trigger在终止时间之前实际触发的次数要大即可)。SimpleTrigger实例通过TriggerBuilder设置主要的属性,通过SimpleScheduleBuilder设置与SimpleTrigger相关的属性。要使用这些builder的静态方法,需要静态导入:
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.DateBuilder.*:
下面的例子,是基于简单调度(simple schedule)创建的trigger。建议都看一下,因为每个例子都包含一个不同的实现点:
指定时间开始触发,不重复:
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // some Date
.forJob("job1", "group1") // identify job with name, group strings
.build();
指定时间触发,每隔10秒执行一次,重复10次:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.forJob(myJob) // identify job with handle to its JobDetail itself
.build();
5分钟以后开始触发,仅执行一次:
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future
.forJob(myJobKey) // identify job with its JobKey
.build();
立即触发,每个5分钟执行一次,直到22:00:
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
在下一小时整点触发,每个2小时执行一次,一直重复:
trigger = newTrigger()
.withIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
.startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
// note that in this example, 'forJob(..)' is not called which is valid
// if the trigger is passed to the scheduler along with the job
.build();
scheduler.scheduleJob(trigger, job);
请查阅TriggerBuilder和SimpleScheduleBuilder提供的方法,以便对上述示例中未提到的选项有所了解。
TriggerBuilder(以及Quartz的其它builder)会为那些没有被显式设置的属性选择合理的默认值。比如:如果你没有调用withIdentity(..)方法,TriggerBuilder会为trigger生成一个随机的名称;如果没有调用startAt(..)方法,则默认使用当前时间,即trigger立即生效。
2、SimpleTrigger Misfire策略
scheduler被停止或者线程不够用的时候,会发生出发错过misfire,SimpleTrigger有几个misfire相关的策略,告诉quartz当misfire发生的时候应该如何处理。这些策略以常量的形式在SimpleTrigger中定义(JavaDoc中介绍了它们的功能)。这些策略包括:SimpleTrigger的Misfire策略常量:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
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都有一个Trigger.MISFIRE_INSTRUCTION_SMART_POLICY策略可以使用,该策略也是所有trigger的默认策略。如果使用smart policy,SimpleTrigger会根据实例的配置及状态,在所有MISFIRE策略中动态选择一种Misfire策略。SimpleTrigger.updateAfterMisfire()的JavaDoc中解释了该动态行为的具体细节。在使用SimpleTrigger构造trigger时,misfire策略作为基本调度(simple schedule)的一部分进行配置(通过SimpleSchedulerBuilder设置):
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever()
.withMisfireHandlingInstructionNextWithExistingCount())
.build();
六、CronTrigger
1、如果你需要的是基于日历表示法的调度,而不是基于指定间隔的简单调度,那么CronTrigger比SimpleTrigger更合适。使用CronTrigger,你可以配置这样的调度:“每周五的中午”,或者“每个工作日的上午9:30”,或者“在一月的每个周一、周三和周五的上午9点到10点之间每隔5分钟”。与SimpleTrigger一样,CronTrigger需要设置startTime属性,表示调度生效的时间,以及(可选的)endTime属性,表示调度的结束时间。Cron表达式用于配置CronTrigger的实例,它由7个字段组成,字段之间由空格分开,它们表示的含义如下:1. 秒 (Seconds)2. 分钟 (Minutes)3. 小时 (Hours)4. 日(一个月的一天) (Day-of-Month)5. 月份 (Month)6. 周(一周的一天) (Day-of-Week)7. 年份(可选的) (Year)
一个完整的Cron表达式的例子如字符串:”0 0 12 ? * WED” - 表示“每周三的中午12:00:00”;每一个字段可以包含范围或者列举。比如,上例中的周字段(即”WED“)可以被替换为:”MON-FRI”, “MON,WED,FRI”, 或者”MON-WED,SAT”。通配符()表示该字段上每一个可取的值。因此,上例中月字段上的表示“每个月”。周字段上的*表示“一周的每一天”。所有的字段都有一些可取的值的集合。有些就很明显 - 比如,秒和分钟字段可以取0到59的整数,小时字段可以取0到23的整数,日字段(Day-of-Month)可以取1到31的整数,不过你需要注意指定的月份到底有多少天。月字段可以取0到11之间的整数,或者使用字符串表示:JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 和 DEC。周字段(Days-of-Week)可以取的值为1到7(1表示Sunday),或者使用字符串表示:SUN, MON, TUE, WED, THU, FRI and SAT。字符‘/’表示增量。比如,分钟字段使用”0/15“,表示”在这个小时内,从0分钟开始,每隔15分钟“;如果分钟字段使用”3/20“,则表示”在这个小时内,从第3分钟开始,每隔20分钟“,这与在分钟字段使用“3,23,43”表示的含义是一样的。注意一个细节,“/35”的含义并不是“每隔35分钟”,而是“在这个小时内,从第0分钟开始,每隔35分钟”。字符’?’可以用于日字段和周字段,表示“没有具体的值“。它主要用于在这两个字段的某一个中指定某个值,更好的理解请参考下面的示例(以及CrontTrigger的JavaDoc)。字符’L’可以用于日字段和周字段,它是last的缩写,但是用于这两个字段时,含义并不相同。比如,“L”用于日子段,表示“这个月的最后一天” - 即一月的第31天,非闰年二月的第28天。如果‘L’单独用于周字段,它的含义就是“7”或者“SAT”。但是,如果‘L’用于周字段时,前面还有一个值,则表示“这个月的最后一个周××” - 比如,“6L”或“FRIL”表示“这个月的最后一个周五”。我们也可以指定与这个月最后一天的偏移量,比如“L-3”表示与这个月的最后一天相差3天。需要注意的是,如果使用选项‘L’,则不要使用列举和范围,否则结果将是不可预料的。字符’W’表示离某一天最近的工作日(weekday)。例如,日字段上的值“15W”表示“离这个月的15号最近的工作日”。字符’#’表示“这个月的第n个周××”。例如,在周字段上的“6#3”或“FRI#3”表示“这个月的3个周五”。以下为一些表达式的例子及对应的含义 - 在org.quartz.CronExpression的JavaDoc有更多的描述。Cron表达式示例:
CronTrigger示例1 - 每隔5分钟执行一次:0 0/5 * * * ?
CronTrigger示例2 - 每隔5分钟,且在该分钟的第10秒执行(如10:00:10 am, 10:05:10 am等):10 0/5 * * * ?
CronTrigger示例3 - 每周三和周五的10:30, 11:30, 12:30和13:30执行:0 30 10-13 ? * WED,FRI
Crontrigger示例4 - 在每个月的第5天和第20天,上午8点到上午10点之间每隔半小时执行,注意:该trigger不会在上午10点执行,只在上午8:00, 8:30, 9:00和9:30执行:0 0/30 8-9 5,20 * ?
提示一下,有些调度需求太复杂了,无法使用一个trigger表达 - 比如:“在上午9点和上午10点之间,每隔5分钟执行一次,且在下午1点到下午10点之间每隔20分钟执行一次”。应对这样的需求,可以创建两个trigger,注册到同一个job上即可。
2、创建CronTrigger:
CronTrigger实例可以通过TriggerBuilder(配置主要属性)和CronScheduleBuilder(配置CronTrigger专有的属性)配置。为了以DSL风格使用这些builder,需要静态导入:
import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.DateBuilder.*:
构建一个trigger,在每天的上午8点到下午5点之间每隔2分钟执行一次,如:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
构建一个trigger,每天上午10:42执行:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
或者
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 * * ?"))
.forJob(myJobKey)
.build();
构建一个trigger,在每周三的上午10:42执行,并配置TimeZone:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42)
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles")))
.forJob(myJobKey)
.build();
3、CronTriggter错过触发策略
下面的这些策略用于告诉Quartz,如果CronTrigger错过触发时应该采取的处理方式。
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW
所有的trigger都可以使用Trigger.MISFIRE_INSTRUCTION_SMART_POLICY策略,该策略也是所有trigger的默认策略。在CronTrigger中,’smart policy’策略其实就是MISFIRE_INSTRUCTION_FIRE_NOW. CronTrigger.updateAfterMisfire()的API文档中对该策略有更详细的解释。
在构造CronTrigger时,将错过触发策略作为调度的一部分进行配置(通过CronScheduleBuilder):
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?")
.withMisfireHandlingInstructionFireAndProceed())
.forJob("myJob", "group1")
.build();