一、简介

简单的、强大的任务计划框架。
通过java API编程,可建立独立的任务计划程序,也可集成进任意java项目。
支持集群等企业级功能。

二、上手

1. 将quartz并导入java项目

1.1 JAR包

官方网站下载(较慢):http://www.quartz-scheduler.org/downloads/
本站下载(Quartz 2.2.3):quartz-2.2.3-distribution.tar.gz

下载完成后,将压缩包lib文件夹中的所有jar包,包括quartz-2.2.3.jar及其他quart依赖的第三方jar包放在java项目的classpath路径下,即可在java项目中使用Quartz的API。
如果项目的buildpath中已经包含有Quartz所依赖的第三方jar包,则需要注意是否有同一个jar包的不同版本同时出现在buildpath中的情况,如果有应该妥善处理。

1.2 MAVEN坐标
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.1</version>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.2.1</version>
</dependency>   

maven项目可以通过maven坐标获得quartz及其依赖的第三方包,将以上maven坐标添加到pom.xml中后,即可在maven项目中使用Quartz的API。

2. 在项目中添加quartz配置文件

  • Quartz会自动在classpath路径下面寻找将quartz.properties文件,并从中读取配置。

  • 可以参考下载的压缩包中的examples/ 文件夹中示例程序中的quartz.properties,编写一个自己的quartz.properties文件。详细的配置教程,参考配置Quartz

  • 如下是一个基本的quartz.properties文件内容,满足Quartz基本运行:

    \# 任务调度器的名称
    org.quartz.scheduler.instanceName = MyScheduler
    \# 线程池中的线程数量,线程数量的多少决定了能够同时运行多少任务
    org.quartz.threadPool.threadCount = 3
    \# 刚开始可以使用RamJobStore,将quartz的数据包括任务和触发器详情放在内存中。之后再学习如何配合数据库将数据持久化到数据库中。
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
    

3. Hello World

3.1 运行任务调度器

以下代码启动了一个空的调度器
./QuartzTest.java

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
public class QuartzTest {
  public static void main(String[] args) {
      try {
          // 获取到调度器实例
          Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
          // 启动调度器
          scheduler.start();
          //必须调用shutdown方法,才能是程序正常结束,否则会一直运行。
          scheduler.shutdown();
      } catch (SchedulerException se) {
          se.printStackTrace();
      }
  }
}

运行结果如下:
QuartzTest-Executed.png

3.2 hello world

以上代码中,仅仅是启动了Quartz的任务调度器,并没有执行任何的任务。要使Quartz真正的触发和执行简单的任务,可以在scheduler.start()和scheduler.shutdown()之间为调度器设置任务和触发器信息,更新后的示例如下:
./QuartzTest.java

import static org.quartz.DateBuilder.evenMinuteDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.SimpleScheduleBuilder.*;
import org.quartz.SchedulerException;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
    public class QuartzTest {
      public static void main(String[] args) {
          try {
              // 获取到调度器实例
              Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
              // 启动调度器
              scheduler.start();
              // 定义任务
              JobDetail job = newJob(HelloJob.class)
                  .withIdentity("job1", "group1")
              .build();
              // 触发任务执行,并每隔3s重复一次
              Trigger trigger = newTrigger()
                  .withIdentity("trigger1", "group1")
                  .startNow()
                  .withSchedule(simpleSchedule()
                  .withIntervalInSeconds(3)
                  .repeatForever())            
                  .build();
              // 往调度器中注入任务和触发器
              scheduler.scheduleJob(job, trigger);
              // 主线程等待10s后再关闭调度器
              Thread.sleep(10000); 
              //必须调用shutdown方法,才能是程序正常结束,否则会一直运行。
              scheduler.shutdown();
          } catch (SchedulerException se) {
              se.printStackTrace();
          }catch(Exception e){
              e.printStackTrace();
          }
      }
    }

以上代码在定义JobDetail对象时,为其绑定了一个HelloJob.class,以下为源码
./HelloJob.java

import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job {
    private static Logger _log = LoggerFactory.getLogger(HelloJob.class);
    //因为此类由JobFactory工厂类负责实例化,因此需要提供无参公共构造函数
    public HelloJob() {
    }
    //当相应注册的Trigger触发器触发时,调度器会实例化本类后,执行次方法
    public void execute(JobExecutionContext context)
        throws JobExecutionException {
        _log.info("Hello World! - " + new Date());
    }
}

以上两个类,即定义了一个每隔3s说一次“Hello World”,一共持续10s的任务调度器。
运行结果如下图:
quartztest1-executed.png

三、 主要概念介绍

1. Quartz API概述

quartz任务调度框架主要包含以下API:

  • Scheduler - 任务调度器,用来添加、移除、查看任务及相应触发器,可以开始/暂停某个触发器
  • Job - 用来实现被执行任务实例的接口
  • JobDetail - 用来定义一个任务实例,为被执行任务定义一些属性信息
  • Trigger - 用来定义任务执行计划的触发器
  • JobBuilder - 用来创建JobDetail实例
  • TriggerBuilder - 用来创建触发器实例

任务调度器实例通过SchedulerFactory创建,通过start()方法启动,通过调用实例本身的shutdown()方法来结束。任务调度器之后在启动后(调用start方法后),其中的触发器才能够正常触发任务。

2. DSL编程风格

quartz大量采用了DSL(Domain Specific Language)风格的编码方式,使一行代码就可以完成对象实例化,如下代码:

//定义JobDetail实例,并绑定HelloJob类
JobDetail job = newJob(Hello.class)
                .withIdentity("myJob","group1)
                .build();
//触发任务开始执行,之后每40秒执行一次
Trigger trigger = newTrigger()
                .withIdentity("myTrigger","group1")
                .startNow()
                .withSchedule(simpleSchedule()
                .withIntervalInSeconds(40)
                .repeatForever())
                .build();
//为调度器添加任务和触发器
sched.scheduleJob(job,trigger);

要使用这种编码风格,需要使用static import静态导入类,这样可以直接调用类中的静态方法。静态导入代码如下:

import static org.quartz.JobBuilder.*;
//不同的“SchedulerBuilder“类可以创建出不同类型的调度器
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
//DateBuilder类中有多个方法通过不同的方式来创建,指向未来某个时间点的java.util.Date实例
import static org.quartz.DateBuilder.*;

3. 任务(Job)

3.1 任务的运行机制

当触发器触发,调度器会调用相应的JobDetail对象,并使用JobFactory将JobDetail绑定的Job实现类实例化,检查是否有属性需要做参数注入,在execution执行完毕后,此Job实现类的实例即被GC回收。在下一次同一个触发器触发时,调度器会调用同一个JobDetail对象,但是会创建新的绑定的Job实现类实例。如果需要在不同的触发之间传递参数,可以在Job实现类的execute方法中,将需要保存的内容保存进JobDetail中的JobDataMap中,或保存进JobExecutionContext中的MergedJobDataMap中。这样在下一次执行此任务时,即可获取到之前的Job实现类实例所保存下来的状态。

3.2 JobDetail对象

JobDetail是触发器触发时调度器所调用的任务对象,其所绑定的Job实现类是具体执行的任务。JobDetail对象中还包含了其他一些执行任务需要的信息,如标识,预置参数等。如下代码定义了一个JobDetail对象和一个Trigger,并将其注册进调度器实例中:

  // 定义一个任务对象,并绑定任务实现类
  JobDetail job = newJob(HelloJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "group1"
      .build();
  // 定一个一个触发器,每40s触发一次,立即开始,永远循环
  Trigger trigger = newTrigger()
      .withIdentity("myTrigger", "group1")
      .startNow()
      .withSchedule(simpleSchedule()
      .withIntervalInSeconds(40)
      .repeatForever())            
      .build();
  //往调度器注册任务和触发器
  sched.scheduleJob(job, trigger);

以上代码仅仅是准备了一个调度器,但是并没有启动,所以触发器并不能触发任务执行。

withIdentity()方法为Job和Trigger指定key,key包含name和group两部分。这样有助于管理调度器中的Job和Trigger对象。同一个调度器实例中的Job或Trigger对象的key必须是唯一的。

3.3 Job接口

Job接口用来实现具体执行任务的类,只包含了一个方法execute,用来定义需要执行的任务。

package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
  throws JobExecutionException;
}

Job接口的实现类,需要提供公共无参构造函数,供任务调度器实例化Job实现类使用。以下代码实现了一个Job类:

  //最简单的Job实现类
  public class HelloJob implements Job {
    public HelloJob() {
    }
    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      System.err.println("Hello!  HelloJob is executing.");
    }
  }
3.4 上下文参数获取及状态保存

3.4.1 JobDataMap对象
JobDataMap是Quartz中用来传递参数的对象,其实现了Map接口,添加了其他一些管理任务数据的方法,进支持Java的基本数据类型。以下代码示例了如何在定义JobDetail对象的时候,往其中的JobDataMap对象中添加数据:

    //定义JobDetail对象,为其绑定Job实现类,并设置标识,预置参数
  JobDetail job = newJob(DumbJob.class)
  .withIdentity("myJob", "group1") // name "myJob", group "group1"
  .usingJobData("jobSays", "Hello World!")
  .usingJobData("myFloatValue", 3.141f)
  .build();

3.4.2 在Job实现类中获取上下文参数1
通过JobExecutionContext的JobDetail中的JobDataMap对象获取定义JobDetail对象时所预置的参数,如下代码:

public class DumbJob implements Job {
    public DumbJob() {
    }
    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();
      //通过JobExecutionContext对象的JobDetail中的JobDataMap对象获取定义JobDetail时预置的参数
      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);
    }
  }

3.4.3 在Job实现类中获取上下文参数2 及 状态保存
Trigger触发器在定义时也可以预置参数,也是保存在了Trigger对象的JobDataMap对象中,可以在JobExecutionContext对象中获取到整合了JobDetail和Trigger两个对象中的JobDataMap的所有预置参数,同名参数会互相覆盖,示例代码如下:

    public class DumbJob implements Job {
    public DumbJob() {
    }
    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();
      //通过JobExecutionContext中的MergedJobDataMap对象,获取JobDetail和Trigger对象定义时,所预置的参数,同名参数会被覆盖
      JobDataMap dataMap = context.getMergedJobDataMap();  
      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);
    }
      }

