前言
还不会 Quartz?如果你还没有接触过Quartz,那么你可能错过了一个很棒的任务调度框架!Quartz 提供了一种灵活、可靠的方式来管理和执行定时任务,让咱们的定时任务更加优雅。本篇文章将为你介绍 Quartz 框架的核心概念、API 和实战技巧,让你轻松上手。也不用担心,作为过来人,我会把难懂的概念和术语解释清楚,让你看完本篇文章后,就知道该如何操作 Quartz。当然,本篇文章难免有不足之处,在此欢迎大家指出。那废话少说,下面我们开始吧!
什么是 Quartz?
Quartz:https://github.com/quartz-scheduler/quartz
官网:http://www.quartz-scheduler.org/
Quartz 是一个功能丰富的开源任务调度框架(job scheduling library)。从最小的独立的 Java 应用程序到最大的电子商务系统,它几乎都可以集成。Quartz 可用于创建简单或复杂的调度,以执行数十、数百个甚至数万个任务;这些任务被定义为标准 Java 组件,这些组件可以执行你想让他做的任何事情。Quartz 调度程序包括许多企业级特性,例如支持 JTA 事务(Java Transaction API,简写 JTA)和集群。
注意:Job == 任务
JTA,即 Java Transaction API,JTA 允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。
为什么学习 Quartz?
定时任务直接用 Spring 提供的 @Schedule 不行吗?为什么还要学习 Quartz?有什么好处?
是的,一开始我也是这么想的,但是某些场景,单靠 @Schedule 你就实现不了了。
比如我们需要对定时任务进行增删改查,是吧,@Schedule 就实现不了,你不可能每次新增一个定时任务都去手动改代码来添加吧。而 Quartz 就能够实现对任务的增删改查。当然,这只是 Quartz 的好处之一。
Quartz 的特性
运行时环境
- Quartz 可以嵌入另一个独立的应用程序中运行
- Quartz 可以在应用程序服务器(比如 Tomcat)中实例化,并参与 XA 事务(XA 是一个分布式事务协议)
- Quartz 可以作为一个独立程序运行(在其自己的Java虚拟机中),我们通过 RMI(Remote Method Invocation,远程方法调用)使用它
- Quartz 可以实例化为一个独立程序集群(具有负载平衡和故障转移功能),用于执行任务
任务的调度(Job Scheduling)
当一个触发器(Trigger)触发时,Job 就会被调度执行,触发器就是用来定义何时触发的(也可以说是一个执行计划),可以有以下任意的组合:
- 在一天中的某个时间(毫秒)
- 在一周中的某些日子
- 在一个月的某些日子
- 在一年中的某些日子
- 重复特定次数
- 重复直到特定的时间/日期
- 无限期重复
- 以延迟间隔重复
Job 由我们自己去命名,也可以组织到命名组(named groups)中。Trigger 也可以被命名并分组,以便在调度器(Scheduler)中更容易地组织它们。
Job 只需在 Scheduler 中添加一次,就可以有多个 Trigger 进行注册。
任务的执行(Job Execution)
- 实现了 Job 接口的 Java 类就是 Job,习惯称为任务类(Job class)。
- 当 Trigger 触发时,Scheduler 就会通知 0 个或多个实现了 JobListener 和 TriggerListener 接口的 Java 对象。当然,这些 Java 对象在 Job 执行后也会被通知到。
- 当 Job 执行完毕时,会返回一个码——JobCompletionCode,这个 JobCompletionCode 能够表示 Job 执行成功还是失败,我们就能通过这个 Code 来判断后续该做什么操作,比如重新执行这个 Job。
任务的持久化(Job Persistence)
- Quartz 的设计包括了一个 JobStore 接口,该接口可以为存储 Job 提供各种机制。
- 通过 JDBCJobStore,可以将 Job 和 Trigger 持久化到关系型数据库中。
- 通过 RAMJobStore,可以将 Job 和 Trigger 存储到内存中(优点就是无须数据库,缺点就是这不是持久化的)。
事务
- Quartz 可以通过使用 JobStoreCMT(JDBCJobStore的一个子类)参与 JTA 事务。
- Quartz 可以围绕任务的执行来管理 JTA 事务(开始并且提交它们),以便任务执行的工作自动发生在 JTA 事务中。
集群
- 故障转移
- 负载均衡
- Quartz 的内置集群功能依赖于 JDBCJobStore 实现的数据库持久性。
- Quartz 的 Terracotta 扩展提供了集群功能,而无需备份数据库。
监听器和插件
- 应用程序可以通过实现一个或多个监听器接口来捕获调度事件以监听或控制 Job / Trigger 的行为。
- 插件机制,我们可向 Quartz 添加功能,例如保存 Job 执行的历史记录,或从文件加载 Job 和 Trigger 的定义。
- Quartz 提供了许多插件和监听器。
初体验
引入 Quartz 依赖项
创建一个 Spring Boot 项目,然后引入如下依赖,就可以体验 Quartz 了。- <dependency>
- <groupId>org.quartz-scheduler</groupId>
- <artifactId>quartz</artifactId>
- <version>2.3.2</version>
- </dependency>
复制代码 示例
现在知道 Quartz 有这么几个概念,分别是 Job、Trigger、Scheduler。在它的设计实现上,分别是 Job 接口、JobDetail 接口、Trigger 接口、Scheduler 接口。除了 Job 接口的实现类需要我们自己去实现,剩下的都由 Quartz 实现了。

