Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SE和J2EE应用中。

参考博客网址

一、Quartz基本配置

Quartz三大核心元素:Scheduler(调度器)、Trigger(触发器)、job(任务)。

通过Scheduler启动Trigger来执行job。

pom依赖:

    <!--quartz相关依赖-->
    <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>
    >>>>SpringBoot替换为:>>>>
    <!--quartz依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>

在Quartz JAR文件的org.quartz包下就包含一个quartz.properties属性配置文件并提供了默认属性。如果需要调整默认配置,可以在类路径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。

配置信息如下:

#可以为任意字符串,对于scheduler来说此值没有意义,但是可以区分同一系统中多个不同的实例,
#如果使用了集群的功能,就必须对每一个实例使用相同的名称,这样使这些实例“逻辑上”是同一个scheduler。
org.quartz.scheduler.instanceName = JobScheduler
#可以是任意字符串,但如果是集群,scheduler实例的值必须唯一,可以使用AUTO自动生成。
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false

# 默认false,若是在执行Job之前Quartz开启UserTransaction,此属性应该为true。
#Job执行完毕,JobDataMap更新完(如果是StatefulJob)事务就会提交。默认值是false,可以在job类上使用@ExecuteInJTATransaction 注解,以便在各自的job上决定是否开启JTA事务。
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
#一个scheduler节点允许接收的trigger的最大数,默认是1,这个值越大,定时任务执行的越多,但代价是集群节点之间的不均衡。
org.quartz.scheduler.batchTriggerAcquisitionMaxCount=1

#线程池的实例类,(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class= org.quartz.simpl.SimpleThreadPool
#线程数量,不会动态增加
org.quartz.threadPool.threadCount= 10
#线程优先级
org.quartz.threadPool.threadPriority= 5
#加载任务代码的ClassLoader是否从外部继承
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread= true
#是否设置调度器线程为守护线程
org.quartz.scheduler.makeSchedulerThreadDaemon: true

#将schedule相关信息保存在RAM中,轻量级,速度快,遗憾的是应用重启时相关信息都将丢失。
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#最大能忍受的触发超时时间,如果超时则认为“失误”
#org.quartz.jobStore.misfireThreshold = 60000

#选择JDBC的存储方式 (JobStoreTX为application自己管理事务|JobStoreCMT为application server管理事务,即全局JTA)
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
#类似于Hibernate的dialect,用于处理DB之间的差异,StdJDBCDelegate能满足大部分的DB(授权)
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#存储相关信息表的前缀
org.quartz.jobStore.tablePrefix = QRTZ_
#这个值必须datasource的配置信息
org.quartz.jobStore.dataSource = myDS
#JobDataMaps是否都为String类型
#(若是true的话,便可不用让更复杂的对象以序列化的形式保存到BLOB列中。以防序列化可能导致的版本号问题)
org.quartz.jobStore.useProperties = false
#最大能忍受的触发超时时间,如果超时则认为“失误”
org.quartz.jobStore.misfireThreshold = 60000
#是否是应用在集群中,当应用在集群中时必须设置为TRUE,否则会出错。
#如果有多个Quartz实例在用同一套数据库时,必须设置为true。
org.quartz.jobStore.isClustered=False
#只用于设置了isClustered为true的时候,设置一个频度(毫秒),用于实例报告给集群中的其他实例。
#这会影响到侦测失败实例的敏捷度。
org.quartz.jobStore.clusterCheckinInterval =15000
#这是JobStore能处理的错过触发的Trigger的最大数量。处理太多(2打)很快就会导致数据库表被锁定够长的时间,
#这样会妨碍别的(还未错过触发)trigger执行的性能。
org.quartz.jobStore.maxMisfiresToHandleAtATime=20
#设置这个参数为true会告诉Quartz从数据源获取连接后不要调用它的setAutoCommit(false)方法。
#在少数情况下是有用的,比如有一个驱动本来是关闭的,但是又调用这个关闭的方法。但是大部分情况下驱动都要求调用setAutoCommit(false)
org.quartz.jobStore.dontSetAutoCommitFalse=false
#这必须是一个从LOCKS表查询一行并对这行记录加锁的SQL。假设没有设置,默认值如下。
#{0}会在运行期间被前面配置的TABLE_PREFIX所代替
org.quartz.jobStore.selectWithLockSQL=SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE
#值为true时告知Quartz(当使用JobStoreTX或CMT)调用JDBC连接的setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法。这有助于某些数据库在高负载和长时间事务时锁的超时。
org.quartz.jobStore.txIsolationLevelSerializable=false