3.4.3 在Job实现类中获取上下文参数3
使用Quartz的自动依赖注入功能,只需要在Job实现类中定义好属性,并添加setter,那么在任务调度器实例化Job实现类的时候,就会自动去检查JobExecutionContext中的MergedJobDataMap对象,将其中的同名参数注入到Job实现类的实例中,以获取到JobDetail对象和Trigger对象定义时所预置的参数,示例代码如下:

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();
      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;
    }
      }
3.5 并发控制及参数刷新注解

3.5.1 @DisallowConcurrentExecution注解
Job实现类的@DisallowConcurrentExecution注解,用来标记绑定其的JobDetail对象不能并发执行。如果同一个使用了此注解的Job实现类被两个或者多个JobDetail对象绑定,那么这几个JobDetail对象依然能够并发执行,因为虽然这个注解使用在Job实现类上面,但是其控制的对象为相应的JobDetail对象,并不是Job实现类的实例。

3.5.1 @PersistJobDataAfterExecution注解
Job实现类的@PersistJobDataAfterExecution注解,用来控制绑定此Job实现类的JobDetail对象在execute方法成功执行之后,更新JobDetail对象中的JobDataMap对象的值。这样保证下一次触发调用此JobDetail对象时,Job实现类实例获取到的JobDataMap中的参数时最新的。类似于@DisallowConcurrentExecution注解,此注解虽然使用在Job实现类上面,但是其控制的依然是绑定Job实现类的JobDetail对象的行为,如果某个Job实现类使用了此注解,那么所有绑定这个Job实现类的JobDetail对象在成功执行execute方法之后都会更新JobDataMap中的值。

如果使用@PersistJobDataAfterExecution,建议同时使用@DisallowConcurrentExecution注解,这样可以避免JobDetail并发执行时,同时对其中的JobDataMap进行更新所造成的数据更新异常。

4. 触发器(Triggers)

Quartz采用了任务(Job)和触发器(Trigger)分为两个对象的方式,官方认为,通过触发器和任务的解偶,能够降低编码量,提高复用性。
Trigger触发器对象也含一个JobDataMap对象,用来将触发器的数据传送给被触发的任务对象。Quartz有多种触发器类型,最常用的是SimpleTrigger和CronTrigger。SimpleTrigger用来实现简单的触发器。例如用来在一个固定的时间触发,然后固定间隔时间重复触发。CronTrigger用来实现基于类日历调度器的较复杂的触发器,例如每周五中午、每月10日10点15分等。

4.1 触发器的一些共有特性

4.1.1 triggerKey
如本部门第3.2节介绍JobDetail对象时所使用的示例代码,定义Trigger对象的时候可以使用withIdentity()方法来为其指定触发器标识,便于任务调度器来管理。触发器标识与任务标识一样,包含组和名两部分,同一个任务调度器实例内,触发器标识需要唯一。

4.1.2 jobKey
当此Trigger触发器触发时,除了一同被注册到任务调度器实例中的JobDetail对象外,标识为Trigger对象所存储的jobKey的JobDetail对象也会被执行。

4.1.3 startTime endTime
为触发器设置一个工作时间范围,在此时间范围之外的触发时机都不会触发任务执行,也就是说只有在此时间范围内的触发才是有效触发。

4.1.4 优先级 - Priority
若一个任务调度器的资源不足以运行大量触发任务时,会引起触发竞争问题,也就是说在某一个时刻有多个触发器需要触发,但是工作线程只满足其中部分触发器正常触发任务执行。这时,触发器中priority优先级属性就起到了作用,值越大的优先级越高,首先被任务调度器执行。priority属性为带符号的整数,不指定的话默认值为5.
priority优先级属性仅对于需要在某个时刻同时触发,并且工作线程不足够同时触发任务执行的情况使用。如果竞争触发器之间的计划触发时间不同,那么不管优先级是哪个高,都会首先触发计划时间较早的触发器。
如果空闲工作线程数量大于等于同一时刻计划触发的触发器数量,那么不存在触发竞争问题。
如果集群中的某个任务调度器宕机,导致其正在运行的任务中断,并且在数据库中此条任务被标记为“需要恢复”,那么当集群中的其他任务调度器重新触发此任务时,其拥有和之前触发时相同的优先级。

4.1.5 漏触发处理机制
以下情况会造成该触发的认为未被触发:触发时机发生时,系统处于关机状态;触发时机发生时,系统资源不足。如果某个任务错过了触发时机,不同的触发器会有不同的机制来处理漏掉的触发。
默认触发器会使用“智能策略 smart policy”来处理漏触发,smart policy会根据不同的触发器类型和配置采取不同的处理机制。
当一个任务调度实例启动时,会扫描是否有保存的漏触发,然后根据漏触发解决机制来处理这些漏触发。
因为触发执行的任务会影响到数据、系统状态等,所以需要了解系统中采用的什么样的漏触发处理机制,这样才能全面了解任务的执行情况。
后面介绍主要触发器的时候,会再介绍各自的漏触发处理机制。

4.1.6 Calendars对象
Calendars对象用来提供另外一种触发时机管理方式。Calendar接口如下:

public interface Calendar {
  public boolean isTimeIncluded(long timeStamp);
  public long getNextIncludedTime(long timeStamp);
}

Quartz中提供了Calendar接口,以及若干实现类,也可以自定义Calendar实现类,需要实现Calendar接口及Serializable接口。
Calendars对象的典型应用是,设定任务调度程序中的触发器仅在工作日触发,Quartz提供的org.quartz.impl.HolidayCalendar类即可实现此需求。
可以使用触发器实例的addCalendar()方法来为一个触发器对象设定Calendar对象。
以下是一个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

以上代码中创建了两个触发器实例,除了为每个触发器设定的触发计划外,还会跳过Calendar对象所设定的排除时间。后面会介绍更多触发器Trigger对象的创建细节。

4.2 SimpleTrigger

4.2.1 SimpleTrigger介绍
SimpleTrigger触发器用来定义在未来的某个时刻开始触发,并间隔固定时间段,重复固定次数的触发器。为达到此目的,主要包含以下参数:

  • 开始时间为触发器首次触发时刻,如果不指定的话,默认是当前时间。
  • 结束时间之后,即使还没有触发完设定的触发次数,触发器也将不再触发。
  • 重复次数可以为0,正整数,或者是常量SimpleTrigger.REPEAT_INDEFINITELY(表示无数次)。
  • 重复间隔时间可以为0,或者无符号的long型值,单位为毫秒。如果重复间隔时间设置为0,那么任务调度器会用最大的资源尽力以并发的形式同时触发执行全部设置的触发次数。

4.2.2 SimpleTrigger的使用
SimpleTrigger实例是通过TriggerBuilder类和SimpleScheduleBuilder两个类来创建的,要使用DSL风格的代码来创建SimpleTrigger实例,需要按照如下静态导入相应类:

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

以下是一些典型的SimpleTrigger的定义,SimpleTrigger还有更多用法,需要参阅其java doc。

  • 未来的一个时刻触发一次

      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();
    
  • 4.2.3 五分钟后,触发一次

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

4.2.3 SimpleTrigger的漏触发处理机制
除了所有触发器都有的 Smart Policy(MISFIRE_INSTRUCTION_SMART_POLICY)之外,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

每种机制的执行原理可以参考java doc。
Smart Policy的执行方式可以参考SimpleTrigger.updateAfterMisfire()的java doc。
以下代码示例了如何为SimpleTrigger指定漏触发处理机制:

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

4.3.1 CronTrigger介绍
CronTrigger可以用来创建类似于“每周五中午”、“每周三早上9:30”或“一月份每周一、三、五早上9:00到10:00之间每隔5分钟触发一次”这样的触发计划。

4.3.2 Cron表达式
Cron表达式,用来描述一个触发计划。由七个使用空格分隔开的子表达式组成,七个子表达式分别用来表示:

  1. 秒 (0-59)
  2. 分钟 (0-59)
  3. 小时 (0-23)
  4. 日 (1-31)
  5. 月 (0-11)(JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC)
  6. 星期 (1-7)(SUN,MON,TUE,WED,THU,FRI,SAT)

一个Cron表达式的示例是:“0 0 12 ? * WED”,表示“每周三12点”

  • 子表达式可以是一个值、一个范围或者一个列表,例如上面示例中的WED也可以为“MON-FRI”,或者“MON,WED,FRI”,甚至是范围和列表的混用"MON-WED,SAT"。
  • 子表达式取值,应该在上面各自表达式表示意义后面的括号中的取值范围中。
  • *通配符,可用在任意一个子表达式,代表此子表达式的所有可能值的集合
  • /符号,用来描述时间增量。例如 如果分钟子表达式为“0/15”,则表示每个小时的从第0分钟开始,每隔15分钟触发一次,也就是第0、15、30、45分钟各触发一次。如果分钟子表达式为"3/20",表示每个小时从第3分钟开始,每隔20分钟触发一次,也就是第3、23、43分钟各触发一次。“/35”与“0/35”的意义相同。
  • ?占位符,表示不指定特定值,只能用在日和星期两个子表达式上
  • L字符,表示last(最后),只能用在日和星期两个子表达式上。L单独使用表示相应子表达式的最后一天:如果L单独使用在日子表达式上面,表示当月的最后一天;如果L单独使用在星期子表达式上,表示星期六(7,SAT)。L与其他有效值连用表示其他意义:和其他有效值使用在星期子表达式上面,表示当月的最后若干天或者当月的最后一个星期几。例如:星期子表达式“6L”或“FRIL”都表示“当月的最后一个星期五”;日子表达式“L-3”表示当月倒数第三天。L字符不能和列表、范围等值联合使用,避免组成引起歧义的组合。
  • W字符用在日子表达式上,用来指定一个离给定日期最近的工作日。例如,在日子表达式上面使用“15W”表示“当月离15日最近的工作日”
  • #用来指定当月第(指定)个星期几。例如,在星期子表达式上面使用“6#3”或者“FRI#3”表示“当月的第三个星期五”.

