与job一样,trigger也很容易使用,但是还有一些扩展选项需要理解,以便更好地使用quartz。trigger也有很多类型,我们可以根据实际需要来选择。
最常用的两种trigger:SimpleTrigger和CronTrigger。

1.Trigger的属性

(1)Trigger的公共属性

所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。
trigger的公共属性有:

  • jobKey属性:当trigger触发时被执行的job的身份;
  • startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
  • endTime属性:表示trigger失效的时间点。比如,“每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。

其它的属性,会在下文中解释。

(2)优先级

image.png
如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。
注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。
注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。

(3)错过触发(misfire Instructions)

image.png
trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息。当你在项目中使用Quartz时,你应该对各种类型的trigger的misfire机制都比较熟悉,这些misfire机制在JavaDoc中有说明。关于misfire机制的细节,会在讲到具体的trigger时作介绍。

(4)日历示例(calendar)

Quartz的Calendar对象(不是java.util.Calendar对象)

可以在定义和存储trigger的时候与trigger进行关联。Calendar用于从trigger的调度计划中排除时间段。比如,可以创建一个trigger,每个工作日的上午9:30执行,然后增加一个Calendar,排除掉所有的商业节日。
任何实现了Calendar接口的可序列化对象都可以作为Calendar对象,Calendar接口如下:
image.png

  1. public interface Calendar extends java.io.Serializable, java.lang.Cloneable {
  2. int MONTH = 0;
  3. /**
  4. * 设置一个基础的calendar或者移除一个现存的
  5. */
  6. void setBaseCalendar(Calendar baseCalendar);
  7. /**
  8. * 得到一个基础的 calendar. 若未设置,值为空
  9. */
  10. Calendar getBaseCalendar();
  11. /**
  12. * 决定给定的时间(用毫秒数)是否被日历包含
  13. */
  14. boolean isTimeIncluded(long timeStamp);
  15. /**
  16. * 决定下个时间是否可以被Calendar的所包含,在给定的时间后
  17. */
  18. long getNextIncludedTime(long timeStamp);
  19. /**
  20. * 通过他的创建者返回一个给定Calendar实例的描述
  21. *
  22. * @return 如果未设置则返回值为空
  23. */
  24. String getDescription();
  25. /**
  26. * 设置Calendar实例的描述 - 可以用来记忆和展示Calendar通过描述.
  27. */
  28. void setDescription(String description);
  29. Object clone();
  30. }

注意到这些方法的参数类型为long。你也许猜到了,他们就是毫秒单位的时间戳。即Calendar排除时间段的单位可以精确到毫秒。你也许对“排除一整天”的Calendar比较感兴趣。Quartz提供的org.quartz.impl.HolidayCalendar类可以很方便地实现。
Calendar必须先实例化,然后通过addCalendar()方法注册到scheduler。如果使用HolidayCalendar,实例化后,需要调用addExcludedDate(Date date)方法从调度计划中排除时间段。以下示例是将同一个Calendar实例用于多个trigger:

  1. HolidayCalendar cal = new HolidayCalendar();
  2. cal.addExcludedDate( someDate );
  3. cal.addExcludedDate( someOtherDate );
  4. sched.addCalendar("myHolidays", cal, false);
  5. Trigger t = newTrigger()
  6. .withIdentity("myTrigger")
  7. .forJob("myJob")
  8. .withSchedule(dailyAtHourAndMinute(9, 30)) // 每天9.30执行job
  9. .modifiedByCalendar("myHolidays") // 节假日除外
  10. .build();
  11. // .. schedule job with trigger
  12. Trigger t2 = newTrigger()
  13. .withIdentity("myTrigger2")
  14. .forJob("myJob2")
  15. .withSchedule(dailyAtHourAndMinute(9, 30)) // 每天9.30执行job
  16. .modifiedByCalendar("myHolidays") // 节假日除外
  17. .build();
  18. // .. schedule job with trigger2

2.SimpleTrigger

(1)SimpleTrigger介绍

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的静态方法,需要静态导入:

  1. import static org.quartz.TriggerBuilder.*;
  2. import static org.quartz.SimpleScheduleBuilder.*;
  3. import static org.quartz.DateBuilder.*:

下面的例子,是基于简单调度(simple schedule)创建的trigger。建议都看一下,因为每个例子都包含一个不同的实现点:
指定时间开始触发,不重复:

  1. SimpleTrigger trigger = (SimpleTrigger) newTrigger()
  2. .withIdentity("trigger1", "group1")
  3. .startAt(myStartTime) // 时间
  4. .forJob("job1", "group1") // job和group name
  5. .build();

