Quartz2.2.x官方教程

零、Quartz是什么?能干什么?

Quartz是一个开源的任务调度框架。基于定时、按期的策略来执行任务是它的核心功能,好比xx月的每一个星期五上午8点到9点,每隔10分钟执行1次。Quartz3个核心要素:调度器(Scheduler)、任务(Job)、触发器(Trigger)Quartz彻底使用Java开发,能够集成到各类规模的应用程序中。它可以承载成千上万的任务调度,而且支持集群。它支持将数据存储到数据库中以实现持久化,并支持绝大多数的数据库。它将任务与触发设计为松耦合,即一个任务能够对应多个触发器,这样可以轻松构造出极为复杂的触发策略。html

本文是对Quartz Job Scheduler Tutorials的全文翻译,做为笔者本身的学习笔记。当前日期是2016220日,最新版本是2.2.x,官方在线文档的后续更新本文再也不跟进。java

1、 使用Quartz

在使用调度器以前,它须要被实例化,你可使用SchedulerFactory来实现。部分Quartz 用户可能会在JNDI 存储中保存一个factory 实例,而部分用户可能会发现直接初始化并使用factory 实例是很简单的(就像例子中那样)。数据库

调度器实例化之后,它能够被启动,置为备用模式或中止。注意一旦它被中止是不能从新启动的,除非从新初始化。在调度器没启动时触发器是不会激活的,在暂停状态下也不会。安全

下面是一个示例代码片断,演示了实例化并启动调度器,而后调度任务执行。服务器

 

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

  Scheduler sched = schedFact.getScheduler();

  sched.start();

  // define the job and tie it to our HelloJob class

  JobDetail job = newJob(HelloJob.class)

      .withIdentity("myJob", "group1")

      .build();

  // Trigger the job to run now, and then every 40 seconds

  Trigger trigger = newTrigger()

      .withIdentity("myTrigger", "group1")

      .startNow()

      .withSchedule(simpleSchedule()

          .withIntervalInSeconds(40)

          .repeatForever())

      .build();

  // Tell quartz to schedule the job using our trigger

  sched.scheduleJob(job, trigger);

 

如你所见,quartz 的使用很是简单,在第2节咱们将简要介绍任务和触发器,以及QuartzAPI,到时候你就能对示例有更深的理解。网络

 

2、 核心API以及任务和触发器介绍

2.1 Quartz API

Quartz API主要包含如下接口:并发

Scheduler:与调度器交互的主要API负载均衡

Job:一个接口,实现该接口的组件将被调度器运行。框架

JobDetail:用于定义Job实例。ide

Trigger:定义了一个Job如何被调度器所运行。

JobBuilder:用于定义/构建JobDetail 实例。

TriggerBuilder:用于定义/构建Trigger实例。

SchedulerSchedulerFactory建立,并随着shutdown方法的调用而终止。建立后它将可被用来添加、删除或列出JobTrigger,或执行一些调度相关的工做,(好比暂停)。只有经过start()方法启动后它才会真的工做。

Quartz提供一些列的Builder类来定义领域特定语言(也被称为流接口)。从示例代码中能看到,Job以及Trigger均可以经过Builder来建立。

各类ScheduleBuilder包含了建立各类类型调度器的方法。DateBuilder 类包含方便地建立指定时间点的日历实例(好比表示下一个整时的时间)的各类方法。

2.2 任务和触发器

Job是一个实现了Job接口的类,它只有execute这一个方法。它的形式以下所示:

  package org.quartz;

  public interface Job {
    public void execute(JobExecutionContext context)
      throws JobExecutionException;
  }

 

JobTrigger激活时,该方法将被Scheduler的一个线程调用执行。JobExecutionContext 是传递给该方法的运行时环境信息,包括调用它的调度器、触发该执行的TriggerJobDetail对象以及其余信息。

JobDetail是在Job被添加到Scheduler时由应用程序建立的,它包含了关于Job的各类属性信息,都在JobDataMap中。

Trigger用于触发任务的执行。它也会关联到的一个JobDataMap--当须要把数据传递给触发器特定的某个任务时这颇有用。Quartz提供了各类触发器,然而最经常使用的是SimpleTrigger CronTrigger

SimpleTrigger是很是好用的,若是你只须要让任务在指定的时间执行,或者让它在指定的时间执行,重复N次,以T为周期。CronTrigger用于进行相似于日历时间的触发,好比每一个周五的下午,或者每月10号的10点。

咱们将任务和触发器设计为互相独立的,这种松耦合有许多好处:任务能够被建立和存储而不依赖于触发器,而且一个任务能够关联到多个触发器。另外,即便触发器已通过期,关联的任务仍然能够被从新配置,而不须要从新定义;一样,你也能够对触发器进行修改或替换而不须要从新定义关联的任务。

2.3 身份识别

任务和触发器注册到调度器时都会有一个识别KEY。任务和触发器均可以添加到组,所以它们的KEY名称在同一个组内必须是惟一的,完整的KEY名由KEY+组名组成。

到这里你可能会对触发器和调度器有一个大概的了解,在下面两章将详细介绍。

3、Job & JobDetail更多细节