4.3.3 几个Cron表达式的示例

  • “0 0/5 * * * ?”: 每隔5分钟触发
  • “0 0/5 * * * ?”: 每隔5分钟在第10秒触发
  • “0 30 10-13 ? * WED,FRI ?":每周三、周五的10:30,11:30,12:30和13:30触发
  • "0 0/30 8-9 5,20 * * ?": 每个月的5日和20日,8点到10点之间,每隔半小时触发,(10点不触发)

Cron表达式对于定义时间计划已经很强大,但是部分情况依然难以通过一条Cron表达式来表示,例如“早上9:00到10:00每5分钟触发一次,另外下午1:00到10:00每20分触发一次”,这样的情况可以定义两个CronTrigger,并且和同一个任务注册进任务调度器中。

4.3.4 创建CronTrigger
通过以下代码,静态引入CronTrigger和CronScheduleBuilder类,即可使用DSL编程风格来创建CronTrigger

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

以下代码,示例创建了一个CronTrigger:“每天早上8点到下午5点,每隔1分钟触发”

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

以下两段代码都创建了触发器:“每天早上10:42触发”

//代码1
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
//代码2
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 * * ?"))
.forJob(myJobKey)
.build();

以下两段代码创建了触发器:“指定时区内的每个星期三早上10:42触发”

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

4.3.5 CronTrigger的漏触发处理机制
CronTrigger默认使用的“Smart Policy”处理机制为:Trigger.MISFIRE_INSTRUCTION_SMART_POLICY,其在CronTrigger中的具体处理机制在CronTrigger.MISFIRE_INSTRUCTION_FIRE_NOW这个常量的java doc说明中。CronTrigger.updateAfterMisfire()方法也有详细解释。
除了所有Trigger都有的“smart policy”,CronTrigger中还有以下几种漏触发处理机制(以常量形式定义在CronTrigger类中):

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW

以下代码示例了如何为CronTrigger指定漏触发处理机制

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

5. 触发器监听器(TriggerListener)和任务监听器(JobListener)

5.1 监听器接口介绍

Quartz提供了任务监听器和触发器监听器,用来监听任务和触发器的相关事件。
触发器监听器可以监听触发器的触发事件、漏触发事件、触发完成事件。触发器监听器的接口定义如下:
org.quartz.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);
}

任务相关的事件有:任务即将被执行的通知、任务执行完毕的通知,任务监听器接口定义如下:
org.quartz.JobListener

 public interface JobListener {
public String getName();
public void jobToBeExecuted(JobExecutionContext context);
public void jobExecutionVetoed(JobExecutionContext context);
public void jobWasExecuted(JobExecutionContext context,
        JobExecutionException jobException);
}
5.2 创建自己的监听器

监听器在Qaurat中是不常被使用的功能,因为直接在任务中硬编码也能够在出发的不同阶段来做一些相应的工作。但是使用监听器的代码会更加稳定可靠,并且简单。
有两种方式来创建自己的任务监听器或者触发器监听器。
第一种是实现以上任务监听器接口或触发器监听器接口;另一种方式是继承Quartz中的org.quartz.JobListener接口,并重写其中你需要监听的事件方法。
定义监听器时,必须给定一个名称,这样才能通过其getName方法来获取到其名称。
监听器是通过任务调度器的ListenerManager注册,并会使用Matcher来分配一个任务或者触发器来监听其事件。

需要注意的是,监听器不会与Job和Trigger一起呗存入JobStore,因此运行任务调度器时,都需要重新注册一下监听器,否则相应任务或者触发器的事件不会被监听到。
以下代码示例了如何往任务调度器中注册一个监听器:

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

使用DSL风格的代码为:

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

当然,要使用DSL风格代码,需要静态引入相关类:

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.*;

以下代码示例了几种典型的监听器的注册:

    //注册一个监听指定组中所有任务的监听器
    scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
    //注册一个监听两个组中所有任务的监听器
    scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));
    //注册一个监听所有任务的监听器
    scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

为触发器注册监听器,同以上为任务注册监听器的方式一致。

6.任务调度器监听器(SchedulerListener)

任务调度器监听器同上面的任务监听器和触发器监听器类似,不同的是任务调度器监听器不需要绑定任务或者触发器,其监听的是任务调度器本身的事件,主要有以下几种:

  • 添加或者移除了任务或者触发器

  • 任务调度器抛出的错误信息

  • 任务调度器即将被关闭等
    org.quartz.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();
    }
    任务调度器监听器是实现了上面的SchedulerListener接口的实现类的实例,通过任务调度器实例的ListenerManager属性来注册到任务调度器中。
    往任务调度器实例中注册一个调度器监听器
    scheduler.getListenerManager().addSchedulerListener(mySchedListener);
    从任务调度器实例中移除一个任务调度器实例
    scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

7.任务信息的保存 Job Stores

JobStrore用来保存所有任务调度器的工作数据,包括 任务、触发器、日历等数据。
任何情况下,不需要在代码中包含任何与JobStore相关的内容,应该在配置文件中指定JobStore,以及配置相应属性。JobStore仅用来支持任务调度器实例的工作,配置文件中定义好后,任务调度器实例即可使用。

7.1 RAMJobStore

RAMJobStore是最简单,也是性能最好的JobStore,因为它直接将工作数据保存在了内存中,没有进行持久化操作。这样也导致当任务调度器实例正常或者异常关闭之后,所有的任务和触发器信息就完全消失。一些轻量化的对状态的一致性要求不是很严格的任务调度程序可以使用这个RAMJobStore来作为调度器工作数据的保存方式。
使用RAMJobStore仅需要在配置文件中添加如下一行即可:
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

7.2 JDBCJobStore

JDBCJobStore可以将任务调度器的工作数据进行持久化,支持几乎所有的数据库,例如:Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB, 和DB2等。
7.2.1 使用方式简述
*在数据库中创建quartz使用的数据表
要使用JDBCJobStore实现任务调度器工作数据的持久化,需要首先定义一组quartz的数据表,在下载的压缩包中的docs/dbTables文件夹下面有对应于各种数据库的建表sql。默认情况下所有的表明都是以QRTZ_作为前缀,并且配置文件中的数据表前缀配置项也应该设置为QRTZ_。如果建表时没有采用默认的前缀,需要把配置文件中也修改为相应的前缀。如果多个任务调度器实例共用一个数据库,可以通过使用不同数据表名前缀的方式,避免互相影响
*选择事务管理方式
JDBCJobStore按照事务管理方式不同,有两个实现,一个是JobStoreTX使用quartz本身的事务管理。另外一个是JobStoreCMT,支持通过J2EE系统的事务管理quart的事务。
*配置数据源
接下来就是为quartz配置数据库连接了,通过配置数据源DataSource来告诉quartz如何获取数据库连接。
可以在quartz.properties中直接配置数据源的信息,也可以在quartz.properties中配置外部程序中的数据源。具体配置方式参照配置DataSource
7.2.2 具体的配置方式

  • 配置JDBCJobStore,根据事务管理方式的不同,选择JobStoreTX或JobStoreCMT
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
  • 配置JDBC代理类,来具体进行JDBC操作。quartz的org.quartz.impl.jdbcjobstore包及其子包中中提供了DB2v6Delegate(用于DB2 v6及之前版本),MSSQLDelegate(用于SQLServer),HSQLDBDelegate(用于HSQLDB),PostgreSQLDelegate(用于PostgreSQL),WeblogicDelegate(用于Weblogic实现的JDBC驱动),OracleDelegate(用于Oracle)等,如果你的数据库找不到专用的jdbc代理,那么可以尝试使用StdJDBCDelegate代理类。
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
  • 配置quartz数据表的前缀
    org.quartz.jobStore.tablePrefix = QRTZ_
  • 配置DataSource
    #指定使用的DataSource名称 org.quartz.jobStore.dataSource = myDS

如果任务调度器中注册的触发器和任务较多,需要将数据源的连接数设置为线程池数量+2
可以将org.quartz.jobStore.userProperties设置为true(默认为false),这样可以限定JobDataMap对象中仅能存储String类型值。这样可以避免存储复杂对象到JobDataMap中时,序列化复杂对象为BLOB类型,产生类的版本问题。

8.资源占用和SchedulerFactory相关的配置

Quartz是一个功能全面强大的任务调度强大的任务调度器,它通过多个模块化部件的精确配合完成任务调度工作。为了能够实现这样的效果,有以下几个主要部件需要配置:

8.1 ThreadPool(线程池)

quartz内置的org.quartz.simpl.SimpleThreadPool是一个固定线程数线程池,推荐使用此内置线程池。使用的时候应该根据任务调度器中注册的任务数量,以及任务的运行时间等来为其配置合适的县城数量。如果线程池中的超线程数量过多,会影响系统整体性能,但是如果过少,则会造成延迟触发,延迟时间超过漏触发阈值的化,还会造成漏触发。

8.2 JobStore(工作数据保存方式)

除了使用quart内置的集中工作数据保存方式JobStore实现类,还可以自己实现JobStore接口来自定义工作数据保存方式。

8.3 DataSources

可以根据需要,配置数据库数据源

8.4 Scheduler(任务调度器本身)

关于任务调度器本身的设置,主要有:名称,RMI设置,线程池设置,工作数据存储设置。quartz提供了以下两种任务调度器工厂来获取任务调度器实例。
8.4.1 StdScheduleFactory
使用StdScheduleFactoryde的getScheduler()方法获取任务调度器实例的时候,StdScheduleFactory工厂会自动获取quartz.properties文件中的配置信息,对任务调度器实例进行初始化。
8.4.2 DirectSchedulerFactory
DirectSchedulerFactory主要用于变成方式初始化任务调度器实例的场景。但是不推荐使用,原因如下:1.用户必须更加清晰的理解他们需要一个什么样的任务调度器;2.所有的代码写死,不利于配置和修改。