#org.quartz.dataSource.NAME.driver
#org.quartz.dataSource.NAME.URL
#org.quartz.dataSource.NAME.user
#org.quartz.dataSource.NAME.password
#org.quartz.dataSource.NAME.maxConnections
#需要注意的是,#NAME字段必须与$@org.quartz.jobStore.dataSource保持一致。
#一般我们的数据库连接池一般使用第三方连接池,那么就会导致org.quartz.jobStore.dataSource=#NAME无法配置!!可以使用quartz.job-store-type=jdbc替代。

SpringBoot2.x整合Quartz的配置文件如下:

spring:
  datasource:
    name: mysql_test
    type: com.alibaba.druid.pool.DruidDataSource
    #druid相关配置
    druid:
      #监控统计拦截的filters
      filter: stat,config
#      driver-class-name: com.mysql.cj.jdbc.Driver
      #基本属性
      url: jdbc:mysql://localhost:3306/mydb
      username: root
      password: 123456
      #初始化连接数
      initial-size: 10
      #最小活跃连接数
      min-size: 5
      #最大活跃连接数
      max-active: 20
      #获取连接的等待时间
      max-wait: 60000
      #间隔多久进行一次检查,检查需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      #一个连接在池中最小的生存时间(5分钟)
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'X'
      # 验证空闲的连接,若无法验证,则删除连接
      test-while-idle: true
      # 不检测池中连接的可用性(默认false)
      # 导致的问题是,若项目作为服务端,数据库连接被关闭时,客户端调用就会出现大量的timeout
      test-on-borrow: false
      #在返回连接池之前是否验证对象
      test-on-return: false
      #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
      #第三发连接池在使用的时候,获取到Connection后,使用完毕,调用关闭方法,并没有将Connection关闭,只是放回到连接池中
      #如果调用这个方法,而没有手动关闭PreparedStatement,就可能造成内存溢出,但是JDK1.7实现了AutoCloseable接口,就不需要关闭了
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20
      # connection-properties:
      use-unfair-lock: true
  quartz:
    #相关属性配置
    properties:
      org:
        quartz:
          scheduler:
            # 集群名,区分同一系统的不同实例,若使用集群功能,则每一个实例都要使用相同的名字
            instanceName: clusteredScheduler
            # 若是集群下,每个instanceId必须唯一
            instanceId: AUTO
          threadPool:
            #一般使用这个便可
            class: org.quartz.simpl.SimpleThreadPool
            #线程数量,不会动态增加
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
          jobStore:
            #选择JDBC的存储方式
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_
            useProperties: false
            isClustered: true
            clusterCheckinInterval: 15000
    job-store-type: jdbc
    #Quartz默认false,是否等待任务运行完毕后关闭Spring容器,若是为false的情况下,可能出现java.lang.IllegalStateException: JobStore is shutdown - aborting retry异常,推荐开启。
    wait-for-jobs-to-complete-on-shutdown: false
    #是否会覆盖数据库正在运行的job。quartz启动之后,会以数据库的为准,若该属性为false,则配置文件修改后不会起作用。
    overwrite-existing-jobs: false

二、jobstore数据库表字段详解

# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
#
# By: Ron Cordell - roncordell
#  I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
JOB_NAME VARCHAR(190) NOT NULL,
JOB_GROUP VARCHAR(190) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(190) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(190) NOT NULL,
    TRIGGER_GROUP VARCHAR(190) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(190) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;

CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(190) NOT NULL,
TRIGGER_GROUP VARCHAR(190) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(190) NULL,
JOB_GROUP VARCHAR(190) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID))
ENGINE=InnoDB;

CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(190) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))
ENGINE=InnoDB;

CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;

上面总共11张表,是Quartz官方提供,默认存储scheduler表

1.qrtz_job_details:存储每一个已配置的job的详细信息

2.qrtz_triggers:存放配置的Trigger信息

3.qrtz_cron_triggers:存放cron类型的触发器

4.qrtz_scheduler_state:存储所有节点的scheduler,会定期检查scheduler是否失效