指定时间触发,每隔10秒执行一次,重复10次:

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .startAt(myTimeToStartFiring) // 若为给定开始时间,默认为当前时间
  4. .withSchedule(simpleSchedule()
  5. .withIntervalInSeconds(10)
  6. .withRepeatCount(10)) // 重复10次共11次
  7. .forJob(myJob) // 表明job
  8. .build();

5分钟以后开始触发,仅执行一次:

  1. trigger = (SimpleTrigger) newTrigger()
  2. .withIdentity("trigger5", "group1")
  3. .startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future
  4. .forJob(myJobKey) // identify job with its JobKey
  5. .build();

立即触发,每个5分钟执行一次,直到22:00:

  1. trigger = newTrigger()
  2. .withIdentity("trigger7", "group1")
  3. .withSchedule(()
  4. .withIntervalInMinutes(5)
  5. .repeatForever())
  6. .endAt(dateOf(22, 0, 0))
  7. .build();

建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:

  1. trigger = newTrigger()
  2. .withIdentity("trigger8") // 未指定group名称,trigger8将会在一个默认的group中
  3. .startAt(evenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
  4. .withSchedule(simpleSchedule()
  5. .withIntervalInHours(2)
  6. .repeatForever())
  7. // note that in this example, 'forJob(..)' is not called which is valid
  8. // if the trigger is passed to the scheduler along with the job
  9. .build();
  10. scheduler.scheduleJob(trigger, job);

(2)SimpleTrigger Misfire策略

SimpleTrigger有几个misfire相关的策略,告诉quartz当misfire发生的时候应该如何处理。(Misfire策略参考教程四:Trigger介绍)。这些策略以常量的形式在SimpleTrigger中定义(JavaDoc中介绍了它们的功能)。这些策略包括:
SimpleTrigger的Misfire策略常量:

  1. MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
  2. MISFIRE_INSTRUCTION_FIRE_NOW
  3. MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
  4. MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
  5. MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
  6. 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设置):

  1. trigger = newTrigger()
  2. .withIdentity("trigger7", "group1")
  3. .withSchedule(simpleSchedule()
  4. .withIntervalInMinutes(5)
  5. .repeatForever()
  6. .withMisfireHandlingInstructionNextWithExistingCount())
  7. .build();

3.CronTrigger

CronTrigger通常比Simple Trigger更有用,如果需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。
使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。

(1)Cron Expressions

Cron-Expressions用于配置CronTrigger的实例。Cron Expressions是由七个子表达式组成的字符串,用于描述日程表的各个细节。这些子表达式用空格分隔,并表示:

  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
  7. Year (optional field)

一个完整的Cron-Expressions的例子是字符串“0 0 12 ?* WED” — 这意味着“每个星期三下午12:00”。
单个子表达式可以包含范围和/或列表。例如,可以用“MON-FRI”,“MON,WED,FRI”或甚至“MON-WED,SAT”代替前一个(例如“WED”)示例中的星期几字段。

通配符(’ ‘字符)可用于说明该字段的“每个”可能的值。因此,前一个例子的“月”字段中的“”字符仅仅是“每个月”。因此,“星期几”字段中的“*”显然意味着“每周的每一天”。

所有字段都有一组可以指定的有效值。这些值应该是相当明显的 - 例如秒和分钟的数字0到59,数小时的值0到23。日期可以是1-31的任何值,但是需要注意在给定的月份中有多少天!月份可以指定为0到11之间的值,或者使用字符串JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。星期几可以指定为1到7(1 =星期日)之间的值,或者使用字符串SUN,MON,TUE,WED,THU,FRI和SAT。

‘/‘字符可用于指定值的增量。例如,如果在“分钟”字段中输入“0/15”,则表示“每隔15分钟,从零开始”。如果您在“分钟”字段中使用“3/20”,则意味着“每隔20分钟,从三分钟开始” - 换句话说,它与“分钟”中的“3,23,43”相同领域。请注意“ / 35”的细微之处并不代表“每35分钟” - 这意味着“每隔35分钟,从零开始” - 或者换句话说,与指定“0,35”相同。

‘?’ 字符是允许的日期和星期几字段。用于指定“无特定值”。当您需要在两个字段中的一个字段中指定某个字符而不是另一个字段时,这很有用。请参阅下面的示例(和CronTrigger JavaDoc)以进行说明。