8.5 日志

quartz使用了SLF4J框架来实现日志功能,可以通过配置SLF4J来设置quartz的日志输出级别等。
如果需要记录更多关于触发器和任务运行的信息,可以激活下下两项配置:
org.quartz.plugins.history.LoggingJobHistoryPlugin and/or org.quartz.plugins.history.LoggingTriggerHistoryPlugin

9. 企业级功能

9.1 集群

quartz的集群系统,具有负载均衡和故障容错能能力。
通过运行多个拥有相同配置文件(允许不同的配置项可以不同)的quartz实例,并且他们链接了同一个数据库中的同一套quartz数据表,即可实现quartz的集群功能.

9.2 JTA事务管理

使用JobStoreCMT作为工作数据保存的持久化方式,可以将quartz嵌入进外部系统的事务管理中,实现更大范围的事务管理。
9.2.1 将全部任务的执行都纳入JTA事务管理
如下设置会在每个任务的execute方法被调用前,指定事务管理的begin方法,在execute方法执行完毕后,调用事务管理的commit方法。来实现JTA事务管理对于所有任务执行的事务管理。
org.quartz.scheduler.wrapJobExecutionInUserTransaction=true
9.2.2 在任务的粒度级别控制事务管理方式
如果你只需要部分任务接受JTA事务管理,那么可以在Job实现类上面使用 @ExcuteInJTATransaction注解来指定此任务执行时接受JTA事务管理。

10.其他

10.1 Plug-Ins

quartz提供了org.quartz.spi.SchedulerPlugin接口来扩展更多的功能。
quartz本身已经有了一些Plugins在org.quartz.plugins包中,实现了当调度器启动时自动计划任务;显示任务执行和触发时间的历史日志;确保JVM退出时,quartz的调度器实例能够干净的关闭等功能。

10.2 JobFactory

触发器触发时,任务调度器实例就会使用其JobFactory属性对象来讲同事注册的任务实现类实例化。默认的JobFactory类会调用Job实现类的newInstance方法。如果你需要通过IOC或者DI容器来管理JobFactory对象,实现Job实现类的实例化,那么可以自行实现JobFactory接口来自定义JobFactory。

10.3 预置Job

org.quartz.jobs已经预置了一些工具任务,例如发送电子邮件,调用EJB等。

四、使用方法

1. 导入quartzAPI

import static org.quartz.JobBuilder.*; import static org.quartz.TriggerBuilder.*; import static org.quartz.SimpleScheduleBuilder.*; import static org.quartz.CronScheduleBuilder.*; import static org.quartz.CalendarIntervalScheduleBuilder.*; import static org.quartz.JobKey.*; import static org.quartz.TriggerKey.*; import static org.quartz.DateBuilder.*; 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.*;

2.获取任务调度器实例

2.1 实例化默认的任务调度器

// 默认的任务调度器实例,会去classpath中寻找quartz.properties文件, //并使用其中配置来实例化任务调度器, //如果没有找到,那么会使用quartzJAR包中的配置文件来实例化任务调度器。 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler scheduler = sf.getScheduler(); // start方法执行之后,注册的触发器和任务才会开始工作。 scheduler.start();

2.2 通过指定的Properties对象来实例化任务调度器

StdSchedulerFactory sf = new StdSchedulerFactory(); sf.initialize(schedulerProperties); Scheduler scheduler = sf.getScheduler(); scheduler.start();

2.3 通过一个指定的配置文件来实例化任务调度器

StdSchedulerFactory sf = new StdSchedulerFactory(); sf.initialize(fileName); Scheduler scheduler = sf.getScheduler(); scheduler.start();

3.挂起任务调度器(待机模式)

3.1将任务调度器切换到挂起模式

// start()已经开始过,任务调度器正常调度任务执行 scheduler.standby(); // 触发器将会暂停触发任务执行 // ... scheduler.start(); // 触发器可以触发任务执行

4.关闭任务调度器

任务调度器实例的shutdown()方法可以关闭(销毁)此热舞调度器实例。当一个任务调度器被关闭,其所查勇的所有资源都会被释放,那么久不能被重新启动。

//等待所有正在执行的任务执行完毕后,任务管理器再关闭 scheduler.shutdown(true); //立即退出 scheduler.shutdown(); //或者 scheduler.shutdown(false);
如果使用的是servlet通过org.quartz.ee.servlet.QuartzInitializerListener启动的quartz任务调度器,当容器关闭时会调用QuartzinitailizerListener的contextDestroyed()方法来退出任务调度器。

5.在servlet容器中实例化一个任务调度器

有两种配置方式可以实现在servlet容器中启动(实例化)quartz任务调度器

5.1通过servlet容器的context listener入口启动quartz

... <context-param> <param-name>quartz:config-file</param-name> <param-value>/some/path/my_quartz.properties</param-value> </context-param> <context-param> <param-name>quartz:shutdown-on-unload</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>quartz:wait-on-shutdown</param-name> <param-value>false</param-value> </context-param> <context-param> <param-name>quartz:start-scheduler-on-load</param-name> <param-value>true</param-value> </context-param> ... <listener> <listener-class> org.quartz.ee.servlet.QuartzInitializerListener </listener-class> </listener> ...

5.2 通过servlet容器配置一个servlet来启动quartz
...
<servlet>
  <servlet-name>QuartzInitializer</servlet-name>
  <servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>
  <init-param>

    <param-name>shutdown-on-unload</param-name>
    <param-value>true</param-value>
  </init-param>
  <load-on-startup>2</load-on-startup>

</servlet>
...

6.定义一个任务

实现一个Job实现类
public class PrintPropsJob implements Job { public PrintPropsJob() { // Instances of Job must have a public no-argument constructor. } public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap data = context.getMergedJobDataMap(); System.out.println("someProp = " + data.getString("someProp")); } }

定义一个任务对象
// Define job instance JobDetail job1 = JobBuilder.newJob(MyJobClass.class) .withIdentity("job1", "group1") .usingJobData("someProp", "someValue") .build();

7.计划一个任务

`// Define job instance
JobDetail job1 = JobBuilder.newJob(ColorJob.class)
.withIdentity("job1", "group1")
.build();

// Define a Trigger that will fire "now", and not repeat
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.build();

// Schedule the job with the trigger
sched.scheduleJob(job, trigger);`

8.取消一个已经计划的任务

8.1 取消某个任务的某个触发器

// Unschedule a particular trigger from the job (a job may have more than one trigger) scheduler.unscheduleJob(triggerKey("trigger1", "group1"));

8.2 取消一个任务的所有触发器

// Schedule the job with the trigger scheduler.deleteJob(jobKey("job1", "group1"));

9.注册一个任务,暂不注册相应触发器,以后再注册

// Define a durable job instance (durable jobs can exist without triggers) JobDetail job1 = newJob(MyJobClass.class) .withIdentity("job1", "group1") .storeDurably() .build(); // Add the the job to the scheduler's store sched.addJob(job, false);

10.为一个已经注册的任务,注册触发器

11.更新一个已经注册的任务

// Add the new job to the scheduler, instructing it to "replace" // the existing job with the given name and group (if any) JobDetail job1 = newJob(MyJobClass.class) .withIdentity("job1", "group1") .build(); // store, and set overwrite flag to 'true' scheduler.addJob(job1, true);

12. 更新一个已经注册的触发器

// Define a new Trigger Trigger trigger = newTrigger() .withIdentity("newTrigger", "group1") .startNow() .build(); // tell the scheduler to remove the old trigger with the given key, and put the new one in its place sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger);
或者
`// retrieve the trigger
Trigger oldTrigger = sched.getTrigger(triggerKey("oldTrigger", "group1");

// obtain a builder that would produce the trigger
TriggerBuilder tb = oldTrigger.getTriggerBuilder();

// update the schedule associated with the builder, and build the new trigger
// (other builder methods could be called, to change the trigger in any desired way)
Trigger newTrigger = tb.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)
.build();
sched.rescheduleJob(oldTrigger.getKey(), newTrigger);`

13.使用xml来为任务调度器注册任务

五、 配置手册

1. 配置文件的加载机制

  • 调用StdSchedulerFactory的getScheduler()方法,获得一个调度器实例的时候,StdSchedulerFactory类会去查找quartz.properties文件来配置自己。
  • StdSchedulerFactory首先会在当前工作路径(或者classpath)查找quartz.properties文件
  • 你也可以通过系统环境变量'org.quartz.properties'指定quartz.properties的位置,StdSchedulerFactory会在这个环境变量中的路径下面寻找配置文件。,
  • 如果都没有找到配置文件的话,StdSchedulerFactory会使用quartz包内部的quartz.properties文件来配置

2. 配置文件基本语法