虽然你的Job代码知道如何执行真实的工做,可是Quartz须要被告知

那个任务所拥有的各类属性值,这是经过JobDetail提供的。JobDetailJobBuilder类构造。

构造器每次执行execute方法时,它都会先建立一个新的实例,这一行为的一个影响是Job必须有一个无参数构造方法;另外一个影响是,Job类所定义的的状态数据是无心义的,由于它们在执行时没法被保存。

如今你可能会问,如何向Job实例提供属性/配置?如何在执行函数中保存状态数据?答案是JobDataMap,它是JobDetail对象的一部分。

3.1 JobDataMap

JobDataMapJAVA Map接口的一个实现,并添加了一些实现用于方便地存取基本类型数据。它可以存储任意规模的,你想要由Job在执行时使用的数据。

下面是一个在定义JobDetail时为JobDataMap添加数据的例子:

 // define the job and tie it to our DumbJob class

  JobDetail job = newJob(DumbJob.class)

      .withIdentity("myJob", "group1") // name "myJob", group "group1"

      .usingJobData("jobSays", "Hello World!")

      .usingJobData("myFloatValue", 3.141f)

      .build();

 

下面是一个任务执行时从JobDataMap获取数据的例子:

public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();
      JobDataMap dataMap = context.getJobDetail().getJobDataMap();
      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");
      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
  }

 

Triggers也能够与JobDataMap进行关联。比方说,当一个任务关联到多个触发器时,你就能够为每个不一样的触发器提供不一样的数据。

任务执行期间,能够由JobExecutionContext getMergedJobDataMap方法获取一个由JobDetail Trigger所提供的合并体的JobDataMap,且后者的同名KEY的值会覆盖前者。固然,你也可使用context.getJobDetail().getJobDataMap()来获取JobDetail 的数据,使用context.getTrigger().getJobDataMap()来获取Trigger的数据。

3.2 Job实例

你能够只建立一个Job,而后建立多个JobDetail ,每个都有本身的属性和JobDataMap,而后把它们都添加到调度器中,这样调度器内部就保存它的多个实例的定义。

好比说,你能够创建一个Job类叫作SalesReportJob,它会指望(经过JobDataMap)传递过来的参数指定了销售报告所针对的人员名称。他们可能会为这个Job建立多个定义,好比SalesReportForJoeSalesReportForMike,分别指定了JoeMike

当触发器激活时,与他关联的JobDetail 被加载,那么相关的Job类将被JobFactory 实例化。默认的JobFactory 只是简单地调用newInstance()方法,而后试图调用类内与JobDataMapKEY名相匹配的setter方法。你可能想创建本身的JobFactory ,以便使你应用程序的IOCDI container 来产生/初始化Job实例。

Quartz语言中,咱们将全部存储的JobDetail 称为“job 定义”或者“JobDetail 实例”;将全部的执行中的任务称为“job 实例”或者“job 定义的实例”;通常状况下若是咱们使用Job这个词咱们是指一个命名的定义或JobDetail 。当咱们描述实现了Job接口的类时,通常会使用“job class”这个词。

3.3任务状态和并发

这里介绍一些关于任务状态和并发的额外的说明。有许多标注能够被添加到job class中,影响到Quartz's 在各方面的行为。

@DisallowConcurrentExecution

这个标注添加到job class中就是告诉Quartz,我不能被并行执行。拿前面的例子来讲,若是SalesReportJob添加了这个标注,那么在同一时间只能有一个SalesReportForJoe的实例在执行,可是SalesReportForMike的实例能够同时执行。这个限制是基于JobDetail而不是 job class的实例。然而它被决定(在Quartz的设计中)要做用于类自身之上,由于它常常会影响到类的编写方式。

@PersistJobDataAfterExecution

它告诉Quartz,在成功执行完(没有抛出异常)以后要更新JobDetailJobDataMap。这样下一次执行时该任务就会收到最新的数据。与@DisallowConcurrentExecution 同样,它也做用于一个 job definition 实例,而不是job类的实例。

@PersistJobDataAfterExecution 

若是使用了本标注,那么你要强烈考虑同时使用DisallowConcurrentExecution 标注,以免当同一个任务的2个实例并行执行时最终保存的数据是什么样的(竞争条件下)这种冲突。

3.4任务的其余属性

持久性

若是一个任务不是持久的,当再也不有关联的活动触发器时它将从调度器中被删除。换句话说,非持久任务的生命期取决于它的触发器。

请求恢复

若是一个任务请求恢复,而且在执行中调度器遭到了硬关闭,那么当调度器从新启动时它将从新执行。在这种状况下,JobExecutionContext.isRecovering()将返回true

3.5任务执行异常

最后,咱们要告诉你Job.execute(..)的一些细节。在该方法中你只被容许抛出JobExecutionException这一种异常(包括运行时异常)。所以,你一般要把全部代码包裹在try-catch块中。你还须要花一点时间来查看JobExecutionException的文档,以便于你可以向调度器发出各类指令来根据你的意愿来处理异常。

 

4、 触发器更多细节