“L”字符允许用于月日和星期几字段。这个角色对于“最后”来说是短暂的,但是在这两个领域的每一个领域都有不同的含义。例如,“月”字段中的“L”表示“月的最后一天” - 1月31日,非闰年2月28日。如果在本周的某一天使用,它只是意味着“7”或“SAT”。但是如果在星期几的领域中再次使用这个值,就意味着“最后一个月的xxx日”,例如“6L”或“FRIL”都意味着“月的最后一个星期五”。您还可以指定从该月最后一天的偏移量,例如“L-3”,这意味着日历月份的第三个到最后一天。当使用’L’选项时,重要的是不要指定列表或值的范围,因为您会得到混乱/意外的结果。

“W”用于指定最近给定日期的工作日(星期一至星期五)。例如,如果要将“15W”指定为月日期字段的值,则意思是:“最近的平日到当月15日”。

‘#’用于指定本月的“第n个”XXX工作日。例如,“星期几”字段中的“6#3”或“FRI#3”的值表示“本月的第三个星期五”。
以下是一些表达式及其含义的更多示例 - 可以在JavaDoc中找到更多的org.quartz.CronExpression

(2)Cron Expressions示例

CronTrigger示例1 - 创建一个触发器的表达式,每5分钟就会触发一次
“0 0/5 ?”
CronTrigger示例2 - 创建触发器的表达式,每5分钟触发一次,分钟后10秒(即上午10时10分,上午10:05:10等)。
“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点之间每半小时触发一次。请注意,触发器将不会在上午10点开始,仅在8:00,8:30,9:00和9:30
“0 0/30 8-9 5,20
?”
请注意,一些调度要求太复杂,无法用单一触发表示 - 例如“每上午9:00至10:00之间每5分钟,下午1:00至晚上10点之间每20分钟”一次。在这种情况下的解决方案是简单地创建两个触发器,并注册它们来运行相同的作业。

(3)构建CronTriggers

CronTrigger实例使用TriggerBuilder(用于触发器的主要属性)和CronScheduleBuilder(对于CronTrigger特定的属性)构建。要以DSL风格使用这些构建器,请使用静态导入:

  1. import static org.quartz.TriggerBuilder.*;
  2. import static org.quartz.CronScheduleBuilder.*;
  3. import static org.quartz.DateBuilder.*:

建立一个触发器,每隔两分钟,每天上午8点至下午5点之间:

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
  4. .forJob("myJob", "group1")
  5. .build();

建立一个触发器,将在上午10:42每天发射:

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .withSchedule(dailyAtHourAndMinute(10, 42))
  4. .forJob(myJobKey)
  5. .build();

或者:

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .withSchedule(cronSchedule("0 42 10 * * ?"))
  4. .forJob(myJobKey)
  5. .build();

建立一个触发器,将在星期三上午10:42在TimeZone(系统默认值)之外触发:

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42))
  4. .forJob(myJobKey)
  5. .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
  6. .build();

或者:

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .withSchedule(cronSchedule("0 42 10 ? * WED"))
  4. .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
  5. .forJob(myJobKey)
  6. .build();

(4)CronTrigger Misfire说明

以下说明可以用于通知Quartz当CronTrigger发生fire时应该做什么。(本教程“更多关于触发器”部分引入了fire情况)。这些指令定义为CronTrigger本身的常量(包括描述其行为的JavaDoc)。说明包括:
CronTrigger的Misfire指令常数:

  1. MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
  2. MISFIRE_INSTRUCTION_DO_NOTHING
  3. MISFIRE_INSTRUCTION_FIRE_NOW

所有触发器还具有可用的Trigger.MISFIRE_INSTRUCTION_SMART_POLICY指令,并且该指令也是所有触发器类型的默认值。“SMART_POLICY”指令由CronTrigger解释为MISFIRE_INSTRUCTION_FIRE_NOW。CronTrigger.updateAfterMisfire()方法的JavaDoc解释了此行为的确切细节。
在构建CronTriggers时,您可以将misfire指令指定为简单计划的一部分(通过CronSchedulerBuilder):

  1. trigger = newTrigger()
  2. .withIdentity("trigger3", "group1")
  3. .withSchedule(cronSchedule("0 0/2 8-17 * * ?")
  4. .withMisfireHandlingInstructionFireAndProceed())
  5. .forJob("myJob", "group1")
  6. .build();