quartz学习笔记

1,476 阅读9分钟

1 quartz 概述

quartz是一个任务调度框架, 用来定时执行任务, 比较常见的任务有不同数据源之间的简单的数据同步任务等。
quartz是可以以集群的模式来运行的。


2 quartz的核心概念

Job, 代表一个需要执行的任务, 需要实现quartz的Job接口。
Trigger, 定义了任务的触发规则, 常用的Trigger, 有SimpleTrigger和CronTrigger。
Scheduler, 将Job和Trigger关联在一起;(仅仅如此吗? 如果仅仅是这个那么就没有专门用这个东西了)。


3 quartz的组件之间的关系


4 quartz的常用api

Scheduler:
scheduleJob(JobDetial job, Trigger trigger): 将jobDetail和trigger关联;
start(): 开始执行任务;
standBy(): 停止执行, 但是可以重新开始执行;
shutdown():不带参数的shutdown, 是等待已经提交的任务执行完毕后关闭Sheduler;
shutdown(boolean now): 为true, 是直接关闭Sheduler, 但是已经在线程中执行的任务有时也会完成执行; 为false时, 等待已经提交的任务执行完毕后关闭Sheduler;

Job, 这个是任务的定义接口;
JobDetail, 封装了Job的实例还有其他的想附加上的数据, 由JobBuilder构建;
Trigger, 任务的触发规则, 由TriggerBuilder构建;
JobListener, TriggerListener和SchedulrListener, 用于监听三个核心组件。



5 quartz的使用

5.1 依赖

quartz的依赖, 可以使用maven来管理, quartz的依赖除了一个artifact为quartz的依赖外, 还有一个artifact为quartz-jobs的可选依赖, 这个依赖放置了一些quartz的工具代码, 一些预定义的job。
quartz核心maven依赖:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.1</version>
</dependency>


5.2 入门示例

使用quartz来调度任务, 具体工作可以分成一下及部分。
1 定义一个Job:
通过实现一个Job接口来实现一个Job, 只需要实现其中的execute方法就可以了。 代码如下:

public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        System.out.println("测试quartz job. 时间为:" + dateFormat.format(date));
    }
}

2 创建一个JobDetail, 通过JobBuilder来创建一个JobDetail

// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        .build();
2 创建相应的trigger, 定义具体如何来调度:
通过TriggerBuilder来创建trigger;

// Trigger the job to run now, and then repeat every 40 seconds
Trigger trigger = newTrigger()
        .withIdentity("trigger1", "group1")
        .startNow()
        .withSchedule(simpleSchedule()
                .withIntervalInSeconds(40)
                .repeatForever())
        .build();
4 通过SchedulerFactory创建scheduler, 将JobDetail和Trigger关联起来, 开始执行任务:

// Grab the Scheduler instance from the Factory
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

// and start it off
scheduler.start();

// Tell quartz to schedule the job using our trigger
scheduler.scheduleJob(job, trigger);


5.3 Job和JobDetail介绍

每次Job被形式时, quartz都会创建一个新的Job实例, 然后调用这个实例的execute方法。

JobDetail
JobDetail实例是对Job实例的一个包装, 其中除了包含Job实例外, 还由这些比较常用的属性: name, group, jobDataMap和jobclass等。 其中jobDataMap, 可以实现在job的不同实例之间共享数据。

设置和获取的例子:

// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        .build();

System.out.println("group:" + job.getKey().getGroup());
System.out.println("name:" + job.getKey().getName());
System.out.println("class:" + job.getKey().getClass());




5.4 JobExecutionContext介绍

JobExecutionContext, 封装运行时和任务相关的东西。可以通过JobExecutionContext的方法来获取JobDetail, Trigger, Scheduler等实例。

context.getJobDetail();
context.getScheduler();
context.getTrigger();


5.5 JobDataMap

一个Map, 在创建JobDetail时, 可以像JobDataMap中指定一些键值对作为输入。 然后再Job的execute的方法中, 可以通过JobExecutionContext的getJobDataMap方法来获取这些属性。
可以用做job的输入。
JobDetail的JobDataMap
在JobDetail创建时放入JobDataMap的键值对, 那么在所有的job实例中都将可以使用这些键值对。
新建JobDetail时放入键值对:

JobDetail job = newJob(HelloJob.class)
        .withIdentity("job1", "group1")
        .usingJobData("key1", "value1")
        .usingJobData("key2", "value2")
        .build();

在job中使用键值对:

System.out.println("key1的值为: " + context.getMergedJobDataMap().getString("key1"));
System.out.println("key2的值为: " + context.getMergedJobDataMap().getString("key2"));

Trigger的JobDataMap
在Trigger创建时放入JobDataMap的键值对,那么所有被这个Trigger触发的Job都可以使用这些键值对
新建Trigger时放入键值对:

Trigger trigger = newTrigger()
        .withIdentity("trigger1", "group1")
        .startNow()
        .withSchedule(simpleSchedule()
                .withIntervalInSeconds(5)
                .repeatForever())
        .usingJobData("key2", "value2我来自trigger")
        .build();

在job中使用

System.out.println("来自trigger的key2的值为: " + context.getMergedJobDataMap().getString("key2"));


如果在trigger和jobDetail中定义了同名的key,并且在job使用getMergedJobDataMap, 那么trigger的键值对有更加高的优先级。






5.6 有状态的Job和无状态的Job

有状态的Job, 新建一个job实例执行任务时, 会传入旧的JobDataMap实例。 同一个Job类的不同实例, 将共享一份JobDataMap.
通过在Job类上加上一个注解@persistJobDataAfterExecution, 就可以将job中对jobDataMap的更改保存。

@PersistJobDataAfterExecution
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        int count = context.getMergedJobDataMap().getInt("count");
        System.out.println("进行计数, 当前的计数为:" + count);
        count++;
        context.getJobDetail().getJobDataMap().put("count", count);
    }
}



5.7 Trigger介绍

用来定义任务的触发规则, 或者说调度规则。


5.8 SimpleTrigger触发器

功能: 可以定义多久执行一次, 重复的次数, 从什么时候开始, 从什么时候结束等
例子: 从现在开始, 重复间隔为1秒, 重复三次

// Trigger the job to run now, and then repeat every 40 seconds
Trigger trigger = newTrigger()
        .withIdentity("trigger1", "group1")
        .startNow()
        .withSchedule(simpleSchedule()
                .withIntervalInSeconds(1)
                .withRepeatCount(3))
        .build();



5.9 CronTrigger触发器

使用Cron表达式来指定任务执行的规则。
使用的示例代码:

// Trigger the job to run now, and then repeat every 10 seconds
Trigger trigger = newTrigger()
        .withIdentity("trigger1", "group1")
        .startNow()
        .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
        .build();


Cron表达式

Cron表达式是一个指定日期的表达式, 使用空格分成七个部分。 其中第7个部分是年, 是可选的, 如果不选, 则表示每一年。
每个字段的含义:
字段
是否必填
允许值
可以出现的运算符
必填
0 - 59
/ * , -
必填
0 - 59
/ * , -
必填
0 - 23
/ * , -
月中的天
必填
1 - 31
/ * , - ? L W C
必填
1 - 12, 也可以用JAN这些缩写
/ * , -
星期中的天
必填
1 - 7, 7时星期六, 也可以用WED这些缩写
/ * , - ? L C #
年份
非必填
1970 - 2099
/ * , -


运算符的含义:

运算符
含义
*
表示这个字段的每一个值, 可以出现在所有字段中。 如”* * * * * ?*”表示每一秒钟。
/
从一个值开始, 间隔多少时间后再次执行, 可以出现在所有的字段。 如在分钟字段上, 0/15表示从0分开始, 间隔15分钟执行一次, 相当于0,15,30,45. 3/20相当于3,23,43.
,
表示多个值, 可以出现在所有的字段。 如周一周三周五可表示为MON,WED,FRI。
-
表示一个区间。如周一到周三可表示为MON-WED。
?
表示放弃这个字段设置, 可以出现在月中的天和周中的天这两个字段。如”* * * * * ?*”表示每一秒钟。
L
L是last的缩写, 可以出现在月中的天和周中的天这两个字段, 当时含义不一样。 在月中的天这个字段中, L表示这个月的最后一天。 而在周中的天中, 如果L单独出现, 那么就表示7或者SAT即星期六, 如果出现在一个数字的后面, 那么就表示这个月的周几, 如“6L”, 表示这个月的最后一个周五
W
W是week的缩写, 可以出现在月中的天这个字段, 可以用来指定距离这个时间最近的周几, 周的天在Day of Week字段中指定。
#
可以出现在周中的天这个字段, 表示这个月的第几个星期。 如1#3, 表示这个月的第三个星期日。




5.10 配置SchedulerFactory