  • 在实例化JobStore,ThreadPool等后,会自动将配置文件中为其定义的属性值set到实例对象中。例如,如果配置文件中定义了'org.quartz.jobStore.myProp=10',那么当JobStore类实例化后,会调用其setMyProp方法,将属性myProp设置为10。 在调用set方法之前,会自动将配置文件中的值转换为java的基础数据类型(int,long,float,double,boolean,String)
  • 如果在配置文件中,需要设置某个对象属性引用另外对象属性值的话,可以采用"$@other.property.name"的语法。例如,要为某个对象属性赋值为调度器的名字,写法如下:"$@org.quartz.scheduler.instanceName"

3. 可配置项

3.1 基础配置
Property Name Req'd Type Default Value
org.quartz.scheduler.instanceName no string 'QuartzScheduler'
org.quartz.scheduler.instanceId no string 'NON_CLUSTERED'
org.quartz.scheduler.instanceIdGenerator.class no string (class name) org.quartz.simpl
.SimpleInstanceIdGenerator
org.quartz.scheduler.threadName no string instanceName
+ '_QuartzSchedulerThread'
org.quartz.scheduler
.makeSchedulerThreadDaemon
no boolean false
org.quartz.scheduler
.threadsInheritContextClassLoaderOfInitializer
no boolean false
org.quartz.scheduler.idleWaitTime no long 30000
org.quartz.scheduler.dbFailureRetryInterval no long 15000
org.quartz.scheduler.classLoadHelper.class no string (class name) org.quartz.simpl
.CascadingClassLoadHelper
org.quartz.scheduler.jobFactory.class no string (class name) org.quartz.simpl.PropertySettingJobFactory
org.quartz.context.key.SOME_KEY no string none
org.quartz.scheduler.userTransactionURL no string (url) 'java:comp/UserTransaction'
org.quartz.scheduler
.wrapJobExecutionInUserTransaction
no boolean false
org.quartz.scheduler.skipUpdateCheck no boolean false
org.quartz.scheduler
.batchTriggerAcquisitionMaxCount
no int 1
org.quartz.scheduler
.batchTriggerAcquisitionFireAheadTimeWindow
no long 0

org.quartz.scheduler.instanceName
Can be any string, and the value has no meaning to the scheduler itself - but rather serves as a mechanism for client code to distinguish schedulers when multiple instances are used within the same program. If you are using the clustering features, you must use the same name for every instance in the cluster that is ‘logically’ the same Scheduler.

org.quartz.scheduler.instanceId
Can be any string, but must be unique for all schedulers working as if they are the same ‘logical’ Scheduler within a cluster. You may use the value “AUTO” as the instanceId if you wish the Id to be generated for you. Or the value “SYS_PROP” if you want the value to come from the system property “org.quartz.scheduler.instanceId”.

org.quartz.scheduler.instanceIdGenerator.class
Only used if org.quartz.scheduler.instanceId is set to “AUTO”. Defaults to “org.quartz.simpl.SimpleInstanceIdGenerator”, which generates an instance id based upon host name and time stamp. Other IntanceIdGenerator implementations include SystemPropertyInstanceIdGenerator (which gets the instance id from the system property “org.quartz.scheduler.instanceId”, and HostnameInstanceIdGenerator which uses the local host name (InetAddress.getLocalHost().getHostName()). You can also implement the InstanceIdGenerator interface your self.

org.quartz.scheduler.threadName
Can be any String that is a valid name for a java thread. If this property is not specified, the thread will receive the scheduler’s name (“org.quartz.scheduler.instanceName”) plus an the appended string ‘_QuartzSchedulerThread’.

org.quartz.scheduler.makeSchedulerThreadDaemon
A boolean value (‘true’ or ‘false’) that specifies whether the main thread of the scheduler should be a daemon thread or not. See also the org.quartz.scheduler.makeSchedulerThreadDaemon property for tuning the SimpleThreadPool if that is the thread pool implementation you are using (which is most likely the case).

org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer
A boolean value (‘true’ or ‘false’) that specifies whether the threads spawned by Quartz will inherit the context ClassLoader of the initializing thread (thread that initializes the Quartz instance). This will affect Quartz main scheduling thread, JDBCJobStore’s misfire handling thread (if JDBCJobStore is used), cluster recovery thread (if clustering is used), and threads in SimpleThreadPool (if SimpleThreadPool is used). Setting this value to ‘true’ may help with class loading, JNDI look-ups, and other issues related to using Quartz within an application server.

org.quartz.scheduler.idleWaitTime
Is the amount of time in milliseconds that the scheduler will wait before re-queries for available triggers when the scheduler is otherwise idle. Normally you should not have to ‘tune’ this parameter, unless you’re using XA transactions, and are having problems with delayed firings of triggers that should fire immediately. Values less than 5000 ms are not recommended as it will cause excessive database querying. Values less than 1000 are not legal.

org.quartz.scheduler.dbFailureRetryInterval
Is the amount of time in milliseconds that the scheduler will wait between re-tries when it has detected a loss of connectivity within the JobStore (e.g. to the database). This parameter is obviously not very meaningful when using RamJobStore.

org.quartz.scheduler.classLoadHelper.class
Defaults to the most robust approach, which is to use the “org.quartz.simpl.CascadingClassLoadHelper” class - which in turn uses every other ClassLoadHelper class until one works. You should probably not find the need to specify any other class for this property, though strange things seem to happen within application servers. All of the current possible ClassLoadHelper implementation can be found in the org.quartz.simpl package.

org.quartz.scheduler.jobFactory.class
The class name of the JobFactory to use. A JobFatcory is responsible for producing instances of JobClasses. The default is ‘org.quartz.simpl.PropertySettingJobFactory’, which simply calls newInstance() on the class to produce a new instance each time execution is about to occur. PropertySettingJobFactory also reflectively sets the job’s bean properties using the contents of the SchedulerContext and Job and Trigger JobDataMaps.

org.quartz.context.key.SOME_KEY
Represent a name-value pair that will be placed into the “scheduler context” as strings. (see Scheduler.getContext()). So for example, the setting “org.quartz.context.key.MyKey = MyValue” would perform the equivalent of scheduler.getContext().put(“MyKey”, “MyValue”).

The Transaction-Related properties should be left out of the config file unless you are using JTA transactions.
org.quartz.scheduler.userTransactionURL

Should be set to the JNDI URL at which Quartz can locate the Application Server’s UserTransaction manager. The default value (if not specified) is “java:comp/UserTransaction” - which works for almost all Application Servers. Websphere users may need to set this property to “jta/usertransaction”. This is only used if Quartz is configured to use JobStoreCMT, and org.quartz.scheduler.wrapJobExecutionInUserTransaction is set to true.

org.quartz.scheduler.wrapJobExecutionInUserTransaction
Should be set to “true” if you want Quartz to start a UserTransaction before calling execute on your job. The Tx will commit after the job’s execute method completes, and after the JobDataMap is updated (if it is a StatefulJob). The default value is “false”. You may also be interested in using the @ExecuteInJTATransaction annotation on your job class, which lets you control for an individual job whether Quartz should start a JTA transaction - whereas this property causes it to occur for all jobs.

org.quartz.scheduler.skipUpdateCheck
Whether or not to skip running a quick web request to determine if there is an updated version of Quartz available for download. If the check runs, and an update is found, it will be reported as available in Quartz’s logs. You can also disable the update check with the system property “org.terracotta.quartz.skipUpdateCheck=true” (which you can set in your system environment or as a -D on the java command line). It is recommended that you disable the update check for production deployments.

org.quartz.scheduler.batchTriggerAcquisitionMaxCount
The maximum number of triggers that a scheduler node is allowed to acquire (for firing) at once. Default value is 1. The larger the number, the more efficient firing is (in situations where there are very many triggers needing to be fired all at once) - but at the cost of possible imbalanced load between cluster nodes. If the value of this property is set to > 1, and JDBC JobStore is used, then the property “org.quartz.jobStore.acquireTriggersWithinLock” must be set to “true” to avoid data corruption.

org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow
The amount of time in milliseconds that a trigger is allowed to be acquired and fired ahead of its scheduled fire time.
Defaults to 0. The larger the number, the more likely batch acquisition of triggers to fire will be able to select and fire more than 1 trigger at a time - at the cost of trigger schedule not being honored precisely (triggers may fire this amount early). This may be useful (for performance’s sake) in situations where the scheduler has very large numbers of triggers that need to be fired at or near the same time.

3.2 配置线程池,用来优化任务调度器的资源占用
Property Name Required Type Default Value
org.quartz.threadPool.class yes string (class name) null
org.quartz.threadPool.threadCount yes int -1
org.quartz.threadPool.threadPriority no int Thread.NORM_PRIORITY (5)
org.quartz.threadPool.makeThreadsDaemons no boolean false
org.quartz.threadPool.threadsInheritGroupOfInitializingThread no boolean true
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread no boolean false
org.quartz.threadPool.threadNamePrefix no string [Scheduler Name]_Worker

org.quartz.threadPool.class
Is the name of the ThreadPool implementation you wish to use. The threadpool that ships with Quartz is “org.quartz.simpl.SimpleThreadPool”, and should meet the needs of nearly every user. It has very simple behavior and is very well tested. It provides a fixed-size pool of threads that ‘live’ the lifetime of the Scheduler.

org.quartz.threadPool.threadCount
Can be any positive integer, although you should realize that only numbers between 1 and 100 are very practical. This is the number of threads that are available for concurrent execution of jobs. If you only have a few jobs that fire a few times a day, then 1 thread is plenty! If you have tens of thousands of jobs, with many firing every minute, then you probably want a thread count more like 50 or 100 (this highly depends on the nature of the work that your jobs perform, and your systems resources!).

org.quartz.threadPool.threadPriority
Can be any int between Thread.MIN_PRIORITY (which is 1) and Thread.MAX_PRIORITY (which is 10). The default is Thread.NORM_PRIORITY (5).

org.quartz.threadPool.makeThreadsDaemons
Can be set to “true” to have the threads in the pool created as daemon threads. Default is “false”. See also the org.quartz.scheduler.makeSchedulerThreadDaemon property.

org.quartz.threadPool.threadsInheritGroupOfInitializingThread
Can be “true” or “false”, and defaults to true.

org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread
Can be “true” or “false”, and defaults to false.

org.quartz.threadPool.threadNamePrefix
The prefix for thread names in the worker pool - will be postpended with a number.

使用指定的线程池

org.quartz.threadPool.class = com.mycompany.goo.FooThreadPool
org.quartz.threadPool.somePropOfFooThreadPool = someValue
3.3 配置全局监听器,获得调度器各事件通知

可以在配置文件中配置全局监听器,只需要指明监听器名称、监听器类、以及监听器类需要初始化的属性值。监听器类需要提供无参的构造器,需要通过配置文件初始化的属性必须是java基本类型。如下是定义触发器事件监听器和任务事件监听器的方式:

#配置触发器监听器
org.quartz.triggerListener.NAME.class = com.foo.MyListenerClass
org.quartz.triggerListener.NAME.propName = propValue
org.quartz.triggerListener.NAME.prop2Name = prop2Value
#配置任务监听器
org.quartz.jobListener.NAME.class = com.foo.MyListenerClass
org.quartz.jobListener.NAME.propName = propValue
org.quartz.jobListener.NAME.prop2Name = prop2Value
3.4 配置扩展插件,为调度器添加功能

为quartz添加扩展功能的配置方法和配置全局监听器类似,需要指明扩展名字、扩展实
现类、以及为扩展实现类准备的初始化属性值。扩展实现类需要提供无参构造,并且需要初始化的属性值必须是java的基本数据类型。如下是配置扩展功能的语法:

org.quartz.plugin.NAME.class = com.foo.MyPluginClass
org.quartz.plugin.NAME.propName = propValue
org.quartz.plugin.NAME.prop2Name = prop2Value

Quartz包中自带了几个常用的扩展,以下是一些自带的扩展功能的配置示例:

#Logging Trigger History Plugin会响应触发器的触发事件,然后使用Jakarta Commons-Logging进行日志记录。详细参见java doc
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger \{1\}.\{0\} fired job \{6\}.\{5\} at: \{4, date, HH:mm:ss MM/dd/yyyy}
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger \{1\}.\{0\} completed firing job \{6\}.\{5\} at \{4, date, HH:mm:ss MM/dd/yyyy\}.

#XML Scheduling Data Processor Plugin,使调度器启动后自动从一个xml文件中加载任务和触发器信息。
#用来定义任务和触发器信息的xml文件 schema definition为:http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.jobInitializer.fileNames = data/my_job_data.xml
org.quartz.plugin.jobInitializer.failOnFileNotFound = true

#Shutdown Hook Plugin,会捕捉JVM终止的事件,调用调度器的shudown()方法。
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true
3.5 配置RMI服务和客户端,远程调用一个远程的调度器实例
3.6 配置RAMJobStore,在内存中存放各实例数据

必须为quartz指定任务、触发器和日历等信息的保存位置,最简单的实现方式是使用RAMJobStore保存在内存中

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

另外需要根据情况来优化quartz的内存资源占用,通过以下属性来优化:

Property Name Required Type Default Value
org.quartz.jobStore.misfireThreshold no int 60000

org.quartz.jobStore.misfireThreshold
The the number of milliseconds the scheduler will ‘tolerate’ a trigger to pass its next-fire-time by, before being considered “misfired”. The default value (if you don’t make an entry of this property in your configuration) is 60000 (60 seconds).

3.7 配置JSBC-JobStoreTX,通过JDBC将各任务和触发器的数据持久化

JDBC-JobStoreTX用来持久化保存任务、触发器、日历等信息。根据事务管理方式的不同有两个持久化类:JDBC-JobStore没有事务管理功能,JDBC-JobStoreTX内置事务管理功能。如果是独立使用quartz,或者quartz所嵌入的程序没有事务管理的话,一般使用JDBC-JobStoreTX。配置方式同前面的RAMJobStore:

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

JDBC-JobStoreTX可以通过以下属性来进行调优设置:

Property Name Required Type Default Value
org.quartz.jobStore.driverDelegateClass yes string null
org.quartz.jobStore.dataSource yes string null
org.quartz.jobStore.tablePrefix no string "QRTZ_"
org.quartz.jobStore.useProperties no boolean false
org.quartz.jobStore.misfireThreshold no int 60000
org.quartz.jobStore.isClustered no boolean false
org.quartz.jobStore.clusterCheckinInterval no long 15000
org.quartz.jobStore.maxMisfiresToHandleAtATime no int 20
org.quartz.jobStore.dontSetAutoCommitFalse no boolean false
org.quartz.jobStore.selectWithLockSQL no string "SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE"
org.quartz.jobStore.txIsolationLevelSerializable no boolean false
org.quartz.jobStore.acquireTriggersWithinLock no boolean false (or true - see doc below)
org.quartz.jobStore.lockHandler.class no string null
org.quartz.jobStore.driverDelegateInitString no string null

org.quartz.jobStore.driverDelegateClass
Driver delegates understand the particular ‘dialects’ of varies database systems. Possible choices include:

  • org.quartz.impl.jdbcjobstore.StdJDBCDelegate (for fully JDBC-compliant drivers)
  • org.quartz.impl.jdbcjobstore.MSSQLDelegate (for Microsoft SQL Server, and Sybase)
  • org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
    org.quartz.impl.jdbcjobstore.WebLogicDelegate (for WebLogic drivers)
  • org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
    org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate (for Oracle drivers used within Weblogic)
  • org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate (for Oracle drivers used within Weblogic)
  • org.quartz.impl.jdbcjobstore.CloudscapeDelegate
  • org.quartz.impl.jdbcjobstore.DB2v6Delegate
  • org.quartz.impl.jdbcjobstore.DB2v7Delegate
  • org.quartz.impl.jdbcjobstore.DB2v8Delegate
  • org.quartz.impl.jdbcjobstore.HSQLDBDelegate
  • org.quartz.impl.jdbcjobstore.PointbaseDelegate
  • org.quartz.impl.jdbcjobstore.SybaseDelegate
    Note that many databases are known to work with the StdJDBCDelegate, while others are known to work with delegates for other databases, for example Derby works well with the Cloudscape delegate (no surprise there).

org.quartz.jobStore.dataSource
The value of this property must be the name of one the DataSources defined in the configuration properties file. See the configuration docs for DataSources for more information.

org.quartz.jobStore.tablePrefix
JDBCJobStore’s “table prefix” property is a string equal to the prefix given to Quartz’s tables that were created in your database. You can have multiple sets of Quartz’s tables within the same database if they use different table prefixes.

org.quartz.jobStore.useProperties
The “use properties” flag instructs JDBCJobStore that all values in JobDataMaps will be Strings, and therefore can be stored as name-value pairs, rather than storing more complex objects in their serialized form in the BLOB column. This is can be handy, as you avoid the class versioning issues that can arise from serializing your non-String classes into a BLOB.

org.quartz.jobStore.misfireThreshold
The the number of milliseconds the scheduler will ‘tolerate’ a trigger to pass its next-fire-time by, before being considered “misfired”. The default value (if you don’t make an entry of this property in your configuration) is 60000 (60 seconds).

org.quartz.jobStore.isClustered
Set to “true” in order to turn on clustering features. This property must be set to “true” if you are having multiple instances of Quartz use the same set of database tables… otherwise you will experience havoc. See the configuration docs for clustering for more information.

org.quartz.jobStore.clusterCheckinInterval
Set the frequency (in milliseconds) at which this instance “checks-in”* with the other instances of the cluster. Affects the quickness of detecting failed instances.

org.quartz.jobStore.maxMisfiresToHandleAtATime
The maximum number of misfired triggers the jobstore will handle in a given pass. Handling many (more than a couple dozen) at once can cause the database tables to be locked long enough that the performance of firing other (not yet misfired) triggers may be hampered.

org.quartz.jobStore.dontSetAutoCommitFalse
Setting this parameter to “true” tells Quartz not to call setAutoCommit(false) on connections obtained from the DataSource(s). This can be helpful in a few situations, such as if you have a driver that complains if it is called when it is already off. This property defaults to false, because most drivers require that setAutoCommit(false) is called.

org.quartz.jobStore.selectWithLockSQL
Must be a SQL string that selects a row in the “LOCKS” table and places a lock on the row. If not set, the default is “SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE”, which works for most databases. The “{0}” is replaced during run-time with the TABLE_PREFIX that you configured above. The “{1}” is replaced with the scheduler’s name.

org.quartz.jobStore.txIsolationLevelSerializable
A value of “true” tells Quartz (when using JobStoreTX or CMT) to call setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) on JDBC connections. This can be helpful to prevent lock timeouts with some databases under high load, and “long-lasting” transactions.

org.quartz.jobStore.acquireTriggersWithinLock
Whether or not the acquisition of next triggers to fire should occur within an explicit database lock. This was once necessary (in previous versions of Quartz) to avoid dead-locks with particular databases, but is no longer considered necessary, hence the default value is “false”.

If “org.quartz.scheduler.batchTriggerAcquisitionMaxCount” is set to > 1, and JDBC JobStore is used, then this property must be set to “true” to avoid data corruption (as of Quartz 2.1.1 “true” is now the default if batchTriggerAcquisitionMaxCount is set > 1).

org.quartz.jobStore.lockHandler.class
The class name to be used to produce an instance of a org.quartz.impl.jdbcjobstore.Semaphore to be used for locking control on the job store data. This is an advanced configuration feature, which should not be used by most users. By default, Quartz will select the most appropriate (pre-bundled) Semaphore implementation to use. “org.quartz.impl.jdbcjobstore.UpdateLockRowSemaphore” QUARTZ-497 may be of interest to MS SQL Server users. See QUARTZ-441.

org.quartz.jobStore.driverDelegateInitString
A pipe-delimited list of properties (and their values) that can be passed to the DriverDelegate during initialization time.
The format of the string is as such:
"settingName=settingValue|otherSettingName=otherSettingValue|..."
The StdJDBCDelegate and all of its descendants (all delegates that ship with Quartz) support a property called ‘triggerPersistenceDelegateClasses’ which can be set to a comma-separated list of classes that implement the TriggerPersistenceDelegate interface for storing custom trigger types. See the Java classes SimplePropertiesTriggerPersistenceDelegateSupport and SimplePropertiesTriggerPersistenceDelegateSupport for examples of writing a persistence delegate for a custom trigger.

3.8 配置JDBC-JobStoreCMT,带有JTA管理的事务的JDBC持久化功能

如果需要quartz所嵌入的程序来管理quartz功能的事务,可以使用JDBC-JobStoreCMT,切其支持所嵌入程序的JTA事务管理,比较强大。
使用JDBC-JobStoreCMT的配置方式依然同上

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

JDBC-JobStoreCMT有如下属性可供配置:

Property Name Required Type Default Value
org.quartz.jobStore.driverDelegateClass yes string null
org.quartz.jobStore.dataSource yes string null
org.quartz.jobStore.nonManagedTXDataSource yes string null
org.quartz.jobStore.tablePrefix no string "QRTZ_"
org.quartz.jobStore.useProperties no boolean false
org.quartz.jobStore.misfireThreshold no int 60000
org.quartz.jobStore.isClustered no boolean false
org.quartz.jobStore.clusterCheckinInterval no long 15000
org.quartz.jobStore.maxMisfiresToHandleAtATime no int 20
org.quartz.jobStore.dontSetAutoCommitFalse no boolean false
org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse no boolean false
org.quartz.jobStore.selectWithLockSQL no string "SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE"
org.quartz.jobStore.txIsolationLevelSerializable no boolean false
org.quartz.jobStore.txIsolationLevelReadCommitted no boolean false
org.quartz.jobStore.acquireTriggersWithinLock no boolean false (or true - see doc below)
org.quartz.jobStore.lockHandler.class no string null
org.quartz.jobStore.driverDelegateInitString no string null

org.quartz.jobStore.driverDelegateClass
Driver delegates understand the particular ‘dialects’ of varies database systems. Possible choices include:

  • org.quartz.impl.jdbcjobstore.StdJDBCDelegate (for fully JDBC-compliant drivers)
  • org.quartz.impl.jdbcjobstore.MSSQLDelegate (for Microsoft SQL Server, and Sybase)
  • org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
  • org.quartz.impl.jdbcjobstore.WebLogicDelegate (for WebLogic drivers)
  • org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
  • org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate (for Oracle drivers used within Weblogic)
  • org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate (for Oracle drivers used within Weblogic)
  • org.quartz.impl.jdbcjobstore.CloudscapeDelegate
  • org.quartz.impl.jdbcjobstore.DB2v6Delegate
  • org.quartz.impl.jdbcjobstore.DB2v7Delegate
  • org.quartz.impl.jdbcjobstore.DB2v8Delegate
  • org.quartz.impl.jdbcjobstore.HSQLDBDelegate
  • org.quartz.impl.jdbcjobstore.PointbaseDelegate
  • org.quartz.impl.jdbcjobstore.SybaseDelegate
    Note that many databases are known to work with the StdJDBCDelegate, while others are known to work with delegates for other databases, for example Derby works well with the Cloudscape delegate (no surprise there).

org.quartz.jobStore.dataSource
The value of this property must be the name of one the DataSources defined in the configuration properties file. For JobStoreCMT, it is required that this DataSource contains connections that are capable of participating in JTA (e.g. container-managed) transactions. This typically means that the DataSource will be configured and maintained within and by the application server, and Quartz will obtain a handle to it via JNDI. See the configuration docs for DataSources for more information.

org.quartz.jobStore.nonManagedTXDataSource
JobStoreCMT requires a (second) datasource that contains connections that will not be part of container-managed transactions. The value of this property must be the name of one the DataSources defined in the configuration properties file. This datasource must contain non-CMT connections, or in other words, connections for which it is legal for Quartz to directly call commit() and rollback() on.

org.quartz.jobStore.tablePrefix
JDBCJobStore’s “table prefix” property is a string equal to the prefix given to Quartz’s tables that were created in your database. You can have multiple sets of Quartz’s tables within the same database if they use different table prefixes.

org.quartz.jobStore.useProperties
The “use properties” flag instructs JDBCJobStore that all values in JobDataMaps will be Strings, and therefore can be stored as name-value pairs, rather than storing more complex objects in their serialized form in the BLOB column. This is can be handy, as you avoid the class versioning issues that can arise from serializing your non-String classes into a BLOB.

org.quartz.jobStore.misfireThreshold
The the number of milliseconds the scheduler will ‘tolerate’ a trigger to pass its next-fire-time by, before being considered “misfired”. The default value (if you don’t make an entry of this property in your configuration) is 60000 (60 seconds).

org.quartz.jobStore.isClustered
Set to “true” in order to turn on clustering features. This property must be set to “true” if you are having multiple instances of Quartz use the same set of database tables… otherwise you will experience havoc. See the configuration docs for clustering for more information.

org.quartz.jobStore.clusterCheckinInterval
Set the frequency (in milliseconds) at which this instance “checks-in”* with the other instances of the cluster. Affects the quickness of detecting failed instances.

org.quartz.jobStore.maxMisfiresToHandleAtATime
The maximum number of misfired triggers the jobstore will handle in a given pass. Handling many (more than a couple dozen) at once can cause the database tables to be locked long enough that the performance of firing other (not yet misfired) triggers may be hampered.

org.quartz.jobStore.dontSetAutoCommitFalse
Setting this parameter to “true” tells Quartz not to call setAutoCommit(false) on connections obtained from the DataSource(s). This can be helpful in a few situations, such as if you have a driver that complains if it is called when it is already off. This property defaults to false, because most drivers require that setAutoCommit(false) is called.

org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse
The same as the property org.quartz.jobStore.dontSetAutoCommitFalse, except that it applies to the nonManagedTXDataSource.

org.quartz.jobStore.selectWithLockSQL
Must be a SQL string that selects a row in the “LOCKS” table and places a lock on the row. If not set, the default is “SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND LOCK_NAME = ? FOR UPDATE”, which works for most databases. The “{0}” is replaced during run-time with the TABLE_PREFIX that you configured above. The “{1}” is replaced with the scheduler’s name.

org.quartz.jobStore.txIsolationLevelSerializable
A value of “true” tells Quartz to call setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) on JDBC connections. This can be helpful to prevent lock timeouts with some databases under high load, and “long-lasting” transactions.

org.quartz.jobStore.txIsolationLevelReadCommitted
When set to “true”, this property tells Quartz to call setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED) on the non-managed JDBC connections. This can be helpful to prevent lock timeouts with some databases (such as DB2) under high load, and “long-lasting” transactions.