TriggersJob同样的易于使用,可是它包含了大量的可定制的选项。前面提到过,有不一样类型的Triggers可供你选择以知足不一样的需求。

4.1 触发器通用属性

除了用于标识身份的TriggerKey 外,还有许多各类Triggers所通用的属性,它们是在建立Triggers定义时经过TriggerBuilder 设置的。

下面列出这些通用属性:

jobKey:指定了Triggers激活时将被执行的job的身份;

startTime:指定了调度器什么时候开始做用。该值是一个java.util.Date对象。某些类型的Triggers确实会在startTime激活,而另外一些Triggers仅简单地标记下调度器未来应当启动的时间。这意味着你能够存储一个Trigger和一个“1月的第5天”这样的调度器,若是startTime设在了41号,那么第一次启动时间要在几个月之后。

endTime:指定了Trigger的调度器在什么时候再也不生效。换句话说,一个触发器的若是设置为“每个月的第5天”而且endTime为“71那么它最后一次激活应该是在65日。

其它属性在之后的章节中介绍。

4.2 优先级

有时候当你有许多触发器(或者Quartz 线程池中的线程不多)时,Quartz 可能没有足够的资源来同时激活全部的触发器。这时,你可能想要控制让哪个触发器先被触发。出于这个目的,你能够设置触发器的priority 属性。若是有N

个触发器将要同时被激活,然而Quartz 只有Z个线程,那么只有前Z个优先级最高的触发器将被激活。若是没有设置,优先级的默认值为5。优先级的取值适用于全部整数,包括正数和负数。

注意:仅当触发器将同时被激活时才会比较优先级。设定在10:59触发器永远比设定在11:00触发器先激活。

注意:若是检测到一个job要求恢复,那么恢复后的优先级不变。

