Quartz 教程

1. 使用quartz

  在你使用scheduler(调度器)之前,需要先初始化它。要做到这点,你需要一个SchedulerFactory类。有些用户可能在JNDI store(命名服务目录)保留一个SchedulerFactory类的实例,有些用户可能直接实例化并使用SchedulerFactory类的实例。
  一旦scheduler被实例化,就可以被启动、设置为备用状态或关闭。注意,一旦scheduler 被关闭,就必须重新实例化后,才能启动。Triggers (触发器)只有scheduler启动后才能被触发,即使处于暂停状态也不行。
  这里有一段代码,实例化并启动一个scheduler并调度job(任务)执行。

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灰常的简单。在第二节,我们会对Jobs、Triggers和Quartz API 做简单的阐述,可以使你更好的理解这个例子。
下文将分别翻译Scheduler、Trigger和Job为调度器、触发器和任务。

2. Quartz API,任务和触发器

Quartz API

 Quartz API 主要的接口如下:
- Scheduler - 与调度器交互的主要API。
- Job - 需要被调度器调度的任务必须实现的接口。
- JobDetail - 用于定义任务的实例。
- Trigger - 用于定义调度器何时调度任务执行的组件。
- JobBuilder - 用于定义或创建JobDetail的实例 。
- TriggerBuilder - 用于定义或创建触发器实例。
(个人总结两条生产线一条整合线:
1. JobBuilder ——JobDetail——Job
2. TriggerBuilder——Trigger
3. Scheduler将Job和Trigger整合在一起)

  一个调度器的生命周期起于通过调度器工厂类的创建,止于其对shutdown()方法的调用。调度器创建后,可以添加、删除或列出任务和触发器,也可以执行其他与调度相关的操作(如暂停触发器)。注意,只有调度器调用了start()方法,才能对响应触发器并调度任务,正如 第一节 所见。
  Quartz提供 builder类,builder类定义了一种DSL语言(又称流式接口,个人认为像javascript的链式调用)。下面看一下 第一节 的代码例子:

// define the job and tie it to our HelloJob class
  JobDetail job = newJob(HelloJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "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);

  这段代码使用JobBuilder 类的静态方法(newJob())定义了任务类。同样,使用TriggerBuilder 类的静态方法定义了触发器类-SimpleScheduleBulder 类类似。
DSL的静态方法可以通过静态导入语句导入,如下:

import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;

  各种不同的ScheduleBuilder类创建不同类型的调度器。
  DateBuilder 类包含了各种用于构建 java.util.Date 类的实例的方法,用于标识特殊时间点。

任务和触发器

  在Quartz中,一个任务是实现了Job接口的普通类,只包含一个简单的执行方法:
Job Interface

 package org.quartz;

  public interface Job {

    public void execute(JobExecutionContext context)
      throws JobExecutionException;
  }

  当任务上的触发器被触发时,任务的execute(..)方法会被调度器的工作线程调用。JobExecutionContext 对象(任务执行上下文)就会被传递给该方法。提供调度器、触发的触发器和任务的JobDetail 对象和其他若干任务运行时信息。
  JobDetail 对象在任务被添加到调度器时,由Quartz 客户端(你的程序)创建并包含许多任务的属性设置。如 JobDataMap,用于暂存任务实例的状态信息。它本质上是任务实例的定义,并在下一节讨论。
  Trigger 对象用于触发任务的执行。当需要调度一个任务时,你实例化触发器并根据调度需要设置其属性。触发器也可以有JobDataMap,使其用于对与触发器关联的任务提供参数信息。Quartz 有多种触发器类型,比较常用的是SimpleTrigger 和CronTrigger类型。
当你需要一个时间点只有一个任务执行或者需要在指定时间出发任务并间隔一定时间重复N次,SimpleTrigger类型触发器就是最方便的。如果需要基于日历类型的计划表执行任务,例如 每周五的中午、每月第十天的上午10:15 触发任务,就需要使用CronTrigger类型触发器。
  为什么要分任务和触发器呢?许多任务调度器不使用任务和触发器分离的概念。有些是为每个任务指定简单的的运行时间和唯一标识,其他的更像是 Quartz的任务和触发器概念的混合。在开发Quartz初期,我们认为,创建一个分离的时间表,根据不同时间表来调度任务,是有意义的。至少,我们认为会有很多的优势。
  例如,任务可以创建并存储在调度器中,独立于触发器;也可以使多个触发器关联同一个任务。任务和触发器松散结合的另一优势是,当调度器中的任务关联的触发器过期时,可以重新安排而不需要重新定义触发器。也可以随时修改或替换触发器而不需要重新定义关联的任务。(任务和触发器是多对多的关系,一个任务可以关联多个触发器,一个触发器也可以被多个任务使用)

标识

  当调度器注册任务和触发器时,会赋予其唯一的标识码。任务和触发器的标识(JobKey 和 TriggerKey)可以放到不同的组中,以便于根据不同的业务需要进行分类,如报告任务组和维护任务组。组内标识的名字必须是唯一的。换句话说,任务或触发器的完整标识是标识名和组名的组合。
  现在你已经对任务和触发器有了基本的认识,你可以通过第三节More About Jobs & JobDetails 和第四节的More About Triggers更加深入的学习。

3. 深入理解任务和任务详情

  正如你在第二节看到的,任务实现非常容易,只需要实现具有一个execute()方法的Job接口。
  虽然你可以实现Job接口并在execute方法中实现特定类型的工作,但Quartz需要被告知你设定任务的各种各样的属性,这就需要在之前部分提到的JobDetail类来实现。
JobDetail对象通过JobBuilder类来实例化。如果要使用DSL类型的代码方式,你需要使用静态导入来导入它的所有静态方法。如:

import static org.quartz.JobBuilder.*;

  下面我们来详细讨论一下“最原始”的任务和Quartz任务的生命周期。让我们回顾一下第一节的代码:

 // define the job and tie it to our HelloJob class
  JobDetail job = newJob(HelloJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "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);

  现在看看“HelloJob”类的定义:

 public class HelloJob implements Job {

    public HelloJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      System.err.println("Hello!  HelloJob is executing.");
    }
  }

  请注意,我们给调度器一个JobDetail实例,这个实例在我们创建JobDetail的时候知道执行时应该调用那个类。每次调度器执行任务的时候,执行execute()方法之前,先创建这个类(HelloJob)的实例。当任务执行完毕后,JobDetail引用的类实例会被销毁并稍后由垃圾回收机制回收。在执行过程中,一方面任务应用的类需要定义无参构造方法(当使用默认的JobFactory实现时)。另一方面,在任务中定义状态成员数据(或变量)没有任何意义,因为状态变量的值不会再任务执行间共享。
  你可能会问 ”我如何为任务实例提供属性或配置信息?”或者“我如何记录或跟踪任务不同执行过程中的状态”。这就需要JobDetail对象的内置JobDataMap解决这些问题。

JobDataMap

  JobDataMap 对象可以用来在任务执行时提供必要的属性设置信息。这些信息必须是序列化的数据对象。它实现的java中的Map接口,并为基本类型提供了存取数据的简单方法。
  这里有个代码段。在给调度器添加任务前,在定义或创建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);
    }
  }

  如果你使用持久化对象JobStore (在教程的 JobStore 部分讨论),需要注意暂存在JobDataMap中的数据类型,因为其中的数据会被序列化,所以容易出现类版本问题。显然,标准的java类型非常的安全,但一旦你已序列化的实例对象的类被别人修改,就必须检查是否破坏了兼容性。所以,可以规定JDBC-JobStore和JobDataMap 中只能使用基本类型和String类型,这将避免随后的序列化问题。
  如果为Job类中添加与JobDataMap中键名重名的变量(如上例对JobSays变量设置setJobSays(String val)方法),Quartz默认的JobFactory实现会在任务初始化时,自动调用setters方法,不用在任务执行时显示从map中获取键的值。
触发器(Triggers )也有相关的JobDataMap。当调度器中的任务需要周期性或重复使用多个触发器时,可以设置不同的触发器有不同的输入数据。
  在任务执行时,JobExecutionContext 中可以找到JobDataMap 对象。JobDataMap 中包含了在创建JobDetail和Trigger时定义的所有键值数据。如果有重名的键名,Trigger对应的键值会覆盖JobDetail对应的键值。
  下面有段在任务执行过程中,从JobExecutionContext的混合JobDataMap 中获取数据的代码:

public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");
      ArrayList state = (ArrayList)dataMap.get("myStateData");
      state.add(new Date());

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
  }

  如果需要依赖JobFactory注入 data map数据到定义的job类中,可以这样写:

 public class DumbJob implements Job {


    String jobSays;
    float myFloatValue;
    ArrayList state;

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      state.add(new Date());

      System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }

    public void setJobSays(String jobSays) {
      this.jobSays = jobSays;
    }

    public void setMyFloatValue(float myFloatValue) {
      myFloatValue = myFloatValue;
    }

    public void setState(ArrayList state) {
      state = state;
    }

  }

  你会发现代码会非常的长,但是execute()方法非常简洁。你可以使用IDE生成setter方法以获取数据,也可以手工写调用来获取 JobDataMap的数据。