5.qrtz_fired_triggers:存储已经触发的trigger相关信息,trigger随着时间的推移状态发生变化,直到最后trigger执行完成,从表中被删除

6.qrtz_simple_triggers:存储简单的trigger,包括重复次数,间隔,以及触发次数

7.qrtz_simprop_triggers:存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器

8.qrtz_blob_triggers:将自定义的triggers使用blog类型进行存储,非自定义的triggers不会存放在此表中,Quartz提供的triggers包括:CronTrigger,CalendarIntervalTrigger,DailyTimeIntervalTrigger以及SimpleTrigger

9.qrtz_calendars:以Blob类型存储Quartz的Calendar信息

10.存储已暂停的Trigger组的信息

11.存储程序的悲观锁的信息

三、任务的并行/串行执行

Spring提供的两种配置JobDetail的方式,官方示例如下:

<!-- JobDetail配置 1 -->
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="example.ExampleJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="5"/>
        </map>
    </property>
</bean>
<!-- JobDetail配置 2 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
    <property name="concurrent" value="false"/>
</bean>
 /**配置 1*/
 JobDetailFactoryBean jobFactory = new JobDetailFactoryBean();
 jobFactory.setName(jobName);
 jobFactory.setBeanName(clazzName);  //包名+类名
 jobFactory.setJobClass((Class<? extends Job>) aClass);
 jobFactory.setGroup(jobGroup);
 jobFactory.setDurability(true);
 jobFactory.afterPropertiesSet();
 /**配置 2*/
 MethodInvokingJobDetailFactoryBean invokingJobDetailFactoryBean=new  MethodInvokingJobDetailFactoryBean();
 invokingJobDetailFactoryBean.setConcurrent(false);
 invokingJobDetailFactoryBean.setName(jobName);
 invokingJobDetailFactoryBean.setGroup(jobGroup);
 invokingJobDetailFactoryBean.setTargetClass(aClass);
 invokingJobDetailFactoryBean.setTargetMethod("xxx");

1.JobDetailFactoryBean方式配置

首先,需要配置jobClass属性,jobClass需要传入一个job实现类。Spring推荐直接继承QuartzJobBean类,官方示例如下:

package example;

public class ExampleJob extends QuartzJobBean {

    private int timeout;

