# Quartz 简介
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
# 核心概念
Job 表示一个工作,要执行的具体内容。此接口中只有一个方法。每一个 job 类都必须实现该接口。
// com.quartz.job
void execute(JobExecutionContext context);
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
Trigger 触发器,代表一个调度参数的配置,它主要包含两种触发器:
SimpleTrigger
、CronTrigger
。Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
# 设计模式
Builder 模式
Factory 模式
组件模式
链式编程
# 体系结构
# 常用 API
Scheduler 是用于与调度程序交互的主程序接口。
Scheduler 调度程序 - 任务执行计划表,只有安排进执行计划的任务 Job(通过 scheduler.schedulejob 方法安排进执行计划),当它预先定义的执行时间到了(任务触发 trigger),该任务才会执行。
Job 是我们预先定义希望在未来时间能被调度程序执行的任务类。
JobDetail 使用 JobDetail 来定义任务的实例, JobDetail 实例是通过 JobBuilder 类创建的。
JobDataMap 可以包含不限量的(序列化的)数据对象,在 job 实例执行的时候,我们可以使用其中的数据; JobDataMap 是 Java Map 接口的一个实现,并且额外增加了一些便于存取基本类型的数据的方法。
Trigger 触发器,Trigger 对象是用来触发执行 Job 的。当调度一个 job 时,我们实例一个触发器,然后调度它的属性来满足 job 执行的条件。它表明任务在什么时候执行。
JobBuilder 用于声明一个任务实例,也可以用于定义一个该任务的详情,比如任务名、组名等,这个声明的实例将会作为一个世纪执行的任务。
TriggerBuilder 触发器创建器,用于创建触发器 trigger 实例。
JobListener 、 TriggerListener 、 SchedulerListener 监听器,用于对组件的监听。
# 使用实例
# 准备工作
创建测试项目 quartz-demo
(此处示例使用 springboot 进行演示)。
# 完整依赖
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<parent> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-parent</artifactId> | |
<version>2.3.3.RELEASE</version> | |
<relativePath/> <!-- lookup parent from repository --> | |
</parent> | |
<groupId>com.xfc</groupId> | |
<artifactId>quartz-demo</artifactId> | |
<version>0.0.1-SNAPSHOT</version> | |
<name>quartz-demo</name> | |
<description>Demo project for Spring Boot</description> | |
<properties> | |
<java.version>1.8</java.version> | |
</properties> | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.quartz-scheduler</groupId> | |
<artifactId>quartz</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-test</artifactId> | |
<scope>test</scope> | |
<exclusions> | |
<exclusion> | |
<groupId>org.junit.vintage</groupId> | |
<artifactId>junit-vintage-engine</artifactId> | |
</exclusion> | |
</exclusions> | |
</dependency> | |
</dependencies> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-maven-plugin</artifactId> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
# 任务调度实例
创建任务类 HelloJob.java
package com.xfc.quartz.job; | |
import org.quartz.Job; | |
import org.quartz.JobExecutionContext; | |
import org.quartz.JobKey; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
public class HelloJob implements Job { | |
@Override | |
public void execute(JobExecutionContext context) { | |
Date date = new Date(); | |
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); | |
String dateStr = sdf.format(date); | |
System.out.println("Hello World, Current Time Is : " + dateStr); | |
System.out.println("-----------------------job info-----------------------"); | |
JobKey jobKey = context.getJobDetail().getKey(); | |
System.out.println("jobKey.getName() = " + jobKey.getName()); | |
System.out.println("jobKey.getGroup() = " + jobKey.getGroup()); | |
System.out.println("任务类名称 = " + context.getJobDetail().getJobClass().getName()); | |
} | |
} |
创建测试类 SchedulerTest.java
package com.xfc.quartz.test; | |
import com.xfc.quartz.job.HelloJob; | |
import org.quartz.*; | |
import org.quartz.impl.StdSchedulerFactory; | |
public class SchedulerTest { | |
public static void main(String[] args) throws SchedulerException { | |
// 1. 创建调度器(Scheduler)从工厂中获取调度实例(默认:实例化 new StdSchedulerFactory ();) | |
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); | |
// 2. 创建任务实例(JobDetail) | |
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)// 加载任务类 | |
.withIdentity("job1", "group1")// 任务名称,任务组名 | |
.build(); | |
// 3. 创建触发器(Trigger) | |
Trigger trigger = TriggerBuilder.newTrigger() | |
.withIdentity("trigger1", "group1")// 触发器名称,触发器组名 | |
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(5))// 每 3 秒调用 1 次,共 5 次 | |
.build(); | |
// 4. 调度器关联任务和触发器 | |
scheduler.scheduleJob(jobDetail, trigger); | |
// 5. 启动调度器 | |
scheduler.start(); | |
} | |
} |
运行 main 方法得到如下结果:
Hello World, Current Time Is : 2020-08-31 03:07:51
Hello World, Current Time Is : 2020-08-31 03:07:54
Hello World, Current Time Is : 2020-08-31 03:07:57
Hello World, Current Time Is : 2020-08-31 03:08:00
Hello World, Current Time Is : 2020-08-31 03:08:03
Hello World, Current Time Is : 2020-08-31 03:08:06
# Job & JobDetail
Job 工作任务调度的接口,任务类需要实现该接口,该接口中定义
execute
方法,类似 JDK 提供的 TimeTask 的 run 方法。Job 实例在 Quartz 中的生命周期:每次调度执行 Job 时,它在调度 excute 方法前会创建一个新的 Job 实例,当调度完成后,关联的 Job 实例会被释放,释放的实例会被垃圾回收机制回收。
JobDetail 为 Job 实例提供了许多设置属性,以及 JobDetailMap 的成员变量属性,它用来储存特定的 job 实例的状态信息,调度器需要借助 JobDetail 对象来添加 job 实例。
jobDetail 重要属性:name、group、jobClass、jobDataMap。
# JobExcutionContext
当 Scheduler 调用一个 job ,就会将 JobExcutionContext 传递给 Job 的 excute () 方法。
Job 能通过 JobExcutionContext 对象访问到 Quartz 运行时候的环境以及 job 本身的明细数据。
# JobDataMap
使用 Map 获取
JobDataMap 可以用来装载任何可序列化的数据对象,当 job 实例对象被执行时这些参数对象会传递给它。
JobDataMap 实现了 JDK 的 Map 接口,并且添加了非常方便的方法用来存储基本数据类型。
通过
TriggerBuilder.newTrigger().usingJobData("key", "value")
及JobBuilder.newJob(HelloJob.class).usingJobData("key", "value")
方式可以放入自定义参数值。通过
JobExecutionContext.getJobDetail().getJobDataMap()
方法可以获取到 JobDetail 中的 JobDataMap 信息。同样地,也可以通过
JobExecutionContext.getTrigger().getJobDataMap()
方法获取到 Trigger 中的参数值信息。
Job 实现类中添加 setter 方法对应 JobDataMap 的键值,Quartz 框架默认的 JobFactory 实现类在初始化 job 实例对象时会自动地调用这些 setter 方法。
注:如果遇到同名的 key 值,同名的内容会被后赋值者覆盖。
# 有状态的 Job 和无状态的 Job
有状态的 Job 可以理解为多次 Job 调用期间可以持有一些状态信息,这些状态信息存储在 JobDataMap 中,而默认的无状态 Job 每一次调用时都会创建一个新的 JobDataMap 。
修改示例代码如下:
package com.xfc.quartz.job; | |
import org.quartz.Job; | |
import org.quartz.JobExecutionContext; | |
import org.quartz.JobKey; | |
import org.quartz.PersistJobDataAfterExecution; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
@PersistJobDataAfterExecution// 有状态 job,多次调用 job 时,会对 job 进行持久化,即在以下示例中,带有此注解时,key1 值在任务每次执行时都会更新 | |
public class HelloJob implements Job { | |
private String key1; | |
private String key2; | |
// Quartz 框架使用 usingJobData () 放置参数时,会默认调用对应同名的 setter 方法 | |
public void setKey1(String key1) { | |
this.key1 = key1; | |
} | |
public void setKey2(String key2) { | |
this.key2 = key2; | |
} | |
@Override | |
public void execute(JobExecutionContext context) { | |
Date date = new Date(); | |
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); | |
String dateStr = sdf.format(date); | |
System.out.println("Hello World, Current Time Is : " + dateStr); | |
System.out.println("-----------------------job info-----------------------"); | |
JobKey jobKey = context.getJobDetail().getKey(); | |
System.out.println("jobKey.getName() = " + jobKey.getName()); | |
System.out.println("jobKey.getGroup() = " + jobKey.getGroup()); | |
System.out.println("任务类名称 = " + context.getJobDetail().getJobClass().getName()); | |
System.out.println("key1 = " + key1); | |
System.out.println("key2 = " + key2); | |
// 修改值并存储到 JobDataMap 中 | |
context.getJobDetail().getJobDataMap().put("key1", key1 + "_append_str"); | |
} | |
} |
package com.xfc.quartz.test; | |
import com.xfc.quartz.job.HelloJob; | |
import org.quartz.*; | |
import org.quartz.impl.StdSchedulerFactory; | |
public class SchedulerTest { | |
public static void main(String[] args) throws SchedulerException { | |
// 1. 创建调度器(Scheduler)从工厂中获取调度实例(默认:实例化 new StdSchedulerFactory ();) | |
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); | |
// 2. 创建任务实例(JobDetail) | |
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)// 加载任务类 | |
.withIdentity("job1", "group1")// 任务名称,任务组名 | |
.usingJobData("key1", "value1") | |
.build(); | |
// 3. 创建触发器(Trigger) | |
Trigger trigger = TriggerBuilder.newTrigger() | |
.withIdentity("trigger1", "group1")// 触发器名称,触发器组名 | |
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(5))// 每 3 秒调用 1 次,共 5 次 | |
.usingJobData("key2", "value2") | |
.build(); | |
// 4. 调度器关联任务和触发器 | |
scheduler.scheduleJob(jobDetail, trigger); | |
// 5. 启动调度器 | |
scheduler.start(); | |
} | |
} |
# Trigger
Quartz 有一些不同的触发器,常用有两种: SimpleTrigger
、 CronTrigger
。
jobKey 表示 job 实例的标识,触发器被触发时,其指定的 job 实例会被执行。
startTime 表示触发器的时间表,第一次开始被触发的时间,其数据类型是 java.util.Date 。
endTime 表示触发器终止被触发的时间,其数据类型是 java.util.Date 。
获取 jobKey , startTime , endTime
context.getTrigger().getJobKey().getName();
context.getTrigger().getStartTime();
context.getTrigger().getEndTime();
# SimpleTrigger
SimpleTrigger
触发器对于设置和使用是最为简单的一种 QuartzTrigger 。
它是为那种需要在特定日期或时间启动,且以一个可能的时间间隔重复执行多次的 job 而设计的。
注意:
SimpleTrigger 的属性有:开始时间、结束时间、重复次数和重复的时间间隔。
重复次数属性的值可以为 0、正整数或常量
SimpleTrigger.REPEAT_INDEFINITELY
。重复的时间间隔属性值必须大于 0 或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为 0 时,意味着与 Trigger 同时触发执行。
如果有指定的结束时间属性值,则结束时间属性优先于重复次数属性。
# CronTrigger
如果需要像日历一样按日程来触发任务,而非像 SimpleTrigger 间隔特定时间触发,CronTrigger 会是更优选择,因为它是基于日历的作业任务调度器。
Cron Expression(Cron 表达式)
Cron 表达式被用来配置 CronTrigger 实例。Cron 表达式是一个由 7 个子表达式组成的字符串,每个子表达式都描述了一个单独的日程细节,每个子表达式之间用空格分隔,它们分别表示:
Seconds
Minutes
Hours
Day-of-Month(一月中的某一天)
Month
Day-of-Week(一周中的某一天)
Year
在线工具:https://cron.qqe2.com
# 配置、资源 SchedulerFactory
Scheduler 的创建方式
StdSchedulerFactory:Quartz 默认的 SchedulerFactory 。
使用一组参数(java.util.Properties)来创建和初始化 Quartz 调度器。
配置参数一般存储在
quartz.properties
文件中。调用 getScheduler 方法就能创建和初始化调度器对象。
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// ...
scheduler.start();
//scheduler.standby ();// 任务挂起
//scheduler.shutdown ();// 关闭任务,布尔参数,是否等待所有任务执行后才关闭
# Quartz.properties
调度器属性
org.quartz.scheduler.instanceName 属性用来区分特定的调度器实例。
org.quartz.scheduler.instanceId 和前者一样,也允许任何字符串,但这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群唯一的 key 值。
线程池属性
threadCount
处理 Job 线程的个数,至少为 1 ,但不建议超过 100 个。
threadPriority
线程的优先级,优先级别高的线程比优先级别低的线程优先得到执行,最小为 1,最大为 10,默认为 5。
org.quartz.threadPool.class
它是一个实现了
org.quartz.spi.ThreadPool
接口的类,Quartz 自带的线程实现类是org.quartz.smpl.SimpleThreadPool
。
作业存储设置
描述了在调度器实例的生命周期中, Job 和 Trigger 信息是如何被存储的。
插件配置
# 其他
除去在配置文件中配置相关属性外,也可以通过 springboot 的配置类。
示例:
package com.jason.quartz.config; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.scheduling.quartz.SchedulerFactoryBean; | |
import javax.sql.DataSource; | |
import java.util.Properties; | |
/** | |
* 定时任务配置 | |
* | |
* @author Jason Chen | |
*/ | |
@Configuration | |
public class ScheduleConfig { | |
@Bean | |
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { | |
SchedulerFactoryBean factory = new SchedulerFactoryBean(); | |
factory.setDataSource(dataSource); | |
//quartz 参数 | |
Properties prop = new Properties(); | |
prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); | |
prop.put("org.quartz.scheduler.instanceId", "AUTO"); | |
// 线程池配置 | |
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); | |
prop.put("org.quartz.threadPool.threadCount", "20"); | |
prop.put("org.quartz.threadPool.threadPriority", "5"); | |
// JobStore 配置 | |
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); | |
// 集群配置 | |
prop.put("org.quartz.jobStore.isClustered", "true"); | |
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); | |
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); | |
prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); | |
//sqlserver 启用 | |
// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); | |
prop.put("org.quartz.jobStore.misfireThreshold", "12000"); | |
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); | |
factory.setQuartzProperties(prop); | |
factory.setSchedulerName("RuoyiScheduler"); | |
// 延时启动 | |
factory.setStartupDelay(1); | |
factory.setApplicationContextSchedulerContextKey("applicationContextKey"); | |
// 可选,QuartzScheduler | |
// 启动时更新己存在的 Job,这样就不用每次修改 targetObject 后删除 qrtz_job_details 表对应记录了 | |
factory.setOverwriteExistingJobs(true); | |
// 设置自动启动,默认为 true | |
factory.setAutoStartup(true); | |
return factory; | |
} | |
} |
# Quartz 监听器
# 概念
Quartz 的监听器用于当任务调度你所关注的事件发生时,能够及时获取这一事件的通知。Quartz 的监听器主要有 JobListener
、 TriggerListener
、 SchedulerListener
三种,它们分别表示任务、触发器、调度器对应的监听器。
# JobListener
在调度过程中,与任务相关的 Job 事件包括:Job 将要执行时的提示和 Job 执行完成后的提示。
使用:
创建一个实现 JobListener 接口的监听器类,并重写其方法。
getName()
获取该 JobListener 的名称。jobToBeExecuted()
在 JobDetail 将要执行时调用。jobExecutionVetoed()
在 JobDetail 即将被执行但被 TriggerListener 否决时调用。jobWasExecuted()
在 JobDetail 被执行之后调用。
创建并注册 JobListener
// 创建并注册 JobListener 监听所有任务
scheduler.getListenerManager().addJobListener(new MyJobListener(), EveryThingMatcher.allJobs());
// 对指定的任务进行监听
scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));
# TriggerListener
在调度过程中,与触发器 Trigger 相关的事件包括:触发器触发、触发器未正常触发、触发器完成等。
getName()
用于获取触发器名称。triggerFired()
当与监听器相关联的 Trigger 被触发, Job 上的 excute () 方法被执行时调用。vetoJobException()
在 Trigger 被触发后, Job 将要被执行时由 Scheduler 调用这个方法。( TriggerListener 可以否决一个 Job 的执行)。triggerMisfired()
在 Trigger 错过被触发时调用。triggerComplete()
Trigger 被触发并且执行 Job 完成时调用。
# SchedulerListener
SchedulerListener
会在 Scheduler 的生命周期中关键事件发生时调用。与 Scheduler 有关的事件包括:增加或删除一个 job/trigger , scheduler 发生严重错误,关闭 scheduler 等。
jobScheduler
用于部署 JobDetail 时调用。
jobUnscheduled
用于卸载 JobDetail 时调用。
triggerFinalized
当一个 Trigger 进入再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移出。
triggersPaused
Scheduler 掉哦那个这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话, triggerName 参数将为 null 。
triggersResumed
Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话, triggerName 参数将为 null 。
jobsPaused
当一个或一组 JobDetail 暂停时调用这个方法。
jobsResumed
当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组, jobName 参数将为 null 。
schedulerError
在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
schedulerStarted
当 Scheduler 开启时调用这个方法。
schedulerInStandbyMode
当 Scheduler 处于 StandBy 模式时调用这个方法。
schedulerShutdown
当 Scheduler 停止时调用这个方法。
schedulingDataCleared
当 Scheduler 中的数据被清除时调用这个方法。
# 参考
http://www.quartz-scheduler.org
https://www.bilibili.com/video/BV19t41127de