任务实例

  许多用户会困惑任务实例是怎样构成的。我们会详述任务状态和并发的概念。
  你可以创建单独的Job类,通过创建多个JobDetail类的实例(同一个Job类不同的属性和JobDataMap )并加入到调度器中。
例如,你创建了实现Job接口的类“SalesReportJob”。类的功能是基于销售报告指定传入的推销员的姓名(姓名通过JobDataMap传入)。然后根据这个类创建多个JobDetail并传入不同的推销员姓名,例如SalesReportForJoe、SalesReportForMike。
  当触发器被触发时,关联的JobDetail被加载,JobDetail对应的Job类会被调度器上配置的JobFactory实例化。默认的JobFactory只是简单调用Job类的构造方法,并尝试调用与JobDataMap匹配的键名的set方法。如果应用通过控制反转或依赖注入来创建或实例化Job实例,你可以自己实现JobFactory 接口。
  在本教程中,我们将存储的JobDetail叫做“Job定义”或“JobDetail实例”,将执行的Job叫做“Job实例”或“job定义的实例”。通常,我们将Job定义为JobDetail。当我们需要指定实现了Job接口的类时,我们会使用“Job类”术语。

任务状态和并发

  下面介绍任务的状态(又叫做JobDataMap)和并发执行。介绍一组注解。将其添加到Job类会影响Quartz的行为。
  @DisallowConcurrentExecution添加到Job类中可以使quartz不并发执行JobDetail实例。
  要慎重使用本注解。在前面的例子中,如果为SalesReportJob添加此注解,在一个时间点只能SalesReportForJoe实例运行,SalesReportForMike实例并发运行。约束是基于JobDetail的,而不是Job类实例的。然而,在设计Quartz的时候,此注解作用于类本身,因为其对类如何编码有关系。
  @PersistJobDataAfterExecution注解添加到Job类使Quartz在任务执行execute()方法成功后,更新复制JobDetail的JobDataMap ,这样在下次执行同样的JobDetail的时候可以使用更新后的JobDataMap ,而不是最初存储的数据。和@DisallowConcurrentExecution注解类似,注解作用于JobDetail的,而不是Job类的。虽然这是决定工作类的属性,因为属性经常改变类是如何编码的。
  如果你使用@PersistJobDataAfterExecution注解,那你也应该考虑@DisallowConcurrentExecution annotation注解。为了防止可能的混乱(如竞争条件),即当同一JobDetail的实例并发运行,导致JobDataMap 的数据出现不确定情况。

任务的其他属性

  下面介绍通过JobDetail对象能够设置任务实例的其他属性:
- Durability 如果一个任务不是持久的,一旦任务没有激活的触发器,任务就会自动从调度器中删除。换句话说,非持久的任务的生命周期取决于它存在处于活动的触发器。
- RequestsRecovery 假设任务设置“RequestsRecovery ”属性,如果任务正在执行,突然遇到调度器停机(如运行的进程突然崩溃或机器突然断电停机),当调度器重新启动后,任务会再次执行。调用JobExecutionContext.isRecovering() 会返回true。

JobExecutionException

  最后,我们需要告知你Job.execute(..)方法的详细信息。执行方法唯一能抛出的异常(包含运行时异常)就是JobExecutionException。所以方法体需要try-catch语句。你应该花些时间看看JobExecutionException的文档,你可以提供各种调试指令来告知调度器如何处理异常。

4. 深入理解触发器

  触发器和任务一样简单,但要充分利用Quartz实现特定定时任务还需要注意并理解触发器包含的可选选项。如前所述,你可以根据不同的定时任务需求选择不同类型的触发器。
  你会在 第五节 Simple Triggers 和第六节 Cron Triggers学习不同类型的触发器。

触发器的通用属性

  除了需要为触发器指定TriggerKey 属性来唯一标识,触发器还有很多通用的属性。这些属性可以在使用TriggerBuilder 类创建触发器时设置。
  下面列举触发器通用的属性:
- jobKey 用于触发器触发时要执行的任务的标识。
- startTime 触发器首次触发的时间。值为java.util.Date的对象,该对象在给定的日历日期定义了一个时刻。一些类型的触发器会在开始时间触发;另一些类型的触发器会标记时间并随后运行。也就是说如果调度器存储的触发器设定为“每个月的第五天执行”(当前是一月份),而开始时间设定为四月一日,那么该触发器会延迟几个月时间,在四月的第五天开始触发。
- endTime 触发器触发的截止时间。也就是说,如果调度器中的触发器设定为“每月的第五天”,截止时间是七月一日,那么触发器最后一次触发时间是六月的第五天。
  其他通用属性会在下文详述。

优先级

  有时,当调度器中有很多触发器(或者Quartz线程池中有若干线程)时,由于没有足够的系统资源,导致计划同一时刻触发的触发器不能同时按指定时间点触发。这种情况下,可能需要选择触发器抢占式优先使用线程资源,可以通过为触发器设置priority 属性实现。如果有N个触发器需要在同一时间点触发,但当前只有Z个可用线程资源(Z《N),前Z个高优先级的触发器会优先执行。如果你没有为触发器设置优先级,默认优先级别为5.所有触发器均可设置优先级,优先级别可以为正数或负数。
  注意:只有当多个触发器在同一时间点触发且资源不足时,才会比较触发器的优先级。一个计划 10:59 触发的触发器必先于11:00触发的触发器执行。当触发器需要恢复时,优先级和初始设置相同。优先级高的触发器会优先恢复。

熄火指令(触发失败指令)

  触发器另一种重要的通用属性是熄火指令。如果触发器因为调度器停止或执行任务时线程池中没有足够的线程资源而导致错过了规定的触发时间,熄火指令就会执行。不同类型的触发器有各自的熄火指令。默认情况下,Quartz使用“smart policy”策略,即根据触发器类型和配置动态执行指令。在调度器启动时,首先查看是否有熄火(之前某时间点该执行而未执行)的持久触发器,如果有,调度器根据触发器的个性配置更新触发器的熄火指令。在项目中使用Quartz时,你应该通过文档熟悉不同类型触发器的熄火指令。有关熄火指令的更多详细内容可以在教程中对应的触发器类型部分找到。

日历

  Quartz日历对象(不是 java.util.Calendar对象)可以关联调度器中的触发器。日历对于触发器排除一段时间执行(或有段时间不执行触发,其余时间正常触发)非常有用。例如,你可以定义一个每天上午 9:30触发任务的触发器,并通过日历排除工作日(周一到周五不执行),也就是只有周六周日两天的上午 9:30触发任务。
日历类可以是任何实现了Calendar接口的序列化对象。如下:
The Calendar Interface

package org.quartz;

public interface Calendar {

  public boolean isTimeIncluded(long timeStamp);

  public long getNextIncludedTime(long timeStamp);

}

  注意接口中这些方法的参数是long类型。正如你所想的那样,这是时间戳的毫秒格式。这意味着日历可以排除到毫秒粒度级别的时间段。通常,可能你会排除一整天时间的情况,Quartz提供的 class org.quartz.impl.calendar .HolidayCalendar类可以满足此需要。
  日历必须实例化并通过addCalendar(..)方法注册到调度器中。假如你使用HolidayCalendar,在实例化后,调用其addExcludedDate(Date date) 方法添加排除执行的日期。同一个日历对象可以在多个触发器中使用,Calendar 例子如下:

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

  构造或创建触发器的细节会在后续章节涉及。目前,上边的代码创建了两个触发器,每个触发器被每天触发执行任务。并且,被日历排除的时间段不会执行任务。
  查看org.quartz.impl.calendar包下的Calendar实现类,总有你想要的。

5. SimpleTrigger

  如果你要实现在一个特定时刻触发任务执行或特定时刻触发并随后间隔一段时间循环执行,SimpleTrigger能满足你的定时要求。例如你想2015年一月13日上午11:23:54触发任务或者随后每10秒触发一次,一共触发5次,便可使用简单触发器实现。
  通过上面的描述,你可以已经知道SimpleTrigger的几个属性设置:开始时间(start-time)、结束时间(end-time)、执行次数(repeat count)和时间间隔(repeat interval)。所有的属性都非常简单,结束时间可能会有特殊情况。