org.quartz.jobStore.acquireTriggersWithinLock
Whether or not the acquisition of next triggers to fire should occur within an explicit database lock. This was once necessary (in previous versions of Quartz) to avoid dead-locks with particular databases, but is no longer considered necessary, hence the default value is “false”.

If “org.quartz.scheduler.batchTriggerAcquisitionMaxCount” is set to > 1, and JDBC JobStore is used, then this property must be set to “true” to avoid data corruption (as of Quartz 2.1.1 “true” is now the default if batchTriggerAcquisitionMaxCount is set > 1).

org.quartz.jobStore.lockHandler.class
The class name to be used to produce an instance of a org.quartz.impl.jdbcjobstore.Semaphore to be used for locking control on the job store data. This is an advanced configuration feature, which should not be used by most users. By default, Quartz will select the most appropriate (pre-bundled) Semaphore implementation to use. “org.quartz.impl.jdbcjobstore.UpdateLockRowSemaphore” QUARTZ-497 may be of interest to MS SQL Server users. “JTANonClusteredSemaphore” which is bundled with Quartz may give improved performance when using JobStoreCMT, though it is an experimental implementation. See QUARTZ-441 and QUARTZ-442

org.quartz.jobStore.driverDelegateInitString
A pipe-delimited list of properties (and their values) that can be passed to the DriverDelegate during initialization time.

The format of the string is as such:
"settingName=settingValue|otherSettingName=otherSettingValue|..."
The StdJDBCDelegate and all of its descendants (all delegates that ship with Quartz) support a property called ‘triggerPersistenceDelegateClasses’ which can be set to a comma-separated list of classes that implement the TriggerPersistenceDelegate interface for storing custom trigger types. See the Java classes SimplePropertiesTriggerPersistenceDelegateSupport and SimplePropertiesTriggerPersistenceDelegateSupport for examples of writing a persistence delegate for a custom trigger.

3.9 配置DataSources,以使用以上两项JDBC-JobStores持久化功能

DataSource用来为JDBC-JobStores,JDBC-JobStoresTX,JDBC-JobStoresCMT提供持久化的数据源,如果使用JDBC-JobStoresCMT来实现持久化,并且采用多数据库集群的方式,可以配置多个DataSources供JDBC-JobStoresCMT来调用,JDBC-JobSoresCMT会接受程序的JTA跨库事务管理。
为quartz配置DataSource有三种方式:
1.直接在配置文件中配置JDBC连接信息,quartz在初始化的时候会自动创建连接。
2.可以调用JNDI管理的数据库连接。
3.自定义数据库连接器对象来提供数据库连接org.quartz.utils.ConnectionProvider。
数据源中的最大连接数应该至少为“任务调度器线程数+3”,如果数据源不是quartz专用的话,还要再加上其他功能所需要的连接数。因此数据库最大连接数应该最少为4.
由于可以在quartz配置文件中配置多个DataSource,所以需要为每个数据源指定名称,然后将指定的名称配置给JDBC-JobStore或者JDBC-JobStoreTX,JDBC-JobStoreCMT。
1.采用JDBC连接信息来配置DataSource

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@10.0.1.23:1521:demodb
org.quartz.dataSource.myDS.user = myUser
org.quartz.dataSource.myDS.password = myPassword
org.quartz.dataSource.myDS.maxConnections = 30
Property Name Required Type Default Value
org.quartz.dataSource.NAME.driver yes String null
org.quartz.dataSource.NAME.URL yes String null
org.quartz.dataSource.NAME.user no String ""
org.quartz.dataSource.NAME.password no String ""
org.quartz.dataSource.NAME.maxConnections no int 10
org.quartz.dataSource.NAME.validationQuery no String null
org.quartz.dataSource.NAME.idleConnectionValidationSeconds no int 50
org.quartz.dataSource.NAME.validateOnCheckout no boolean false
org.quartz.dataSource.NAME.discardIdleConnectionsSeconds no int 0 (disabled)

org.quartz.dataSource.NAME.driver
Must be the java class name of the JDBC driver for your database.

org.quartz.dataSource.NAME.URL
The connection URL (host, port, etc.) for connection to your database.

org.quartz.dataSource.NAME.user
The user name to use when connecting to your database.

