共计 22135 个字符,预计需要花费 56 分钟才能阅读完成。
前言
还不会 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 波及到的设计模式:
-
Factory Pattern:
// 从 Factory 中获取 Scheduler 实例 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
-
Builder Pattern:
JobDetail job = newJob(HelloJob.class) .withIdentity("job1", "group1") // 名字为 job1,组为 group1 .build();
这里的
newJob
办法是 JobBuilder 类中的一个静态方法,就是通过这个来构建 JobDetail 的。/** * Create a JobBuilder with which to define a <code>JobDetail</code>, * and set the class name of the <code>Job</code> to be executed. * * @return a new JobBuilder */ public static JobBuilder newJob(Class <? extends Job> jobClass) {JobBuilder b = new JobBuilder(); b.ofType(jobClass); return b; } /** * Produce the <code>JobDetail</code> instance defined by this * <code>JobBuilder</code>. * * @return the defined JobDetail. */ public JobDetail build() {JobDetailImpl job = new JobDetailImpl(); job.setJobClass(jobClass); job.setDescription(description); if(key == null) key = new JobKey(Key.createUniqueName(null), null); job.setKey(key); job.setDurability(durability); job.setRequestsRecovery(shouldRecover); if(!jobDataMap.isEmpty()) job.setJobDataMap(jobDataMap); return job; }
同样,构建 Trigger 对象是应用 TriggerBuilder 类以及 SimpleScheduleBuilder 类构建的,Schedule 次要是一个工夫安排表,就是定义何时执行工作的时间表。
- 当然,除了下面说的两种设计模式外,还有其余的设计模式,这里就不细说了。比方单例模式,观察者模式。
简略了解 Job、Trigger、Scheduler
每天中午 12 点唱、跳、Rap、篮球
- Job:唱、跳、Rap、篮球
- Trigger:每天中午 12 点为一个触发点
- Scheduler:本人,我本人调度 Trigger 和 Job,让本人每天中午 12 点唱、跳、Rap、篮球
对于 Job
Job 接口源码:
package org.quartz;
public interface Job {void execute(JobExecutionContext context) throws JobExecutionException;
}
当该工作的 Trigger 触发时,那么 Job 接口的 execute
办法就会被 Scheduler 的某一个工作线程调用。JobExecutionContext 对象就会作为参数传入这个办法,该对象就提供 Job 实例的一些对于工作运行时的信息。
咱们晓得,写完一个 Job 类后,须要将定义一个 JobDetail 绑定到咱们的 Job 类:
// 定义一个 Job(用 JobDetail 形容的 Job),并将这个 Job 绑定到咱们写的 HelloJob 这个工作类上
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1") // 名字为 job1,组为 group1
.build();
在这个过程中,有许多属性是能够设置的,比方 JobDataMap,这个对象可能存储一些工作的状态信息数据,这个前面说。
Trigger 对象用于触发工作的执行。当咱们想要调度某个工作时,能够实例化 Trigger 并设置一些咱们想要的属性。Trigger 也能够有一个与之相干的 JobDataMap,这对于特定的触发器触发时,传递一些参数给工作是很有用。Quartz 有几种不同的 Trigger 类型,但最罕用的类型是 SimpleTrigger 和 CronTrigger。
对于 SimpleTrigger 和 CronTrigger
如果咱们想要在某个工夫点执行一次某个工作,或者想要在给定工夫启动一个工作,并让它反复 N 次,执行之间的提早为 T,那么就能够应用 SimpleTrigger。
如果咱们想依据相似日历的时间表来执行某个工作,例如每天晚上凌晨 4 点这种,那么就能够应用 CronTrigger。
为什么会设计出 Job 和 Trigger 这两个概念?
在官网上是这样说的:
Why Jobs AND Triggers? Many job schedulers do not have separate notions of jobs and triggers. Some define a‘job’as simply an execution time (or schedule) along with some small job identifier. Others are much like the union of Quartz’s job and trigger objects. While developing Quartz, we decided that it made sense to create a separation between the schedule and the work to be performed on that schedule. This has (in our opinion) many benefits.
For example, Jobs can be created and stored in the job scheduler independent of a trigger, and many triggers can be associated with the same job. Another benefit of this loose-coupling is the ability to configure jobs that remain in the scheduler after their associated triggers have expired, so that that it can be rescheduled later, without having to re-define it. It also allows you to modify or replace a trigger without having to re-define its associated job.
简而言之,这有许多益处:
- 工作能够独立于触发器,它能够在调度器中创立和存储,并且许多触发器能够与同一个工作关联。
- 这种松耦合可能配置工作,在其关联的触发器已过期后依然保留在调度器中,以便之后重新安排,而无需从新定义它。
- 这也容许咱们批改或替换触发器的时候无需从新定义其关联的工作。
Job 和 Trigger 的身份标识(Identities)
在下面的代码中咱们也看到了,Job 和 Trigger 都有一个 withIdentity
办法。
JobBuilder 中的 withIdentity
办法:
private JobKey key;
public JobBuilder withIdentity(String name, String group) {key = new JobKey(name, group);
return this;
}
TriggerBuilder 中的 withIdentity
办法:
private TriggerKey key;
public TriggerBuilder<T> withIdentity(String name, String group) {key = new TriggerKey(name, group);
return this;
}
当 Job 和 Trigger 注册到 Scheduler 中时,就会通过这个 key 来标识 Job 和 Trigger。
工作和触发器的 key(JobKey 和 TriggerKey)容许将它们放入「组」中,这有助于将工作和触发器进行分组,或者说分类。而且工作或触发器的 key 的名称在组中必须是惟一的,他们残缺的 key(或标识符)是名称和组的组合。从下面的代码中也能够看到,构造方法都是两个参数的,第一个参数是 name,第二个参数是 group,结构进去的就是整个 key 了。
对于 JobDetail 和 JobDataMap
咱们通过写一个 Job 接口的实现类来编写咱们期待执行的工作,而 Quartz 须要晓得你将哪些属性给了 Job。那 Quartz 是如何晓得的呢?Quartz 就是通过 JobDetail 晓得的。
留神,咱们向 Scheduler 提供了一个 JobDetail 实例,Scheduler 就能晓得要执行的是什么工作,只需在构建 JobDetail 时提供工作的类即可(即 newJob(HelloJob.class)
)。
每次调度程序执行工作时,在调用其 execute
办法之前,它都会创立该工作类的一个新实例。执行实现工作后,对工作类实例的援用将被抛弃,而后该实例将被垃圾回收。
那咱们如何为作业实例提供属性或者配置?咱们如何在执行的过程中追踪工作的状态?这两个问题的答案是一样的:要害是 JobDataMap,它是 JobDetail 对象的一部分。
JobDataMap 能够用来保留任意数量的(可序列化的)数据对象,这些对象在工作实例执行时须要应用的。JobDataMap 是 Java 中 Map 接口的一个实现,具备一些用于存储和检索原始类型数据的办法。
示例:
PlayGameJob:
public class PlayGameJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {JobKey key = context.getJobDetail().getKey();
// 获取 JobDataMap,该 Map 在创立 JobDetail 的时候设置的
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String gameName = jobDataMap.getString("gameName");
float gamePrice = jobDataMap.getFloat("gamePrice");
System.out.println("我玩的" + gameName + "才破费了我" + gamePrice + "块钱");
}
}
接着应用 usingJobData
设置该工作须要的数据,最初调度该工作:
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
JobDetail job = newJob(PlayGameJob.class)
.withIdentity("myJob", "group1")
.usingJobData("gameName", "GTA5")
.usingJobData("gamePrice", 55.5f)
.build();
Trigger trigger = newTrigger()
.withIdentity("myJob", "group1")
.build();
scheduler.scheduleJob(job, trigger);
Thread.sleep(10000);
scheduler.shutdown();
控制台输入:
14:18:43.295 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=cn.god23bin.demo.quartz.job.PlayGameJob
14:18:43.299 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
14:18:43.300 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
我玩的 GTA5 才破费了我 55.5 块钱
当然,也能够这样写:
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("gameName", "GTA5");
jobDataMap.put("gamePrice", 55.5f);
JobDetail job = newJob(PlayGameJob.class)
.withIdentity("myJob", "group1")
.usingJobData(jobDataMap)
.build();
之前还说过,Trigger 也是能够有 JobDataMap 的。当你有这种状况,就是 在调度器中曾经有一个 Job 了,然而想让不同的 Trigger 去触发执行这个 Job,每个不同的 Trigger 触发时,你想要有不同的数据传入这个 Job,那么就能够用到 Trigger 携带的 JobDataMap 了。
噢对了!对于咱们下面本人写的 PlayGameJob,还能够换一种写法,不须要应用通过 context.getJobDetail().getJobDataMap()
获取 JobDataMap 对象后再依据 key 获取对应的数据,间接在这个工作类上写上咱们须要的属性,提供 getter 和 setter 办法,这样 Quartz 会帮咱们把数据赋值到该对象的属性上。
PlayGameJob:
// 应用 Lombok 的注解,帮咱们生成 getter 和 setter 办法以及无参的构造方法
@Data
@NoArgsConstructor
public class PlayGameJob implements Job {
// Quartz 会把数据注入到工作类定义的属性上,间接用就能够了
private String gameName;
private float gamePrice;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {JobKey key = context.getJobDetail().getKey();
System.out.println("我玩的" + gameName + "才破费了我" + gamePrice + "块钱");
}
}
这样的成果,就是缩小了 execute
办法中的代码量。
如何了解 Job 实例?
这个的确会有一些困惑,比方一开始说的 Job 接口,还有 JobDetail 接口,而且为什么会说成 JobDetail 对象是 Job 的实例?是吧。
想要了解,举个例子:
当初咱们写了一个发送音讯的 Job 实现类——SendMessageJob。
接着咱们创立了多个 JobDetail 对象,这些对象都有不同的定义,比方有叫做 SendMessageToLeBron 的 JobDetail、有 SendMessageToKobe 的 JobDetail,这两个 JobDetail 都有它各自的 JobDataMap 传递给咱们的 Job 实现类。
当 Trigger 触发时,Scheduler 将加载与其关联的 JobDetail(工作定义),并通过 Scheduler 上配置的 JobFactory 实例化它所援用的工作类(SendMessageJob)。默认的 JobFactory 只是在工作类上调用 newInstance(),而后尝试在与 JobDataMap 中键的名称匹配的类中的属性名,进而调用 setter 办法将 JobDataMap 中的值赋值给对应的属性。
在 Quartz 的术语中,咱们将每个 JobDetail 对象称为「Job 定义或者 JobDetail 实例」,将每个正在执行的工作称为「Job 实例或 Job 定义的实例」。
个别状况下,如果咱们只应用「工作」这个词,咱们指的是一个名义上的工作,简而言之就是咱们要做的事件,也能够指 JobDetail。当咱们提到实现 Job 接口的类时,咱们通常应用术语「工作类」。
两个须要晓得的注解
JobDataMap 能够说是工作状态的数据,这里的数据和并发也有点关系。Quartz 提供了几个注解,这几个注解会影响到 Quartz 在这方面的动作。
@DisallowConcurrentExecution 注解是用在工作类上的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DisallowConcurrentExecution {}
DisallowConcurrentExecution
这个注解的作用就是告知 Quartz 这个工作定义的实例(JobDetail 实例)不能并发执行,举个例子,就下面的 SendMessageToLeBron 的 JobDetail 实例,是不能并发执行的,但它是能够与 SendMessageToKobe 的 JobDetail 的实例同时执行。须要留神的是它指的不是工作类的实例(Job 实例)。
@PersistJobDataAfterExecution 注解也是用在工作类上的。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PersistJobDataAfterExecution {}
@PersistJobDataAfterExecution
这个注解的作用是告知 Quartz 在 execute
办法胜利实现后更新 JobDetail 的 JobDataMap 的存储正本(没有引发异样),以便同一工作的下一次执行能接管更新后的值,而不是最后存储的值。
与 @DisallowConcurrentExecution
注解一样,这是实用于工作定义实例(JobDetail 实例),而不是工作类实例(Job 实例)。
对于 Trigger
咱们须要理解 Trigger 有哪些属性能够去设置,从最开始的初体验中,咱们给 Trigger 设置了一个 TriggerKey 用来标识这个 Trigger 实例,实际上,它还有好几个属性给咱们设置。
共用的属性
下面也说过 Trigger 有不同的类型(比方 SimpleTrigger 和 CronTrigger),不过,即便是不同的类型,也有雷同的属性。
- jobKey:作为 Trigger 触发时应执行的工作的标识。
- startTime:记录下首次触发的工夫;对于某些触发器,它是指定触发器应该在何时触发。
- endTime:触发器不再失效的工夫
- …
还有更多,上面说一些重要的。
priority
优先级,这个属性能够设置 Trigger 触发的优先级,值越大则优先级越高,就优先被触发执行工作。当然这个是在 同一时间调度下才会有这个优先级比拟的,如果你有一个 A 工作在 6 点触发,有一个 B 工作在 7 点触发,即便你的 B 工作的优先级比 A 工作的高,也没用,6 点 的 A 工作总是会比 7 点 的 B 工作先触发。
misfireInstruction
misfire instruction,错失触发指令,也就是说当某些状况下,导致触发器没有触发,那么就会执行这个指令,默认是一个「智能策略」的指令,它可能依据不同的 Trigger 类型执行不同的行为。
当 Scheduler 启动的时候,它就会先搜查有没有错过触发的 Trigger,有的话就会基于 Trigger 配置的错失触发指令来更新 Trigger 的信息。
calendar
Quartz 中也有一个 Calendar 对象,和 Java 自带的不是同一个。
在设置 Trigger 的时候,如果咱们想排除某些日期工夫,那么就能够应用这个 Calendar 对象。
SimpleTrigger
如果咱们想在特定的工夫点执行一次工作,或者在特定的时刻执行一次,接着定时执行,那么 SimpleTrigger 就能满足咱们的需要。
SimpleTrigger 蕴含了这么几个属性:
- startTime:开始工夫
- endTime:完结工夫
- repeatCount:反复次数,能够是 0,正整数,或者是一个常量
SimpleTrigger.REPEAT_INDEFINITELY
- repeatInterval:反复的工夫距离,必须是 0,或者是一个正的长整型的值(long 类型的值),示意毫秒,即多少毫秒后反复触发
SimpleTrigger 的实例对象能够由 TriggerBuilder 和 SimpleScheduleBuilder 来创立。
示例
上面举几个例子:
- 构建一个给定时刻触发工作的 Trigger,不会反复触发:
// 明天 22 点 30 分 0 秒
Date startAt = DateBuilder.dateOf(22, 30, 0);
// 通过强转构建一个 SimpleTrigger
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startAt) // 开始的日期工夫
.forJob("job1", "group1") // 通过 job 的 name 和 group 辨认 job
.build();
- 构建一个给定时刻触发工作的 Trigger,每十秒反复触发十次:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(startAt) // 如果没有给定开始工夫,那么就默认当初开始触发
.withSchedule(SimpleScheduleBuilder.simpleSchedule() // 通过 simpleSchedule 办法构建 SimpleTrigger
.withIntervalInSeconds(10)
.withRepeatCount(10)) // 每隔 10 秒反复触发 10 次
.forJob(job) // 通过 JobDetail 自身来辨认 Job
.build();
- 构建一个给定时刻触发工作的 Trigger,在将来五分钟内触发一次:
Date futureDate = DateBuilder.futureDate(5, DateBuilder.IntervalUnit.MINUTE);
JobKey jobKey = job.getKey();
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate) // 应用 DateBuilder 创立一个将来的工夫
.forJob(jobKey) // 通过 jobKey 辨认 job
.build();
- 构建一个给定时刻触发工作的 Trigger,而后每五分钟反复一次,直到早晨 22 点:
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(DateBuilder.dateOf(22, 0, 0))
.build();
- 构建一个给定时刻触发工作的 Trigger,而后每下一个小时整点触发,而后每 2 小时反复一次,始终反复上来:
trigger = newTrigger()
.withIdentity("trigger8") // 这里没有指定 group 的话,那么 "trigger8" 就会在默认的 group 中
.startAt(DateBuilder.evenHourDate(null)) // 下一个整点时刻 (分秒为零 ("00:00"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.forJob("job1", "group1")
.build();
错失触发指令
比方我在要触发工作的时候,机器宕机了,当机器从新跑起来后怎么办呢?
当 Trigger 错失触发工夫去触发工作时,那么 Quartz 就须要执行 Misfire Instruction
,SimpleTrigger 有如下的以常量模式存在的 Misfire 指令:
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
- MISFIRE_INSTRUCTION_FIRE_NOW
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
咱们晓得,所有的 Trigger,SimpleTrigger 也好,CronTrigger 也好,不论是什么类型,都有一个 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY
能够应用,如果咱们应用这个指令,那么 SimpleTrigger 就会动静地在下面 6 个指令中抉择,抉择的行为取决于咱们对于 SimpleTrigger 的设置。
当咱们在构建 Trigger 的时候,就能够给 Trigger 设置上 Misfire 指令:
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever()
.withMisfireHandlingInstructionNextWithExistingCount())
.build();
CronTrigger
应用 CronTrigger,咱们能够指定触发工作的 工夫安顿 (schedule),例如, 每周五中午 ,或 每个工作日和上午 9:30,甚至 每周一,周三上午 9:00 到上午 10:00 之间每隔 5 分钟 和 1 月的星期五。
CronTrigger 也有一个 startTime,用于指定打算何时失效,以及一个(可选的)endTime,用于指定何时进行这个工作的执行。
cron 表达式
cron 表达式有 6 位,是必须的,从左到右别离示意:秒、分、时、日、月、周
。
当然也能够是 7 位,最初一位就是年(可选项):秒、分、时、日、月、周、年
。
取值阐明:失常意识,秒分 都是 0 – 59,时 则是 0 – 23,日 则是 1 – 31,月 在这边则是 0-11,周 则是 1 – 7(这里的 1 指的是星期日)。年 则只有 1970 – 2099
月份能够指定为 0 到 11 之间的值,或者应用字符串 JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV 和 DEC
星期几能够应用字符串 SUN,MON,TUE,WED,THU,FRI 和 SAT 来示意
具体可参考这里:简书 -Cron 表达式的具体用法
Cron 生成工具:cron.qqe2.com/
示例
- 构建一个 Trigger,每天上午 8 点到下午 5 点之间每隔一分钟触发一次:
Trigger trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/1 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
- 构建一个 Trigger,每天上午 10:42 触发:
JobKey myJobKey = job.getKey();
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
- 构建一个触发器,该触发器将在星期三上午 10 点 42 分在 TimeZone 中触发,而不是零碎的默认值:
JobKey myJobKey = job.getKey();
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder
.weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42)
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles")))
.forJob(myJobKey)
.build();
错失触发指令
对于 CronTrigger,它有 3 个 Misfire 指令
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
- MISFIRE_INSTRUCTION_DO_NOTHING
- MISFIRE_INSTRUCTION_FIRE_NOW
咱们在构建 Tirgger 的时候就能够给这个 Trigger 指定它的 Misfire 指令:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?")
.withMisfireHandlingInstructionFireAndProceed())
.forJob("myJob", "group1")
.build();
对于 CRUD
存储定时工作
存储定时工作,不便后续应用,通过 Scheduler 的 addJob
办法
void addJob(JobDetail jobDetail, boolean replace) throws SchedulerException;
该办法会增加一个没有与 Trigger 关联的 Job 到 Scheduler 中,而后这个 Job 是处于休眠的状态直到它被 Trigger 触发进行执行,或者应用 Scheduler.triggerJob()
指定了这个 Job,这个 Job 才会被唤醒。
JobDetail job1 = newJob(MyJobClass.class)
.withIdentity("job1", "group1")
.storeDurably() // Job 必须被定义为 durable 的
.build();
scheduler.addJob(job1, false);
更新已存储的定时工作
addJob
办法的第二个参数 -replace,就是用在这里,设置为 true,那么就是更新操作。
JobDetail job1 = newJob(MyJobClass.class)
.withIdentity("job1", "group1")
.build();
// store, and set overwrite flag to 'true'
scheduler.addJob(job1, true);
更新触发器
替换 已存在的 Trigger:
// 定义一个新的 Trigger
Trigger trigger = newTrigger()
.withIdentity("newTrigger", "group1")
.startNow()
.build();
// 让 Scheduler 依据 Key 去移除旧的 Trigger, 而后将新的 Trigger 放上去
scheduler.rescheduleJob(new TriggerKey("oldTrigger", "group1"), trigger);
更新 已存在的 Trigger:
// 依据 Key 检索已存在的 Trigger
Trigger oldTrigger = scheduler.getTrigger(new TriggerKey("oldTrigger", "group1");
// 获取 TriggerBuilder
TriggerBuilder tb = oldTrigger.getTriggerBuilder();
// 更新触发动作,并构建新的 Trigger
// (other builder methods could be called, to change the trigger in any desired way)
Trigger newTrigger = tb.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)
.build();
// 从新用新的 Trigger 调度 Job
scheduler.rescheduleJob(oldTrigger.getKey(), newTrigger);
勾销定时工作
应用 Scheduler 的 deleteJob
办法,入参为一个 TriggerKey,即 Trigger 标识,这样就能勾销特定的 Trigger 去触发对应的工作,因为一个 Job 可能有多个 Trigger。
scheduler.unscheduleJob(new TriggerKey("trigger1", "group1"));
应用 Scheduler 的 deleteJob
办法,入参为一个 JobKey,即 Job 标识,这样就能删除这个 Job 并勾销对应的 Trigger 进行触发。
scheduler.deleteJob(new JobKey("job1", "group1"));
获取调度器中的所有定时工作
思路:通过 scheduler 获取工作组,而后遍历工作组,进而遍历组中的工作。
// 遍历每一个工作组
for(String group: scheduler.getJobGroupNames()) {
// 遍历组中的每一个工作
for(JobKey jobKey : scheduler.getJobKeys(GroupMatcher.groupEquals(group))) {System.out.println("通过标识找到了 Job,标识的 Key 为:" + jobKey);
}
}
获取调度器中的所有触发器
思路:同上。
// 遍历每一个触发器组
for(String group: scheduler.getTriggerGroupNames()) {
// 遍历组中的每一个触发器
for(TriggerKey triggerKey : scheduler.getTriggerKeys(GroupMatcher.groupEquals(group))) {System.out.println("通过标识找到了 Trigger,标识的 Key 为:" + triggerKey);
}
}
获取某一个定时工作的触发器列表
因为一个工作能够有多个触发器,所以是获取触发器列表。
List<Trigger> jobTriggers = scheduler.getTriggersOfJob(new JobKey("jobName", "jobGroup"));
总结
想要应用 Quartz,那么就引入它的依赖。
从应用上来说:
- 对于一个工作,咱们能够写一个工作类,即实现了 Job 接口的 Java 类,并重写
execute
办法。接着须要一个 JobDetail 来形容这个 Job,或者说把这个 Job 绑定到这个 JobDetail 上。而后咱们就须要一个 Trigger,这个 Trigger 是用来示意何使触发工作的,能够说是一个执行打算,在何时如何触发,Trigger 是有好几种类型的,目前罕用的就是 SimpleTrigger 和 CronTrigger。最初,在把 JobDetail 和 Trigger 扔给 Scheduler,让它去组织调度; - 对于一个触发器,它有对应的类型,以及对应的 Misfire 指令,个别在创立 Trigger 的时候,就指定上这些信息;
- 对于它们的 CRUD,都是应用调度器进行操作的,比方往调度器中增加工作,更新工作。
从 Quartz 的设计上来说,它有波及到多种设计模式,包含 Builder 模式,Factory 模式等等。
以上,便是本篇文章的内容,咱们下期再见!
参考:http://www.quartz-scheduler.org/
最初的最初
心愿各位屏幕前的 靓仔靓女们
给个三连!你轻轻地点了个赞,那将在我的心里世界削减一颗亮堂而夺目的星!
咱们下期再见!