4.3 激活失败指令(Misfire Instructions

触发器的另外一个重要属性是激活失败指令。当一个持久的触发器由于调度器被关闭或者线程池中没有可用的线程而错过了激活时间时,就会发生激活失败(Misfire)。不一样类型的触发器具备不一样的激活失败指令。默认状况下它们使用一个“聪明策略”指令,它具备基于触发器类型和配置的动态行为。当调度器启动时,它会搜索全部激活失败的持久触发器,而后根据各自已配置的激活失败指令来对它们进行更新。当你在项目中使用Quartz时,你应当熟悉相应触发器的激活失败指令,它们在JavaDoc中有解释。在本教程针对各类触发器的章节中有更详细的介绍。

4.4 日历

Quartz Calendar 对象(不是java.util.Calendar对象)能够在触发器被定义时被关联并存储到调度器。在从触发器的激活策略中排除时间块时Calendar 很是有用。好比说,你能够添加一个触发器,它在天天早上的9:30激活,而后添加一个Calendar 来排除掉全部的商业假日。

Calendar 能够是任何实现了Calendar 接口的可序列化对象。Calendar 接口以下所示:

package org.quartz;

public interface Calendar {

  public boolean isTimeIncluded(long timeStamp);

  public long getNextIncludedTime(long timeStamp);

}

 

注意上述方法的参数类型是long,就像你猜测的那样,它们是毫秒单位的时间戳。这意味着Calendar 可以以毫秒的精度来排除时间块。你极可能会对排除整日感兴趣,为了方便起见,Quartz 包含了org.quartz.impl.HolidayCalendar,它能够实现这个。

Calendars 必须被实例化并使用调度器的addCalendar(..) 进行注册。若是你使用HolidayCalendar,在初始化之后你必须使用 addExcludedDate(Date date) 来生成你想要排除的日期。同一个Calendar实例能够被不一样的触发器使用,就像下面这样:

Calendar Example

HolidayCalendar cal = new HolidayCalendar();

cal.addExcludedDate( someDate );

cal.addExcludedDate( someOtherDate );

sched.addCalendar("myHolidays", cal, false);

Trigger t = newTrigger()
    .withIdentity("myTrigger")
    .forJob("myJob")
    .withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
    .modifiedByCalendar("myHolidays") // but not on holidays
    .build();
// .. schedule job with trigger

Trigger t2 = newTrigger()
    .withIdentity("myTrigger2")
    .forJob("myJob2")
    .withSchedule(dailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
    .modifiedByCalendar("myHolidays") // but not on holidays

    .build();
// .. schedule job with trigger2

 

触发器的建立/构造将在后面的章节中介绍,这里你只须要记住上面的代码生成了2触发器,均在天天激活。然而,排除日期中的激活都会被跳过。

5、 SimpleTrigger

SimpleTrigger 可以知足你这样的需求:你但愿job在某一个特定的时间执行,或者在某刻执行后以一个指定的周期进行重复。好比说,你但愿某个任务在2015113日早上11:23:54执行,或者在该时间执行后每隔10秒又重复执行5次。

经过上面的描述,你不难发现SimpleTrigger 的属性应当包含:start-timeend-time, repeat count, repeat interval

repeat count能够是0或者一个正整数,或者SimpleTrigger.REPEAT_INDEFINITELY值。repeat interval必须是0或者一个正的长整型数,表明毫秒数。注意,0表示任务的重复将并行(或以调度器的管理能力近似并行)执行。

若是你对QuartzDateBuilder类还不熟悉,你会发现它对于根据startTime ( endTime)计算触发器的激活时间很是有用。

endTime属性(若是被设置)将会覆盖repeat count属性。这颇有用,若是你想让触发器每隔10秒触发一次直到给定的时间--相对于你本身根据startTime endTime来计算repeat count,你能够直接设置endTime,而后将 repeat count设置为REPEAT_INDEFINITELY 

SimpleTrigger 的实例经过TriggerBuilder(包括SimpleTrigger的主要属性) SimpleScheduleBuilder (包括SimpleTrigger特有属性)来构建,为了以DSL风格引用以上类,使用以下的静态引用:

import static org.quartz.TriggerBuilder.*;

import static org.quartz.SimpleScheduleBuilder.*;

import static org.quartz.DateBuilder.*:

 

下面是各类使用简单构造器来定义触发器的例子,把它们阅读完,由于每一个都展现了不一样的方面。

构造一个构造器,在指定时刻激活,没有重复:

  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分钟后激活1次:

  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);

 

花点时间看看TriggerBuilderSimpleScheduleBuilder全部能够函数,这样可以熟悉以上例子没有介绍到的可能会对你因为的选项。

注意,TriggerBuilder(以及Quartz的其它builder)一般会为你没有明确指定的属性选择一个合理的值。好比:若是你没有调用*withIdentity(..)*方法,那么TriggerBuilder 将为你的触发器生成一个随机的名字;若是你没有调用 *startAt(..)*方法,那么当前时间(马上激活)将被赋值。

5.1 SimpleTrigger 激活失败指令

SimpleTrigger 有不少指令可用于通知Quartz 当发生激活失败时应如何处理(激活失败已经在第4节中介绍)。这些指令被定义为SimpleTrigger类的常数(JavaDoc描述了它们的行为)。这些指令包括:

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.MISFIRE_INSTRUCTION_SMART_POLICY指令,这是全部类型的触发器的默认指令。

若是使用了“聪明策略”,SimpleTrigger 将根据配置和触发器实例的状态从它的指令中动态选择。JavaDoc关于SimpleTriggerImpl .updateAfterMisfire() 方法的介绍解释了这一动态行为的细节,具体以下。

Repeat Count=0instruction selected = MISFIRE_INSTRUCTION_FIRE_NOW;

Repeat Count=REPEAT_INDEFINITELYinstruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

Repeat Count>0instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

6、 CronTriggers

若是你须要的调度器的任务循环是基于相似于日历那种,而不是以一个明确的周期为依据,那么CronTriggers每每比SimpleTrigger更有用。

有了CronTrigger,你能够指定“每一个周五下午”或者“每一个工做日早上9:30”,甚至是“一月的每一个周一,周二和周五早上9:00--10:00之间每隔5分钟1”这样的调度器。即便如此,与SimpleTrigger同样,CronTrigger也有一个startTime和一个(可选的)endTime,指定了调度器在应该在什么时候启动和中止。

6.1 Cron表达式

Cron表达式(Cron Expressions)用于配置CronTrigger实例。它是由7个子表达式组成的字符串,指定了调度器的每个细节。这些子表达式由空格分隔,分别表明如下内容:

星期

年(可选)

 "0 0 12 ? * WED"为例,它表示每个月每一个周二的12点。每一个子表达式均可以包含范围与/或列表。好比,前例中的WED部分能够替换为 "MON-FRI", "MON,WED,FRI", 甚至"MON-WED,SAT"

掩码(*)表明任何容许的值。所以,“月”区域的*表示每月;“星期”区域的*表示一周的每一天。

全部区域都有一些可分配的有效值,这些值都是很是显而易见的。

 斜杠('/')表示值的增长。好比说,若是在“分钟”区域填写'0/15',它表示从0分开始并每隔15分钟。 '3/20'表示从3分开始并 每隔20分钟,即0323,43分。 "/35"等同于"0/35"。注意,原文档说,“ "/35"不表明每隔35分钟,而是每一个小时的第35分,及等同于'0,35'。”经测试发现,该说法不正确。应等同于"0/35"。

问号 '?' 可被“日”和“星期”域使用。它表示没有指定值。当你指定了这俩域的其中一个时,另外一个域就可使用问号。

 字母'L' 可被“日”和“星期”域使用。它是last的缩写,可是在这两个域中有着不一样的含义。好比,“日”区域中的"L"表示本月的最后一天,好比1月的31日或者平年2月的28日。若是只有它本身用在“星期”域中,它表示7或者星期六。若是在“星期”域跟在某个星期的后面,那它表示本月的上一个xx日。好比,"6L" 或者 "FRIL"都表示本月上一个星期五。你也能够为本月的最后一天指定个偏移量,好比 "L-3"表示本月的倒数第3天。当使用L选项时,很是重要的一点是不要同时指定列表或范围,不然你会获得混乱的结果。

字母'W'用于指定距离某天最近的工做日(周一到周五)。好比,你在“日”区域使用了"15W",那它表示距离本月15号最近的工做日。

井号 '#' 用于本月第xx工做日。好比,“星期”域的"6#3" or "FRI#3"表示本月第3个星期五。

6.2 Cron表达式实例

下面是一些表达式实例及其含义。你能够在JavaDocorg.quartz.CronExpression部分找到更多内容。

CronTrigger Example 1"0 0/5 * * * ?"

天天,从00分开始每隔5分钟;

CronTrigger Example 2"10 0/5 * * * ?"

天天,从00分开始每隔5分钟,在该分钟的10秒;

CronTrigger Example 3"0 30 10-13 ? * WED,FRI"

每个月星期二和星期五上午,10点至13点,期间的每一个半点;

CronTrigger Example 4"0 0/30 8-9 5,20 * ?"

每个月5号和20号,早上8点至9点,期间的每一个半点;注意不包含10:00

 

注意,有一些调度要求用一个触发器来表达可能过于复杂,好比“早上9点至10点每隔5分钟,以及早上10点至13点每隔20分钟”。这时你能够构造2个触发器,而后绑定到同一个任务上。

6.3 构造CronTrigger

CronTrigger 的实例经过TriggerBuilder(包括CronTrigger 的主要属性) CronScheduleBuilder (包括CronTrigger 特有属性)来构建,为了以DSL风格引用以上类,使用以下的静态引用:

import static org.quartz.TriggerBuilder.*;

import static org.quartz.CronScheduleBuilder.*;

import static org.quartz.DateBuilder.*:

 

构造一个天天上午8点到下午五点之间每隔2分钟的CronTrigger 

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

 

构造一个天天上午10:42触发CronTrigger 

 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();

 

构造一个非系统默认时区内每周二上午10:42触发CronTrigger 

 

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

 

或者

 

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

 

 

6.4 CronTrigger激活失败指令

下列实例用于CronTrigger发生激活失败(misfire)时通知Quartz 如何处理。这些指令被定义为CronTrigger类内部的常量,包括:

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

MISFIRE_INSTRUCTION_DO_NOTHING

MISFIRE_INSTRUCTION_FIRE_NOW

如前所述,Trigger.MISFIRE_INSTRUCTION_SMART_POLICY依然是CronTrigger的默认指令,然而它默认选择MISFIRE_INSTRUCTION_FIRE_NOW做为执行策略。更详细的解释请查看CronTriggerImpl类的updateAfterMisfire函数。

CronTrigger激活失败指令在构造CronTrigger实例时指定。

7、 触发器监听器与任务监听器

触发器监听器(TriggerListeners)任务监听器(JobListeners )分别接收关于TriggerJob的事件。触发器相关的事件包括:触发器激活、激活失败以及激活完成(执行的任务运行完毕)。

TriggerListener 接口形式以下:

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);
}

 