Scheduler是由SchedulerFactory创建的. 默认使用StdSchedulerFactory来创建Scheduler, 这个factory会读取quartz.properties的配置, 来创建Scheduler. 也可以指定不使用quart.properties的属性, 而是在代码中指定一个Properties给factory.





5.10 quartz.properties

可以通过quartz.properties文件来配置quartz的行为。 quartz包中有一个quartz.properties, 可以在我们自己的项目的类路径下放置quartz.properties来覆盖默认的配置。

调度器属性:
org.quartz.scheduler.instanceName 属性用来区分特定的调度器实例
org.quartz.scheduler.instanceId 这个属性是调度器的id, 在集群环境中, 这个属性的值需要是唯一的. 如果想让quartz生成id, 可以使用AUTO值

线程池属性:
threadCount: 线程的数量;
threadPriority: 线程的优先级;
org.quartz.threadPool.class: quartz的线程池实现类

作业存储设置:
描述了Job和Trigger的信息是如何被存储的

插件配置

更加详细的插件配置, 参考官网: www.quartz-scheduler.org/documentati…





5.11 quartz的Listener

有JobListener, TriggerListener和SchedulerListener, 可以实现对quartz的核心组件的监听, 放置一些横切逻辑.
quartz的Listener可以是全局的, 也可以是只注册到特定的组件的.





5.12 JobListener的使用

接口定义:

public interface JobListener {
    // 返回监听器的名字
    String getName();
    // 这个Job将被执行之前
    void jobToBeExecuted(JobExecutionContext context);
    // 当Job被Trigger的规则触发, 将要被执行, 当时TriggerListener撤销了这个
    // Job的执行
    void jobExecutionVetoed(JobExecutionContext context);
    // 这个Job被执行之后执行
    void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);
}

使用示例:
1 JobListener的编写:

public class HelloJobListener implements JobListener {
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println("任务将要被执行");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println("任务被Trigger触发, 但是被TriggerListener撤销执行");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println("任务已经被执行");
    }
}

2 配置

为所有的Job添加监听器:// 配置一个全局的JobListener
scheduler.getListenerManager().addJobListener(new HelloJobListener(), EverythingMatcher.allJobs());
为一些Job添加监听器: // 配置一个全局的JobListener
scheduler.getListenerManager().addJobListener(new HelloJobListener(), KeyMatcher.key("job1", "group1"));


5.13 TriggerListener的使用

接口定义:

public interface TriggerListener {
    // 指定这个TriggerListener的名字
    String getName();
    // 当trigger的调度条件已经满足, job将要被触发, 这个在vetoJobExecution之前执行
    void triggerFired(Trigger trigger, JobExecutionContext context);
    // 当trigger的调度条件已经满足, job将要被触发, 
    // 这个方法在triggerFired之后执行
    // 如果返回true, 那么job将不会被执行
    boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
    // 当一个trigger不被满足时执行
    void triggerMisfired(Trigger trigger);
    // 当这个trigger触发的job被执行完毕时执行
    void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode);

}
所有Trigger的监听:

scheduler.getListenerManager().addTriggerListener(new TriggerListenerDemo(),
        EverythingMatcher.allTriggers());
匹配特定的Trigger:

scheduler.getListenerManager().addTriggerListener(new TriggerListenerDemo(),
        KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));



5.14 SchedulerListenner的使用

接口定义:

public interface SchedulerListener {
    // 当一个任务被schedule的时候执行
    void jobScheduled(Trigger trigger);

    // 当一个job被unschedule的时候执行
    void jobUnscheduled(TriggerKey triggerKey);

    // 当一个trigger永远不会被再次触发的时候执行
    void triggerFinalized(Trigger trigger);

    void triggerPaused(TriggerKey triggerKey);
    
    void triggersPaused(String triggerGroup);
    
    void triggerResumed(TriggerKey triggerKey);

    void triggersResumed(String triggerGroup);

    void jobAdded(JobDetail jobDetail);
    
    void jobDeleted(JobKey jobKey);
    
    void jobPaused(JobKey jobKey);

    void jobsPaused(String jobGroup);
    
    void jobResumed(JobKey jobKey);

    void jobsResumed(String jobGroup);

    void schedulerError(String msg, SchedulerException cause);

    void schedulerInStandbyMode();


    void schedulerStarted();
    

    void schedulerStarting();
    

    void schedulerShutdown();
    

    void schedulerShuttingdown();


    void schedulingDataCleared();
}