    /**
     * Setter called after the ExampleJob is instantiated
     * with the value from the JobDetailFactoryBean (5)
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException{
        // do the actual work
    }

}

在executeInternal()中进行实现,其次是配置jobDataAsMap的属性

<entry key="timeout" value="5"/>

只要继承QuartzJobBean,就可以通过这种方式直接向Job属性中传递值。

2.配置Trigger和Scheduler

Quartz中常用的Trigger只有两种:

<!-- trigger 1 -->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!-- see the example of method invoking job above -->
    <property name="jobDetail" ref="jobDetail"/>
    <!-- 10 seconds -->
    <property name="startDelay" value="10000"/>
    <!-- repeat every 50 seconds -->
    <property name="repeatInterval" value="50000"/>
</bean>

<!-- trigger 2 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="exampleJob"/>
    <!-- run every morning at 6 AM -->
    <property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
 CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
 triggerFactoryBean.setName("corn_" + clazzName);
 //jobDetails
 triggerFactoryBean.setJobDetail(jobFactory.getObject());
 triggerFactoryBean.setCronExpression(quartzCorn);
 triggerFactoryBean.setGroup(QUARTZ_TRIGGER_GROUP);
 triggerFactoryBean.afterPropertiesSet();
  • simpleTrigger触发器每隔一段时间执行一次任务,jobDetail就是前面生成的jobDetail;startDelay表示系统启动起来后过多长时间开始执行任务,单位:毫秒;repeatInterval表示任务执行时间间隔,单位毫秒
  • cornTrigger是cron表达式来执行的定时任务。

分别记录在qrtz_cron_triggers | qrtz_simple_triggers & qrtz_triggers表中

3.Quartz串行设置

MethodInvokingJobDetailFactoryBean中,含有一个concurrent属性,可以用来控制任务是否并行执行。但在JobDetailFactoryBean中并没有这个属性。并且由于MethodInvokingJobDetailFactoryBean功能差,在实际开发中使用的并不多。所以,如何防止JobDetailFactoryBean并行呢?代码如下:

package example;

//只需要在Job类上加上这个注解即可
@DisallowConcurrentExecution
public class ExampleJob extends QuartzJobBean {

    private int timeout;

    /**
     * Setter called after the ExampleJob is instantiated
     * with the value from the JobDetailFactoryBean (5)
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException{
        // do the actual work
    }

}

四、misfire处理机制

在Quartz中,当一个持久化的触发器因为调度器被关闭、线程池没有可用线程、项目重启、任务的串行执行等错过激活时间,就会发生激活失败(misfire)。

1.如何判定激活失败

在配置文件中有一个misfireThreshold属性,用来指定调度引擎设置触发器超时的“临界值”。也就是说Quartz对于任务的超时是有容忍度的。只有超过这个容忍度才会判定位misfire。

#设置容忍度为12s
org.quartz.jobStore.misfireThreshold = 12000

示例:

Cron=[0/2 * * * * ?]即每两秒循环一次

jobDetail每次执行需要7s

@Component
@DisallowConcurrentExecution
public class TestJob2 extends  CustomQuartzJobBean{
    private Logger logger = LoggerFactory.getLogger(TestJob2.class);

    @Override
    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException{
        logger.info("【数据库配置定时】-【开始】");
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("【数据库配置定时】-【结束】");
    }
}
任务编号 预定运行时刻 实际运行时刻 延迟量(秒)
1 17:54:00 17:54:00 0
2 17:54:02 17:54:07 5
3 17:54:04 17:54:14 10
4 17:54:06 17:54:21 misfire

从上面案例可以看出,每次延迟为5秒,当延迟累加超过设定的容忍度12s时,将不会执行。

2.激活失败如何处理

激活失败指令是触发器的一个重要配置,所有类型的触发器都有一个默认配置的指令:

Trigger.MISFIRE_INSTRUCTION_SMART_POLICY

但该默认策略对于不同类型的触发器其具体行为是不同的。源码常量有以下几个:

// 所有的misfile任务马上执行
public static final int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
// 在Trigger中默认选择MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 策略
public static final int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
// CornTrigger默认策略,合并部分misfire,正常执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
// 所有的misFire都不管,执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;

1) quartz中CronTrigger使用的策略

通过setMisfireInstruction方法设置misfire策略或者通过CronScheduleBuilder设置misfire策略

 CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
 triggerFactoryBean.setName("corn_" + clazzName);
 triggerFactoryBean.setJobDetail(jobFactory.getObject());
 triggerFactoryBean.setCronExpression(quartzCorn);
 triggerFactoryBean.setGroup(QUARTZ_TRIGGER_GROUP);
 //设置misfire策略
 triggerFactoryBean.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY);
 triggerFactoryBean.afterPropertiesSet();
 CronScheduleBuilder csb = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
 //MISFIRE_INSTRUCTION_DO_NOTHING
 csb.withMisfireHandlingInstructionDoNothing();
 //MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
 csb.withMisfireHandlingInstructionFireAndProceed();//(默认)
 //MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
 csb.withMisfireHandlingInstructionIgnoreMisfires();

举例说明:

设置背景:在每个星期周一下午5点到7点,每隔一个小时执行一次,理论上共执行3次

1.情景一:

//所有misfire的任务会马上执行
public static final int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;

若是5点misfire,6:15系统恢复之后,5,6点的misfire会马上执行。

2.情景二:

//不做任何事情
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;

若是5点misfire,6:15系统恢复之后,只会执行7点的misfire。

3.情景三:

// CornTrigger默认策略,合并部分misfire,正常执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;

若是5点misfire,6:15系统恢复之后,立即执行一次(只会一次)misfire。

2) quartz中SimpleTrigger使用的策略

若是使用默认策略MISFIRE_INSTRUCTION_SMART_POLICY

  • 如果Repeat=0;【重复0次】

    instruction selected = MISFIRE_INSTRUCTION_FIRE_NOW;【立刻执行,对于不会重复执行的任务,只是默认的处理策略。】

  • 如果Repeat Count=REPEAT_INDEFINITELY;【无限重复】

    instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;【在下一个激活点执行,且错过的执行机会作废。】

  • 如果Repeat Count>0;【有限重复】

  • instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;【立即执行,并执行指定的次数。】

3) misfire执行流程

  1. 若配置(默认为true,可配置)成获取锁前先检查是否有需要recovery的trigger,先获取misfireCount;
  2. 获取TRIGGER_ACCESS锁;
  3. misfired的判断依据:status=waiting,current_time-next_fire_time>misfireThreshold(可配置,默认1分钟)【即实际触发时间-预计触发时间大于容忍度时间】,获取misfired的trigger,默认一个事务中只能最大有20个misfired trigger(可配置)。
  4. updateAfterMisfired:获取misfired的策略(默认是MISFIRE_INSTRUCTION_SMART_POLICY该策略在CronTrigger中为MISFIRE_INSTRUCTION_FIRE_ONCE_NOW),根据策略更新nexFireTime。
  5. 将nextFireTime等更新到trigger表;
  6. commit connection,释放锁。

img

五、Trigger状态转换

Trigger对应数据库表为qrtz_triggers和qrtz_fired_triggers

1.正常获取触发任务执行流程

一个触发器只能绑定一个job,但是一个job可以有多个触发器。

触发器线程执行开始时,先从数据库表中获取状态为waiting和要要触发的trigger,然后waiting(等待)状态将更新位acquired(获得),表示该触发器线程抢占到了。然后插入触发器信息以及实例名到FRIED_TRIGGERS表中,状态为ACQUIRED。前面的更新和后面的插入是在同一个事务中进行的。

然后等待触发时间的到来。

执行时间到达后,每触发一次任务,都会在数据库对应表中创建一条记录,并且状态变为executing(执行)。如果任务运行并发执行,此时表状态更新为waiting、paused(暂停)、complete(不需要执行)。

如果任务不允许并发执行,还会把Triggers表里的状态更新为BLOCK(阻塞)或PAUSED_BLOCK。

注意:Triggers表更新时根据任务名和任务所属组名而不是根据触发器名称和触发器组名来更新的。这就解决了一个任务有多个触发器的并发问题;然后触发器线程会创建一个执行环境来执行任务,以便在任务执行完后更新触发器的状态。任务执行完成后,在一个事务中触发器状态更新为waiting,删除qrtz_fired_triggers表里对应的记录。

img

2.如何避免多个节点执行同一个任务

在qrtz_trigger表中有NEXT_FIRE_TIME字段(下一次触发时间)。每个任务在即将执行的时候,获取qrtz_locks表中的行级锁,开启一个事务(更新qrtz_trigger表状态为acquired,并且插入qrtz_fire_trigger一条数据,起始状态为acquired)。

六、Quartz集群

配置文件如下:

#集群中应用采用相同的Scheduler实例
org.quartz.scheduler.instanceName: wenqyScheduler

#集群节点的ID必须唯一,可由quartz自动生成
org.quartz.scheduler.instanceId: AUTO

#通知Scheduler实例要它参与到一个集群当中
org.quartz.jobStore.isClustered: true

#需持久化存储
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

#数据源
org.quartz.jobStore.dataSource=myDS

#quartz表前缀
org.quartz.jobStore.tablePrefix=QRTZ_

#数据源配置
org.quartz.dataSource.myDS.driver: com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL: jdbc:mysql://localhost:3306/ncdb
org.quartz.dataSource.myDS.user: root
org.quartz.dataSource.myDS.password: 123456
org.quartz.dataSource.myDS.maxConnections: 5
org.quartz.dataSource.myDS.validationQuery: select 0

同一集群下,instanceName必须相同,instanceId可自动生成,isClustered为true,持久化存储,指定数据库类型对应的驱动类和数据库连接。

七、Quartz异步通知

一个Trigger只能绑定一个job,但是一个job可以绑定多个Trigger

若我们将一个job上绑定多个触发器,且每个触发器只是触发一次的话,那么,实际上我们便可以实现阶梯式的异步通知。

public class TestQuartzForMoreJob {

    Logger logger=LoggerFactory.getLogger(TestQuartzForMoreJob.class);

    @Test
    public void test() {
        SimpleScheduleBuilder scheduleBuilder1=SimpleScheduleBuilder.repeatSecondlyForTotalCount(1);
        //任务
        JobDetail jobDetail = JobBuilder.newJob(HelloQuartz.class)
                .withIdentity("job1", "job_group1")
                .storeDurably()
                .build();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Date date = new Date();
        Date date1 = addSecond(date, 5);
        Date date2 = addSecond(date, 15);
        //日志打印
        logger.info("获取到任务的时间:"+sdf.format(date));
        logger.info("第一次通知的时间:"+sdf.format(date1));
        logger.info("第二次通知的时间:"+sdf.format(date2));
        //触发器1
        SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1")
                .withSchedule(scheduleBuilder1)
                .startAt(date1)
                .forJob(new JobKey("job1", "job_group1"))
                .build();
        //触发器2
        SimpleTrigger trigger2 = newTrigger().withIdentity("trigger2", "group1")
                .withSchedule(scheduleBuilder1)
                .forJob(new JobKey("job1", "job_group1"))
                .startAt(date2)
                .build();
        try {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            //多触发器关联
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.scheduleJob(trigger2);
            scheduler.start();
            Thread.sleep(100000);
            scheduler.shutdown();
        } catch (SchedulerException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static Date addSecond(Date date, int second) {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.setTime(date);
//        calendar.add(Calendar.MINUTE, minute);
        calendar.add(Calendar.SECOND, second);
        return calendar.getTime();
    }
}

将器持久化至轮询数据库,数据库表结构如下:

CREATE TABLE `yy_notify` (
  `TXN_ID` varchar(32) NOT NULL COMMENT '系统单号',
  `SERVICE_ID` varchar(255) NOT NULL COMMENT 'Spring Bean id',
  `SCAN_STATUS` varchar(2) DEFAULT NULL COMMENT '扫描状态(00:待扫描,01:不扫描)',
  `SCAN_TIMES` int(1) DEFAULT '0' COMMENT '扫描次数',
  `NEXT_SCAN_TIME` timestamp NULL DEFAULT NULL COMMENT '下次扫描时间,用于梯度查询场景',
  `NOTIFY_STATUS` varchar(255) DEFAULT NULL COMMENT '(02-通知成功,03-通知返回失败,01-未通知)',
  `CREATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `MODIFY_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`TXN_ID`) USING BTREE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

八、动态操作定时任务

方法一:JobDetailFactoryBean或者CronTriggerFactoryBean构建

    /**第一种写法*/
    @Bean
    public JobDetailFactoryBean jobDetail(){
        //查询数据库或者配置文件
        JobDetailFactoryBean jobDetailFactoryBean=new JobDetailFactoryBean();
        jobDetailFactoryBean.setName("");
        jobDetailFactoryBean.setBeanName("");
        jobDetailFactoryBean.setJobClass((Class<? extends Job>) aClass);
        jobDetailFactoryBean.setGroup("");
        jobDetailFactoryBean.setDurability(true);
        return jobDetailFactoryBean;
    }