任务相关的事件包括:任务即将被执行的通知、任务执行完毕的通知。JobListener 接口形式以下:

public interface JobListener {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,

            JobExecutionException jobException);

}

 

 

7.1 使用你本身的监听器

实现TriggerListener/JobListener便可实现你本身的监听器。监听器须要注册到调度器,且必须给他一个名称(或者说它们必须可以经过其getName方法来获取其名称)。

为了方便起见,你能够继承JobListenerSupport 类或者TriggerListenerSupport ,而后重载你感兴趣的方法便可。

监听器须要与一个匹配器一块儿注册到调度器,该匹配器用于指定监听器想要接收哪一个触发器/任务的事件。监听器在运行期间注册到调度器,且并无与触发器和任务一块儿存储到JobStore 中。这是由于监听器一般是你应用的一个结合点,所以每次应用运行时它都须要从新注册到调度器。

在指定任务中添加感兴趣的任务监听器方法以下:

scheduler.getListenerManager().addJobListener(myJobListener, KeyMatcher.jobKeyEquals(new JobKey("myJobName", "myJobGroup")));

经过如下对匹配器和Key的静态引用,可使代码更整洁:

import static org.quartz.JobKey.*;

import static org.quartz.impl.matchers.KeyMatcher.*;

import static org.quartz.impl.matchers.GroupMatcher.*;

import static org.quartz.impl.matchers.AndMatcher.*;

import static org.quartz.impl.matchers.OrMatcher.*;

import static org.quartz.impl.matchers.EverythingMatcher.*;

...etc.

 

它使代码变成这样:

scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));

在组中为全部任务添加感兴趣的任务监听器方法以下:

scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));

在两个指定组中为全部任务添加感兴趣的任务监听器方法以下:

scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));

为全部任务添加感兴趣的任务监听器方法以下:

scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

注册TriggerListeners 的方法与以上相同。大多数Quartz用户并不会用到监听器,然而应用程序须要获得事件通知的话它们是很是易用的,并且不须要任务自己显式地通知应用程序。

8、 调度器监听器

调度器监听器(SchedulerListeners)TriggerListeners JobListeners很是相似,除了它是接收来自于调度器本身的事件--不必定与某个特定的triggerjob相关。

Scheduler相关的事件包括:trigger/job的添加与删除、Scheduler内部的严重错误、调度器被关闭的通知,等等。