执行次数可以是0、正数或通过Trigger.REPEAT_INDEFINITELY变量设置。执行时间间隔必须是0或者正长整型,单位是毫秒。注意如果间隔时间设置为0,就会导致触发器并发触发指定执行次数个线程执行(如果较执行次数较多,会接近调度器管理线程总数)。
  如果你还不熟悉Quartz的DateBuilder 类,设置根据开始时间或截止时间设置相对触发时间。
  如果设置了截止时间属性,它会在适当的时候覆盖执行次数属性。假如你希望设置一个每隔10秒触发一次,直到截止时间的触发器。如果要计算开始时间到截止时间,每隔10秒总共执行多少次比较麻烦,你可以指定截止时间和 REPEAT_INDEFINITELY 参数。你甚至可以指定一个比指定时间段内能执行次数大的数值,但会在截止时间前停止执行,无论执行次数是否执行完。
  可以通过TriggerBuilder 类或SimpleScheduleBuilder 类创建简单触发器实例。为了使用流式编程方式,需要静态导入相关的类成员:

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秒钟执行一次,指定了执行次数

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

Build a trigger that will fire at the top of the next hour, then repeat every 2 hours, forever(实在不懂)

 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其他的builders)显示设置属性的值,比如没有调用withIdentity(..) 方法,TriggerBuilder 会为触发器生成随机的名称;没有调用startAt(..) ,会默认当前时间执行。

SimpleTrigger 的熄火指令

  SimpleTrigger 有针对触发器没有正常触发(触发失败的情况详见 第四节深入了解触发器)的处理说明。这些处理说明在SimpleTrigger中是以常量的方式定义。说明如下:
有关SimpleTrigger的失败指令

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 对所有触发器有效,包括简单触发器。
  如果设置“smart policy”指令,SimpleTrigger会基于配置和给定的触发器实例动态选择它的失败指令。文档中有关SimpleTrigger.updateAfterMisfire() 部分会详述这种动态行为。
  在创建简单触发器时,通过simple schedule(如SimpleSchedulerBuilder) 设置失败指令。

 trigger = newTrigger()
    .withIdentity("trigger7", "group1")
    .withSchedule(simpleSchedule()
        .withIntervalInMinutes(5)
        .repeatForever()
        .withMisfireHandlingInstructionNextWithExistingCount())
    .build();

6. CronTrigger

  对于针对类似日历的触发方式,而不是设置精确时间间隔触发的情况,CronTrigger 会比SimpleTrigger更有效果。
  使用CronTrigger ,可以设置类似如下的触发时间。如:
- 每周五的中午
- 每个工作日的上午九点半
- 一月份星期一、星期三、星期五的上午九点到十点,每隔五分钟
  即便如此,和Simple Trigger 类似,当需要强制触发器触发时,CronTrigger 要设置开始时间属性;当需要强制中断触发器触发时,要设置截止时间属性。

Cron 表达式

  Cron表达式用于设置Cron Trigger的触发时间。它是由时间表多个子表达式(如时分秒)组成的字符串。这些子表达式由空格分开,代表时间的不同属性(字符串组成从左到右依次为1-7组合):
1. 秒
2. 分钟
3. 小时
4. 一月的一天
5. 月
6. 一周的一天
7. 年(可选)
  例如:表达式“0 0 12 ? * WED”表示“每周三下午的12:00:00”。

  有关Cron 表达式不建议学习,太过繁琐。网上有很多Cron表达式足以满足日常需要。相关Cron 表达式的细节有时间再译。

Cron表达式例子

  下面简单举一些常用的Cron表达式:
每五分钟触发

“0 0/5 * * * ?”

每隔五分钟的第十秒触发,如10:00:10 am、10:05:10 am等等。

“10 0/5 * * * ?”

每周三到周五10:30, 11:30, 12:30和13:30触发

“0 30 10-13 ? * WED,FRI”

每月的第五天到第二十天之间,在上午八点到十点之间,每半小时触发(注意只会在8:00, 8:30, 9:00 和 9:30, 10:00不触发)

“0 0/30 8-9 5,20 * ?”
创建Cron触发器

  Cron Trigger实例可通过TriggerBuilder 或CronScheduleBuilder 创建。静态导入并使用流式编程方式:

import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.DateBuilder.*:

每天的上午八点到下午五点,每两分钟触发

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

每天的上午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();

特定时区的每周三上午10:42触发,而不是系统默认时区

 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();
CronTrigger 的熄火指令

  以下指令是CronTrigger针对触发失败情况(详见教程的“深入理解触发器”一节)的处理指令。这些指令以常量的形式定义。指令常量如下:

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW

  所有的触发器类型都有 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY 指令。“smart policy”指令由CronTrigger 的MISFIRE_INSTRUCTION_FIRE_NOW表示。API中有关CronTrigger.updateAfterMisfire() 的部分详细解释了该行为。
在创建Cron 触发器时,由simple schedule(通过CronSchedulerBuilder) 设置失败指令。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值