Quartz 的基本应用
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
配置、资源 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 中的数据被清除时调用这个方法。
参考
著作権声明
本記事のリンク:https://www.chinmoku.cc/dev/java/extension/quartz-tutorial/
本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。