	/**第二种写法*/
    JobDetailFactoryBean jobFactory = new JobDetailFactoryBean();
    jobFactory.setName("");
    jobFactory.setBeanName("");
    jobFactory.setJobClass((Class<? extends Job>) aClass);
    jobFactory.setGroup("");
    jobFactory.setDurability(true);
    jobFactory.afterPropertiesSet();
    //完成JobDetail的创建
    JobDetail jobDetail= jobFactory.getObject();

方法二:使用建筑者模式创建job或者trigger

    //创建Job
    public static JobDetail getJobDetail(JobKey jobKey, String description, boolean jobShouldRecover, JobDataMap jobDataMap, Class<? extends Job> jobClass) {

        return JobBuilder.newJob(jobClass)
                .withIdentity(jobKey)
                .withDescription(description)
                .setJobData(jobDataMap)
                .usingJobData(jobDataMap)   //设置JobDataMap字段
                .requestRecovery(jobShouldRecover)
                .storeDurably()  //表示当没有触发器与之关联时,仍然将job继续保存在Scheduler中
                .build();
    }
    //创建Trigger
    public static Trigger getCornTrigger(TriggerKey triggerKey, String description, JobDataMap jobDataMap, String cronExpression, JobKey jobKey) {
        return TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withDescription(description)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .forJob(jobKey.getName(), jobKey.getGroup()) //制定Trigger和Job的关联关系
                .usingJobData(jobDataMap)  //具体执行的方法中可以拿到这个传进去的信息。
                .build();
    }

1.任务进行动态处理

首先我们需要在项目启动完毕时,去加载自定义定时配置表的配置,动态的去创建任务。那么需要实现CommandLineRunner/ApplicationRunner接口。动态操作定时任务本质是对scheduler调用,然后根据自身业务进行扩展。代码如下:

/**
 * Created by EalenXie on 2019/7/10 13:49.
 * 核心其实就是Scheduler的功能 , 这里只是非常简单的示例说明其功能
 * 如需根据自身业务进行扩展 请参考 {@link org.quartz.Scheduler}
 */
@Slf4j
@Service
public class QuartzJobService {