SchedulerListener 接口形式以下:

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 schedulerStarted();

    public void schedulerInStandbyMode();

    public void schedulerShutdown();

    public void schedulingDataCleared();

}

 

SchedulerListeners 注册到调度器的ListenerManager。基本上任何实现了SchedulerListener 的类均可以做为调度器监听器。

添加SchedulerListener的方法以下:

scheduler.getListenerManager().addSchedulerListener(mySchedListener);

删除SchedulerListener的方法以下:

scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

9、 JobStores

JobStore的职责是记录全部你提供给调度器的“工做数据”:任务、触发器、日历等等。为你的调度器实例选择合适的JobStore的一个很是重要的步骤。幸运的是,一旦你理解了它们之间的不一样,选择起来是很是容易的。你在配置文件中声明将要选择哪一个JobStore,以此将它提供给SchedulerFactory,由于你的调度器实例是由它生成的。

永远不要在代码中直接使用JobStore实例。有的人由于某些缘由想要这样作。JobStore是给Quartz 在后台使用的。你须要(经过配置)告诉Quartz 使用哪个JobStore,而后你只应将代码集中在调度器接口上。

9.1 RAMJobStore

RAMJobStore 是最易于使用的obStore,也是性能最好的(从CPU时间的角度)。RAMJobStore 的名字就很说明问题:它将数据保存在内存中。这就是它为什么快如闪电且已易于配置。它的缺点在于若是你的应用程序结束了或崩溃了,那么全部数据都将丢失,这说明RAMJobStore与触发器和任务设置中的non-volatility并不匹配。在某些应用中这是能够接受的甚至是要求的行为,但对于另外一些来讲这多是灾难性的。

要使用RAMJobStore (假设你使用的是StdSchedulerFactory)只须要在配置文件中将JobStore class属性值设置为org.quartz.simpl.RAMJobStore便可。配置Quartz使用RAMJobStore的方法以下:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

9.2 JDBCJobStore

JDBCJobStore 的做用也是显而易见的--它将全部数据经过JDBC保存在数据库中。正是如此它的配置要比RAMJobStore更困难一些,也没有它快。然而性能的缺陷也不是特别的糟糕,尤为是你的数据表使用主键做为索引。在至关现代且网络环境至关好(在调度器与数据库之间)的机器上,获取并更新一个激活的触发器所须要的时间一般小于 10毫秒。

JDBCJobStore 几乎兼容全部数据区,它被普遍使用到Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB, 以及DB2上。使用JDBCJobStore时,你必须首先为quartz建立一个表格,你在Quartz 分发包的 "docs/dbTables" 目录下可以找到建立表格的SQL脚本。若是尚未针对你的数据库的脚本,那就随便看一个,并以任何方式进行修改。有一件事情要注意,在这些脚本中全部表格都是以QRTZ开头(好比"QRTZ_TRIGGERS" "QRTZ_JOB_DETAIL")。这个前缀能够是任何值,你只须要告诉JDBCJobStore 便可(在配置文件中)。在同一个数据库中为不一样的调度器实例使用不一样的数据表时,使用不一样的前缀是有用处的。

一旦创建了数据表,在配置和使用JDBCJobStore 以前你还须要作一个重要的决定。你须要决定你的应用使用什么类型的事务(transaction)。若是你不须要将你的调度指令(例如增长或删除触发器)绑定到其余的事务,那你可使用JobStoreTX (这是最经常使用的选项)让Quartz 来管理事务。

若是你要让Quartz 与其它事务(好比与一个J2EE应用服务器)一块儿工做,那你应当使用JobStoreCMT Quartz 将使用APP服务器容器来管理这些事务。

最后一部分是设置DataSource JDBCJobStore 要从它这里获取到你数据库的链接。DataSources 在属性文件中定义,且有一些不一样的定义方式。一种是让Quartz 本身来建立和管理DataSource ,提供全部到数据库的链接信息。还有一种是让Quartz 使用自身所在的应用服务器提供的DataSource ,向JDBC提供DataSourceJNDI 名称。具体的配置请参阅 "docs/config"文件夹中的配置文件。

要使用JDBCJobStore (假设你使用的是StdSchedulerFactory)你首先要将配置文件中的JobStore class 设置为org.quartz.impl.jdbcjobstore.JobStoreTX 或者org.quartz.impl.jdbcjobstore.JobStoreCMT两者其一,取决于你对以上选项的选择。

配置Quartz使用JobStoreTx

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

接下来你须要选择供JobStore使用的DriverDelegateDriverDelegate 的职责是处理你的数据库所须要的JDBC相关的工做。StdJDBCDelegate 使用了"vanilla" JDBC代码(以及SQL语句)来作这些工做。若是没有为你的数据库指定其它的Delegate你能够尝试这个--咱们仅为看起来使用StdJDBCDelegate 会有问题的数据库提供了特定的Delegate。其它的Delegate位于 "org.quartz.impl.jdbcjobstore"包或其子包内,包括: DB2v6Delegate (for DB2 version 6 及之前) HSQLDBDelegate (for HSQLDB)MSSQLDelegate (for Microsoft SQLServer), PostgreSQLDelegate (for PostgreSQL), WeblogicDelegate (for using JDBC drivers made by Weblogic), OracleDelegate (for using Oracle),等等。