Quartz中的调度器(Scheduler)的主要作用就是调度 Job 和 Trigger 的执行。在Quartz中,Job代表需要执行的任务,Trigger代表触发Job执行的条件和规则。调度器会根据Trigger的配置来确定Job的执行时机。
下面的代码包含了一个 Scheduler 的实例对象,接着是调用 start 方法,最后调用 shutdown 方法。- import org.quartz.*;
- import org.quartz.impl.StdSchedulerFactory;
- public class QuartzTest {
- public static void main(String[] args) {
- try {
- // 从 Factory 中获取 Scheduler 实例
- Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
- // 开始并关闭
- scheduler.start();
- scheduler.shutdown();
- } catch (SchedulerException se) {
- se.printStackTrace();
- }
- }
- }
复制代码 一旦我们使用 StdSchedulerFactory.getDefaultScheduler() 获取 Scheduler 对象后,那么程序就会一直运行下去,不会终止,直到我们调用了 scheduler.shutdown() 方法才会停止运行。这是因为获取 Scheduler 对象后,就有许多线程在运行着,所以程序会一直运行下去。
与此同时,控制台会输出相应的日志:- 10:14:02.442 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
- 10:14:02.445 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
- 10:14:02.452 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
- 10:14:02.452 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
- 10:14:02.453 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
- 10:14:02.453 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
- Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
- NOT STARTED.
- Currently in standby mode.
- Number of jobs executed: 0
- Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
- Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
- 10:14:02.453 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
- 10:14:02.453 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
复制代码 从日志中也能看出 Quartz 的一些信息,比如版本、使用的线程池、使用的任务存储机制(这里默认是 RAMJobStore)等等信息。
我们想要执行任务的话,就需要把任务的代码放在 scheduler.start() 和 scheduler.shutdown() 之间。
QuartzTest:- import cn.god23bin.demo.quartz.job.HelloJob;
- import org.quartz.*;
- import org.quartz.impl.StdSchedulerFactory;
- // 这里导入了 static,下面才能直接 newJob, newTrigger
- import static org.quartz.JobBuilder.newJob;
- import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
- import static org.quartz.TriggerBuilder.newTrigger;
- public class QuartzTest {
- public static void main(String[] args) {
- try {
- // 从 Factory 中获取 Scheduler 实例
- Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
- // 开始并关闭
- scheduler.start();
- // 定义一个 Job(用JobDetail描述的Job),并将这个 Job 绑定到我们写的 HelloJob 这个任务类上
- JobDetail job = newJob(HelloJob.class)
- .withIdentity("job1", "group1") // 名字为 job1,组为 group1
- .build();
- // 现在触发任务,让任务执行,然后每5秒重复执行一次
- Trigger trigger = newTrigger()
- .withIdentity("trigger1", "group1")
- .startNow()
- .withSchedule(simpleSchedule()
- .withIntervalInSeconds(5)
- .repeatForever())
- .build();
- // 告知 Quartz 使用我们的 Trigger 去调度这个 Job
- scheduler.scheduleJob(job, trigger);
- // 为了在 shutdown 之前让 Job 有足够的时间被调度执行,所以这里当前线程睡眠30秒
- Thread.sleep(30000);
- scheduler.shutdown();
- } catch (SchedulerException | InterruptedException se) {
- se.printStackTrace();
- }
- }
- }
复制代码 HelloJob:实现 Job 接口,重写 execute 方法,实现我们自己的任务逻辑。- import org.quartz.Job;
- import org.quartz.JobExecutionContext;
- import org.quartz.JobExecutionException;
- import java.text.SimpleDateFormat;
- public class HelloJob implements Job {
- @Override
- public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- System.out.println("Hello Job!!! 时间:" + sdf.format(jobExecutionContext.getFireTime()));
- }
- }
复制代码 运行程序,输出如下信息:- 10:25:40.069 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=cn.god23bin.demo.quartz.job.HelloJob
- 10:25:40.071 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
- 10:25:40.071 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job1
- Hello Job!!! 时间:2023-03-28 10:25:40
- 10:25:45.066 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=cn.god23bin.demo.quartz.job.HelloJob
- 10:25:45.066 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
- 10:25:45.066 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job1
- Hello Job!!! 时间:2023-03-28 10:25:45
- # 省略后面输出的信息,都是一样的
复制代码 API 有哪些?
Quartz API 的关键接口如下:
- Scheduler :最主要的 API,可以使我们与调度器进行交互,简单说就是让调度器做事。
- Job :一个 Job 组件,你自定义的一个要执行的任务类就可以实现这个接口,实现这个接口的类的对象就可以被调度器进行调度执行。
- JobDetail : Job 的详情,或者说是定义了一个 Job。
- JobBuilder : 用来构建 JobDetail 实例的,然后这些实例又定义了 Job 实例。
- Trigger : 触发器,定义 Job 的执行计划的组件。
- TriggerBuilder : 用来构建 Trigger 实例。
Quartz 涉及到的设计模式:
<ul>Factory Pattern:- // 从 Factory 中获取 Scheduler 实例
- Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
复制代码 Builder Pattern:- JobDetail job = newJob(HelloJob.class)
- .withIdentity("job1", "group1") // 名字为 job1,组为 group1
- .build();
复制代码 这里的 newJob 方法是 JobBuilder 类中的一个静态方法,就是通过这个来构建 JobDetail 的。
[code]/** * Create a JobBuilder with which to define a JobDetail, * and set the class name of the Job to be executed. * * @return a new JobBuilder */public static JobBuilder newJob(Class |