    //Quartz定时任务核心的功能实现类
    private Scheduler scheduler;

    public QuartzJobService(@Autowired SchedulerFactoryBean schedulerFactoryBean) {
        scheduler = schedulerFactoryBean.getScheduler();
    }

    /**
     * 创建和启动 定时任务
     * {@link org.quartz.Scheduler#scheduleJob(JobDetail, Trigger)}
     *
     * @param define 定时任务
     */
    public void scheduleJob(TaskDefine define) throws SchedulerException {
        //1.定时任务 的 名字和组名
        JobKey jobKey = define.getJobKey();
        //2.定时任务 的 元数据
        JobDataMap jobDataMap = getJobDataMap(define.getJobDataMap());
        //3.定时任务 的 描述
        String description = define.getDescription();
        //4.定时任务 的 逻辑实现类
        Class<? extends Job> jobClass = define.getJobClass();
        //5.定时任务 的 cron表达式
        String cron = define.getCronExpression();
        JobDetail jobDetail = getJobDetail(jobKey, description, jobDataMap, jobClass);
        Trigger trigger = getTrigger(jobKey, description, jobDataMap, cron);
        scheduler.scheduleJob(jobDetail, trigger);
    }


    /**
     * 暂停Job
     * {@link org.quartz.Scheduler#pauseJob(JobKey)}
     */
    public void pauseJob(JobKey jobKey) throws SchedulerException {
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢复Job
     * {@link org.quartz.Scheduler#resumeJob(JobKey)}
     */
    public void resumeJob(JobKey jobKey) throws SchedulerException {
        scheduler.resumeJob(jobKey);
    }

    /**
     * 删除Job
     * {@link org.quartz.Scheduler#deleteJob(JobKey)}
     */
    public void deleteJob(JobKey jobKey) throws SchedulerException {
        scheduler.deleteJob(jobKey);
    }


    /**
     * 修改Job 的cron表达式
     */
    public boolean modifyJobCron(TaskDefine define) {
        String cronExpression = define.getCronExpression();
        //1.如果cron表达式的格式不正确,则返回修改失败
        if (!CronExpression.isValidExpression(cronExpression)) return false;
        JobKey jobKey = define.getJobKey();
        TriggerKey triggerKey = new TriggerKey(jobKey.getName(), jobKey.getGroup());
        try {
            CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            JobDataMap jobDataMap = getJobDataMap(define.getJobDataMap());
            //2.如果cron发生变化了,则按新cron触发 进行重新启动定时任务
            if (!cronTrigger.getCronExpression().equalsIgnoreCase(cronExpression)) {
                CronTrigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(triggerKey)
                        .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                        .usingJobData(jobDataMap)
                        .build();
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        } catch (SchedulerException e) {
            log.error("printStackTrace", e);
            return false;
        }
        return true;
    }


    /**
     * 获取定时任务的定义
     * JobDetail是任务的定义,Job是任务的执行逻辑
     *
     * @param jobKey      定时任务的名称 组名
     * @param description 定时任务的 描述
     * @param jobDataMap  定时任务的 元数据
     * @param jobClass    {@link org.quartz.Job} 定时任务的 真正执行逻辑定义类
     */
    public JobDetail getJobDetail(JobKey jobKey, String description, JobDataMap jobDataMap, Class<? extends Job> jobClass) {
        return JobBuilder.newJob(jobClass)
                .withIdentity(jobKey)
                .withDescription(description)
                .setJobData(jobDataMap)
                .usingJobData(jobDataMap)
                .requestRecovery()
                .storeDurably()
                .build();
    }


    /**
     * 获取Trigger (Job的触发器,执行规则)
     *
     * @param jobKey         定时任务的名称 组名
     * @param description    定时任务的 描述
     * @param jobDataMap     定时任务的 元数据
     * @param cronExpression 定时任务的 执行cron表达式
     */
    public Trigger getTrigger(JobKey jobKey, String description, JobDataMap jobDataMap, String cronExpression) {
        return TriggerBuilder.newTrigger()
                .withIdentity(jobKey.getName(), jobKey.getGroup())
                .withDescription(description)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .usingJobData(jobDataMap)
                .build();
    }


    public JobDataMap getJobDataMap(Map<?, ?> map) {
        return map == null ? new JobDataMap() : new JobDataMap(map);
    }


}

九、监听

Quartz监听种类有:

  • JobListener: 任务监听
  • TriggerListener: 触发器监听
  • SchedulerListener: 调度器监听

作用域分为全局监听器和局部监听器,全局监听器能够接收所有job/trigger的事件通知,局部监听器只能接收在其上注册job/trigger的事件通知

1.JobListener源码

public interface JobListener {
    //获取该JobListener的名称
    String getName();
    //Scheduler在JobDetail将要被执行时调用该方法
    void jobToBeExecuted(JobExecutionContext context);
    //Scheduler在JobDetail将要被执行时,但又被TriggerListener否决调用
    void jobExecutionVetoed(JobExecutionContext context);
    //任务执行完毕调用该方法
    void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);

}

将JobListener绑定到Schedule中:

//监听所有的Job
scheduler.getListenerManager().addJobListener(new SimpleJobListener(), EverythingMatcher.allJobs());
//监听特定的Job
scheduler.getListenerManager().addJobListener(new SimpleJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("HelloWorld1_Job", "HelloWorld1_Group")));
//监听同一任务组的Job
scheduler.getListenerManager().addJobListener(new SimpleJobListener(), GroupMatcher.jobGroupEquals("HelloWorld2_Group"));
//监听两个任务组的Job
scheduler.getListenerManager().addJobListener(new SimpleJobListener(), OrMatcher.or(GroupMatcher.jobGroupEquals("HelloWorld1_Group"), GroupMatcher.jobGroupEquals("HelloWorld2_Group")));

2.TriggerListener源码

public interface TriggerListener {
    //获取触发器的名字
    public String getName();
    //Job的execute()方法被调用时调用该方法。
    public void triggerFired(Trigger trigger, JobExecutionContext context);
    //Trigger触发后,TriggerListener给了一个选择否定Job的执行。假如该方法返回true,该Job将不会被触发
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
    //Trigger错过触发时间触发该方法,此方法不应该含有长时间的处理逻辑。
    public void triggerMisfired(Trigger trigger);
    //Trigger被触发并且完成Job后触发。
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            int triggerInstructionCode);
}

将TriggerListener绑定到Scheduler中:

//监听所有的Trigger
scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), EverythingMatcher.allTriggers());
//监听特定的Trigger
scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), KeyMatcher.keyEquals(TriggerKey.triggerKey("HelloWord1_Job", "HelloWorld1_Group")));
//监听一组Trigger
scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), GroupMatcher.groupEquals("HelloWorld1_Group"));
//移除监听器
scheduler.getListenerManager().removeTriggerListener("SimpleTrigger");