当选择了Delegate之后,你须要在配置文件中设置它的名称。

配置JDBCJobStore 使用DriverDelegate

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

接下来你须要通知JobStore 你使用的数据表前缀是什么。

配置JDBCJobStore 使用的数据表前缀:

org.quartz.jobStore.tablePrefix = QRTZ_

最后,你须要设置JobStore使用的DataSource 。你填写的DataSource 必须在配置文件中有定义,所以咱们指定Quartz 使用"myDS"(已经在配置文件的某处定义)。

配置JDBCJobStore 使用的DataSource 名称:

org.quartz.jobStore.dataSource = myDS

若是你的调度器很繁忙(好比在运行任务数量几乎老是等于线程池的容量)那么你可能须要将DataSource 链接数设置为线程池的容量+2

"org.quartz.jobStore.useProperties"配置参数默认为false,为了使JDBCJobStore JobDataMaps的全部值均为String类型,而不是存储为更复杂的可序列化对象,该值能够设置为true。长远来看这样更加安全,由于你避免了将非字符串类序列化为BLOB类是的类版本问题。

9.3 TerracottaJobStore

TerracottaJobStore 提供了一种不使用数据库状况下的量化和鲁棒性手段。这意味着你的数据库能够与Quartz的负载无关,并将其节省下来的资源用于你的其它应用。

TerracottaJobStore 可被集群化或非集群化使用,在这些场景下都会为你的任务数据提供一个存储介质,它在你的应用重启期间也具备持久性,由于数据存储在Terracotta 服务器。它的性能比使用基于JDBCJobStore 的数据库要好得多(大约一个数量级),但仍比RAMJobStore慢得多。

要使用TerracottaJobStore (假设你使用的是StdSchedulerFactory),只须要将配置文件中的JobStore class 设置为org.terracotta.quartz.TerracottaJobStore,并额外添加一行配置指定Terracotta 服务器的位置。

配置Quartz 使用TerracottaJobStore的方法:

org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore

org.quartz.jobStore.tcConfigUrl = localhost:9510

关于JobStoreTerracotta的详细信息请参阅http://www.terracotta.org/quartz

10、 配置,资源使用及调度器工厂

Quartz 的结构是模块化的,所以要让它运行起来的话各个组件之间必须很好地结合起来。在运行Quartz 以前必须先进行配置的主要组件有:

线程池

JobStore

DataSources (若是须要的话)

调度器自身

线程池为Quartz 提供了一个线程集以便在执行任务时使用。线程池中线程越多,能够并发执行的任务数越多。然而,过多的线程可能会是你的系统变慢。许多Quartz 用户发现5个左右的线程就足够了--由于在任意时刻的任务数都不会超过100,并且一般它们都不会要求要同时执行,并且这些任务的生命期都很短(很快就结束)。另外一些用户发现他们须要10,15,50或者甚至100个线程,由于他们在任意时刻都有成千上万的触发器和大量的调度器--它们都平均至少有10-100个任务须要在任一时刻运行。为你的调度器池找到合适容量彻底取决于你如何使用它。并无严格的规律,除了保持线程数量尽量的少(为了节约你机器的资源)--然而你要确保足以按时激活你的任务。注意,若是一个触发器达到了激活时间而没有可用线程,Quartz 将阻塞(暂停)直到有一个可用的线程,而后任务将被执行--在约定时间的数个毫秒以后。这甚至会致使线程的激活失败--若是在配置的"misfire threshold"期间都没有可用的线程。

org.quartz.spi 包中定义了一个线程池接口,你能够根据你的洗好建立一个线程池应用。Quartz 包含一个简单(但很是使人满意的)线程池叫作org.quartz.simpl.SimpleThreadPool。这个线程池只是维护一个固定的线程集合--从不会增多也不会减小。但它很是健壮而且获得了很是好的测试--几乎全部使用Quartz 用户都会使用它。

JobStores DataSources 在第9章已经讨论过。这里须要提醒的是一个事实,全部的JobStores 实现了org.quartz.spi.JobStore接口--若是某个打包好的JobStores 不知足你的须要,你能够本身建立一个。

最后,你须要建立你本身的调度器实例。调度器自身须要一个名称,告知它的RMI参数并把JobStore 和线程池的实例传递给它。RMI 参数包含了调度器是否应该将本身建立为一个RMI服务对象(使它对远程链接可用),使用哪一个主机和端口等等。StdSchedulerFactory 也能够生产调度器实例,它其实是到远端进程建立的调度器的代理。

10.1 StdSchedulerFactory

StdSchedulerFactory是一个org.quartz.SchedulerFactory接口的实现。它使用一系列的属性 (java.util.Properties)来建立和初始化Quartz 调度器。属性一般存储在文件中并从它加载,也能够由你的应用程序产生并直接传递到factory。直接调用factory getScheduler() 方法将产生调度器,初始化它(以及它的线程池、JobStore DataSources),而后返回它的公共接口的句柄。