org.quartz.dataSource.NAME.password
The password to use when connecting to your database.

org.quartz.dataSource.NAME.maxConnections
The maximum number of connections that the DataSource can create in it’s pool of connections.

org.quartz.dataSource.NAME.validationQuery
Is an optional SQL query string that the DataSource can use to detect and replace failed/corrupt connections.
For example an oracle user might choose “select table_name from user_tables” - which is a query that should never fail - unless the connection is actually bad.

org.quartz.dataSource.NAME.idleConnectionValidationSeconds
The number of seconds between tests of idle connections - only enabled if the validation query property is set.
Default is 50 seconds.

org.quartz.dataSource.NAME.validateOnCheckout
Whether the database sql query to validate connections should be executed every time a connection is retrieved from the pool to ensure that it is still valid. If false, then validation will occur on check-in. Default is false.

org.quartz.dataSource.NAME.discardIdleConnectionsSeconds
Discard connections after they have been idle this many seconds. 0 disables the feature. Default is 0.

  1. 使用应用程序通过JNDI管理的DataSource

     org.quartz.dataSource.myOtherDS.jndiURL=jdbc/myDataSource 
     org.quartz.dataSource.myOtherDS.java.naming.factory.initial=com.evermind.server.rmi.RMIInitialContextFactory
    

    org.quartz.dataSource.myOtherDS.java.naming.provider.url=ormi://localhost
    org.quartz.dataSource.myOtherDS.java.naming.security.principal=admin
    org.quartz.dataSource.myOtherDS.java.naming.security.credentials=123

Property Name Required Type Default Value
org.quartz.dataSource.NAME.jndiURL yes String null
org.quartz.dataSource.NAME.java.naming.factory.initial no String null
org.quartz.dataSource.NAME.java.naming.provider.url no String null
org.quartz.dataSource.NAME.java.naming.security.principal no String null
org.quartz.dataSource.NAME.java.naming.security.credentials no String null

org.quartz.dataSource.NAME.jndiURL
The JNDI URL for a DataSource that is managed by your application server.

org.quartz.dataSource.NAME.java.naming.factory.initial
The (optional) class name of the JNDI InitialContextFactory that you wish to use.

org.quartz.dataSource.NAME.java.naming.provider.url
The (optional) URL for connecting to the JNDI context.

org.quartz.dataSource.NAME.java.naming.security.principal
The (optional) user principal for connecting to the JNDI context.

org.quartz.dataSource.NAME.java.naming.security.credentials
The (optional) user credentials for connecting to the JNDI context.

  1. 通过自定义数据库连接器连接的配置方式

    org.quartz.dataSource.myCustomDS.connectionProvider.class = com.foo.FooConnectionProvider
    org.quartz.dataSource.myCustomDS.someStringProperty = someValue
    org.quartz.dataSource.myCustomDS.someIntProperty = 5

Property Name Required Type Default Value
org.quartz.dataSource.NAME.connectionProvider.class yes String (class name) null

org.quartz.dataSource.NAME.connectionProvider.class
The class name of the ConnectionProvider to use. After instantiating the class, Quartz can automatically set configuration properties on the instance, bean-style.

3.10 配置Database Clustering,使JDBC-JobStore获得失效救援和负载平衡能力

将quartz配置成集群,即可利用其容错和负载均衡功能实现高可用性和扩展性的功能。
quartz_cluster.png
为多个quartz配置JDBC-Jobstore(JobStoreTX 或者 JobStoreCMT),并且连接到同一个数据库,这些quartz就可以组成一个集群。
集群中的负载均衡功能会自动工作,集群中的节点都会尽可能快的执行触发的任务。当一个触发时机到了后,第一个获得到任务的节点(通过为任务上锁获得任务)将会执行这个任务。
为保证程序执行的正确性,每一个任务的每一次触发,都只会被一个节点触发。但是同一个任务的不同触发,不保证在同一个节点执行,每次触发时机节点的选择是随机的。在任务繁忙的任务调度系统中,负载均衡任务执行节点的选择机制是接近于随机的,在任务不繁忙的任务调度系统中,任务的执行可能都会在某一个节点上面。
当某一个任务执行到一半的时候,集群的容错功能就会工作。当一个节点宕机,其他的节点会检测到数据库中标记为正在被宕机节点运行的任务,将有“recovery”(数据库的JobDetail有"requests recovery"属性)标记的任务继续执行;宕机节点中没有标记“recovery”的任务,不会做任何特殊处理,在下一次触发到来时,正常执行。
集群系统通过将工作符合分散到多个节点执行,可以更好的应对将运行耗时交长和cpu占用较高的任务场景。但是对于大量(例如几千)的短时间(例如1秒)任务,应该考虑将这些段短任务分组,然后放到多个独立的任务调度系统(或者多个任务调度系统集群)上面运行。任务调度集群使用了集群锁,当集群节点超过三个时,性能会随着节点数量的增加而降低,同时集群锁的性能也依赖于数据库的性能。
集群中的节点的配置文件中需要包含:“org.quartz.jobStore.isClustered=true”来激活集群。配置文件中只有线程池数量和“org.quartz.scheduler.instanceId”可以不同,其他应该完全一致。每一个节点的instanceId都应该是唯一的。为了简化配置,当配置文件中其他内容完全一致时,可以将“org.quartz.scheduler.instanceId”设置为“AUTO”,这样所有的节点就可以使用完全一样的配置文件。

官方文档中,说明不能在不同机器上运行集群
多个未配置成集群的quartz,禁止使用同一个数据库。

以下是一个典型的quartz集群配置文件

# Configure Main Scheduler Properties  
org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO
# Configure ThreadPool  
org.quartz.threadPool.class =org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5
# Configure JobStore  
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
# Configure Datasources  
org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@polarbear:1521:dev
org.quartz.dataSource.myDS.user = quartz
org.quartz.dataSource.myDS.password = quartz
org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery=select 0 from dual
六、最佳实践技巧

生产系统技巧

跳过升级检查.quartz默认会链接其服务器检查是否有更新版本的程序可用,如果有的话会在日志文件中记录相应内容。但是在生产环境下,此功能显得不太合适。可以通过以下配置方式来禁用升级检查功能。

1.配置文件中禁用系统升级
quartz.properties
org.quartz.schedulers.kipUpdateCheck=true
2.通过系统环境变量禁用系统升级
org.terracotta.quartz.skipUpdateCheck=true

将其通过-D指令加入java命令行参数也可以起到同样效果。

JobDataMap相关技巧

只使用java的基本数据类型,包括String。可以避免许多问题。
JobDataMap章节,介绍了几种在Job实现类中execute方法获外部参数的方法,包括获取JobDetail对象中的JobDataMap;获取Trigger对象中的JobDataMap;获取JobExecutionContext中的JobDataMap。推荐使用第三种方式,因为这种方法获得的JobDataMap是前两种方法获取的对象的结合,包含了JobDetail和Trigger中的所有参数(同名参数覆盖)。

Trigger技巧

使用DateBuilder,TriggerBuilder和TriggerUtils
  • DateBuilder可以用来创建日期对象,用来设置触发器的开始和结束时间。
  • TriggerBuilder用来创建触发器,其提供了一系列的setter方法来创建触发器。
  • TriggerUtils用来分析触发器对象,例如计算还剩下的触发次数。

JDBC JobStore使用技巧

禁止直接操作quartz的数据表

quartz的数据表都是由任务调度器实例使用的,如果要修改工作数据,可以通过任务调度器实例的API来进行修改。如果直接操作数据表,会引起严重的系统运行异常。

不属于同一个集群的实例,禁止使用同一组quartz数据表
配置足够的数据库连接数。

数据库连接数应该至少为线程池线程数+3。更多的话有利于系统流畅运行,少的话可能会引起资源竞争导致降低系统性能.

在使用夏时令区域,避免在夏时令切换的保留小时内安排任务。

Job使用技巧

等待条件

如果一个任务需要长时间运行,并且运行过程中需要其他的条件就绪之后才能继续运行,需要在程序中插入Thread。sleep()方法来暂停任务执行等待其他条件就绪,这种情况应该意识到任务当前占用的线程资源被浪费了。对于这种任务,应该尝试将其切分为多个任务,分开调度,减少线程占用。

抛出异常

任务实现类在实现Job接口的execute方法时,内部的代码最好使用try-catch进行异常检查。
如果一个任务在运行过程中跑出了异常,全让她惴立即重新运行这个任务。通过任务的检查,可以自定义跑出异常时后续的执行逻辑,例如重新执行,或者执行其他任务,或者解决异常问题。

任务恢复和任务执行的幂等性

如果一个正在运行的且被标记为"recoverable"的任务,在运行过程中任务调度器出现问题,导致任务运行失败,那么这个任务会被恢复重新运行,这意味着这个任务的全部或者部分操作会执行两遍。所以任务的定义需要满足幂等性,以避免重复操作引起的逻辑或者数据混乱。

监听器(触发器监听器,任务监听器,任务调度器监听器)使用技巧

监听器中的代码应该简洁高效

不建议在监听器中国定义太大的工作量,过多的处理逻辑会显著增加资源占用。

异常处理

监听器中的代码最好使用try-catch来检查异常。避免直接跑出异常导致事件监听异常导致任务执行异常。

将quartz的任务调度器对象通过接口暴露给外部

一定要注意安全性

将任务调度器的实例接口通过系统接口暴露给外部,的确是可以很方便的实现功能。但是可能会引起严重的安全问题。
quartz中内置了一些job,操作权限比较敏感,例如org.quartz.jobs.NatiiveJob可以执行服务器上面的系统命令,因此可以对操作系统进行修改,如果将quartz暴露给外部用户,将会导致严重的安全隐患。SendEmailJob等也可能会引起敏感和严重的后果。

解决MAVEN项目中,shiro与quartz的冲突

`
org.apache.shiro
shiro-quartz
${shiro.version}

org.opensymphony.quartz quartz org.quartz-scheduler quartz `
七、例程