3.SchedulerListener

public interface SchedulerListener {
     //用于部署JobDetail时调用
    public void jobScheduled(Trigger trigger);
    //用于卸载JobDetail时调用
    public void jobUnscheduled(String triggerName, String triggerGroup);
    //当一个Trigger没有触发次数时调用。
    public void triggerFinalized(Trigger trigger);

    public void triggersPaused(String triggerName, String triggerGroup);

    public void triggersResumed(String triggerName, String triggerGroup);
    //当一个或一组 JobDetail 暂停时调用这个方法。
    public void jobsPaused(String jobName, String jobGroup);
    //当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
    public void jobsResumed(String jobName, String jobGroup);
    //在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
    public void schedulerError(String msg, SchedulerException cause);
    //当Scheduler 开启时,调用该方法
    public void schedulerStarted();
    //当Scheduler处于StandBy模式时,调用该方法
    public void schedulerInStandbyMode();
    //当Scheduler停止时,调用该方法
    public void schedulerShutdown();
    //当Scheduler中的数据被清除时,调用该方法。
    public void schedulingDataCleared();
}

将SchedulerListener绑定到Scheduler中:

//创建监听
scheduler.getListenerManager().addSchedulerListener(new SimpleSchedulerListener());
//移除监听
scheduler.getListenerManager().removeSchedulerListener(new SimpleSchedulerListener());