Quartz 分发包的"docs/config"目录下有一些示例配置(包含属性的描述)。你能够Quartz 文档的"Reference"章节的配置手册中寻找完整的文档。

10.2 DirectSchedulerFactory

DirectSchedulerFactory是另外一个SchedulerFactory 实现。它对于那些想要以更程序化的方式来建立调度器实例的人来讲是有用的。它通常不建议使用,由于:(1)它要求用户很是明白他在作什么(2)它不容许声明式的配置--换句话说,你须要对调度器的设置进行硬编码。

10.3 日志

Quartz 使用SLF4J 框架来知足全部的日志须要。为了调整日志设置(好比输出量,以及输出路径),你须要理解SLF4J 框架,这超出了本文的范围。若是你想要获取关于触发器激活和任务执行的额外信息,你可能会对如何启用org.quartz.plugins.history.LoggingJobHistoryPlugin/rg.quartz.plugins.history.LoggingTriggerHistoryPlugin感兴趣。

11、 高级(商业特性

11.1 集群

集群目前与JDBC-Jobstore (JobStoreTX  JobStoreCMT) 以及TerracottaJobStore一同工做。这些特性包括负载均衡和任务容错(若是JobDetail"request recovery" 设置为true)。

使用JobStoreTX  JobStoreCMT的集群

经过将"org.quartz.jobStore.isClustered"属性值设置为true来启用集群。集群中的每一个实例都应使用quartz.properties文件的同一份拷贝。使用惟一配置文件的例外状况包含如下可容许的例外:线程池容量不一样,以及 "org.quartz.scheduler.instanceId" 属性值不一样。集群中的每一个节点都必须有一个惟一的实例ID,将该值设为"AUTO"便可轻易实现(不须要不一样的配置文件)。

用于不要在不一样的机器上使用集群,除非它们使用了某种时钟同步服务实现了时钟同步,且运行的很是规律(每一个机器的时钟必须在同一秒内)。若是你还不太熟悉怎么实现能够看这里http://www.boulder.nist.gov/timefreq/service/its.htm

若是已经有集群实例使用了一组数据表,永远不要激活一个一样使用该组数据表的非集群实例,可能发生严重的数据冲突,而且运行状态必定是不稳定的。

每一个任务每次只会被一个节点激活。个人意思是,若是某任务有一个触发器,它每隔10秒钟激活一次,那么在12:00:00只有一个节点执行该任务,12:00:10也有一个节点执行该任务,以此类推。并不须要每次都是同一个节点--具体是哪一个多少有一些随机性。对于繁忙调度器(有大量触发器)的负载均衡机制是近似随机的,但对于非繁忙调度器(只有一两个触发器)每次都是相同的活跃节点。

使用TerracottaJobStore的集群

只须要将调度器配置为使用TerracottaJobStore (第9章已经介绍),你的调度器就会所有配置为集群。也许你还想考虑如何设置你的Terracotta 服务器,尤为是如何开启持久性之类选项的配置,而且为了高可用而运行一批Terracotta 服务器。商业版的TerracottaJobStore 提供了Quartz 的高级特性,容许智能地将任务定位到合适的集群节点。关于JobStore Terracotta 的更多信息请查看 http://www.terracotta.org/quartz

11.2 JTA 事务

在第9章已经解释过,JobStoreCMT 容许Quartz 的调度器在JTA 事务中运行。经过将"org.quartz.scheduler.wrapJobExecutionInUserTransaction"属性设置为true任务也能够在JTA 事务中运行(UserTransaction)。设置该值后,一个JTA 事务的begin()将在任务的execute 方法执行前被调用,而且在commit() 结束时commit() 被调用。这适用于全部任务。

若是你想为每一个任务指定是否要由JTA 事务来包装它的执行,那你应当在该任务的类中使用@ExecuteInJTATransaction标注。

当使用JobStoreCMT时,除了JTA 事务自动包装任务执行以外,你在调度器接口上所进行的调用也会参与到事务中。你只须要确认在调度器上调用方法前已经启动了一个事务便可。你可使用UserTransaction来直接完成,或经过将你使用调度器的代码放到一个使用了容器管理事务的SessionBean 中。

 

12、 其它特性

12.1 插件

Quartz 为插入额外功能提供了一个接口(org.quartz.spi.SchedulerPlugin)Quartz包含的插件提供了各类工具特性,你在org.quartz.plugins包中能找到。它们提供了诸如任务自动调度、记录历史任务和触发器事件、确保在JVM存在时调度器干净的关闭等特性。

12.2 任务工厂(JobFactory)

当触发器激活时,相关的任务将被调度器配置的JobFactory 实例化。默认的JobFactory 只是简单的调用任务类的newInstance()方法。你可能想要建立你本身的JobFactory 实现以实现诸如让你应用程序的IoC DI容器来产生或初始化任务实例。查看org.quartz.spi.JobFactory接口,以及相关的Scheduler.setJobFactory(fact) 方法。

12.3 'Factory-Shipped' Jobs

Quartz还提供了大量工具任务,你可使用它们来完成诸如发送邮件和激活EJB等工做。这些开箱即用的任务在org.quartz.jobs 包中能够找到。