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