新增:《Quartz快速入门 》

This commit is contained in:
2022-10-08 22:02:32 +08:00
parent 23043ac7c3
commit 448023ffaf
7 changed files with 394 additions and 0 deletions

View File

@@ -0,0 +1,394 @@
---
title: Quartz快速入门
author: 查尔斯
date: 2021/03/10 18:58
categories:
- 工具四海谈
tags:
- Java
- 作业调度
---
# Quartz快速入门
## 前言
::: tip 空巢青年们有一句话是什么来着?
孤独到极致是什么感觉:只有 QQ 邮箱祝你生日快乐。
:::
笔者刚才也去翻了翻 QQ 邮箱以往的邮件,的确每年都有来自它的祝福,而且好几年都没换个花样儿。
如果你在各类平台都填写过生日信息,这个场景你应该也不陌生:在你填写的生日那天,这些平台比你的男女朋友还准时的出现了,初次见它们的你,一下子感动的不知所措,难道这就是爱吗?
![202103101320507](../../../../../public/img/2021/03/10/202103101320507.png)
很可惜,不是。这份 “宠爱” 是 “海王” 的 “雨露均沾”,是 “鱼塘塘主” 的 “千篇一律”。它们都是提前设置好的,每天都会进行的定时任务,只要服务器没炸完,它们总会准时准点。
当然了,除了准点生日祝福,类似的场景一点也不少见。例如:每月 1 号的房贷,每月 9 号的花呗...
好啦,本篇,笔者就要带你认识一个 Java 领域中知名的开源作业调度(根据时间,执行作业)框架,有了它,你也可以做 “海王”。
![202103101321066](../../../../../public/img/2021/03/10/202103101321066.jpg)
<!-- more -->
## 简介
::: tip Quartz简介
Quartz 是 OpenSymphony 开源组织在 Job scheduler作业调度领域又一个开源项目它可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
从最小的独立应用程序到最大的电子商务系统Quartz 可以用来创建简单或复杂的时间表,以执行数十个、数百个甚至数万个工作。[1]
:::
![202103101321977](../../../../../public/img/2021/03/10/202103101321977.jpg)
## 简单使用
在使用 Quartz 框架前,我们需要先了解一下 Quartz 中的三个核心概念。
**Job作业/任务):** 需要执行的具体工作任务。
**Trigger触发器** 在特定的时间触发任务的执行。
**Scheduler调度器** 任务的实际执行者,负责粘合任务和触发器,但记得一个 Job 可以绑定到多个 Trigger但一个 Trigger 只能服务于一个 Job。
![202103101322176](../../../../../public/img/2021/03/10/202103101322176.png)
以 QQ 邮箱发送生日祝福为例,给今天过生日的用户发送生日祝福邮件就是 Job每天 9 点来执行就是 Trigger。了解完概念之后接下来和笔者一起先简单的使用一下。
### 引入依赖
首先,我们创建一个普通的 Maven 项目,然后引入 Quartz 的依赖。
::: tip 笔者说
Quartz 的 API 在 1.x 和 2.x 区别还是很大的2.x 系列的 API 采用的是 Domain Specific Language DSL风格也可以说是流式/链式风格fluent interface各种 builder 构建器。
:::
```xml
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
```
### 创建任务
创建一个类,实现 Job 接口,然后重写 Job 接口的 execute 方法。在 execute 方法中编写的就是需要执行的具体工作。
```java
public class SimpleJob implements Job {
// 需要执行的具体工作
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 输出当前线程和时间
System.out.println("SimpleJob:::" + Thread.currentThread().getName() + ":::" + SimpleDateFormat.getDateTimeInstance().format(new Date()));
}
}
```
在使用的时候,需要通过 JobDetail 来绑定好刚创建的类。
::: tip 笔者说
这个过程你没有觉得像创建线程一样吗实现Runnable 接口,然后通过 Thread 来绑定好 Runnable 实现类。
:::
```java
// 创建任务对象,绑定任务类并为其命名及分组
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "group1")
.build();
```
### 创建触发器
有了任务之后,我们还要为其准备好一个合适的触发器,既然是简单的使用,那我们先来创建一个每隔 3 秒就会触发的简单触发器。
```java
// 创建触发器对象,简单触发器每隔 3 秒执行一次任务
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
// 简单触发器
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
```
### 创建调度器
最后,我们创建调度器,利用调度器来粘合任务和触发器,这样任务就会在指定时间触发执行了。
```java
// 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 粘合任务和触发器
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
```
简单使用的完整代码如下:
```java
public class Test {
public static void main(String[] args) throws Exception {
// 创建任务对象,绑定任务类并为任务命名及分组
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "group1")
.build();
// 创建触发器对象,简单触发器每隔 3 秒执行一次任务
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
// 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 绑定任务和触发器
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
// 注意Quartz会开启子线程而为了防止主线程结束我们为主线程设置下休眠
Thread.sleep(60000);
}
}
```
![202103101322327](../../../../../public/img/2021/03/10/202103101322327.gif)
## CronTrigger
刚才我们简单使用了一下 Quartz定时任务按照触发器的设置跑起来了但这里的触发器 SimpleTrigger ,它只能实现这种周期性触发。而我们前言中提到的,每天指定时间或每月指定时间来触发,用它就有些捉襟见肘了。
所以 Quartz 中还有一种触发器叫CronTrigger而在 CronTrigger 中,可以通过 Cron 表达式来轻松的实现前言中的需求。
### Cron表达式
这个 Cron 表达式,**是由6~7个由空格分隔的时间元素组成第7个时间元素是可选的** 。
::: tip 笔者说
秒、分、时、日、月、周、年,这 7 个时间元素能精确的定位到某个时间点,当然一般定位到某年的情况是非常少的,所以 年 这个时间元素是可选的,可以不用指定。
:::
| 位置 | 时间元素 | 允许值 | 允许的特殊字符 |
| :--- | :------- | :------------------ | :------------- |
| 1 | 秒 | 0 ~ 59 | , - * / |
| 2 | 分 | 0 ~ 59 | , - * / |
| 3 | 时 | 0 ~ 23 | , - * / |
| 4 | 日 | 1 ~ 31 | , - * / L W |
| 5 | 月 | 1 ~ 12 或 JAN ~ DEC | , - * / |
| 6 | 周 | 1 ~ 7 或 SUN ~ SAT | , - * / L # |
| 7 | 年 | 空 或 1970 ~ 2099 | , - * / |
Cron 表达式到底长成啥样?先打个样儿:`0 0 8 14 2 ? 2021` ,它代表的是 2021 年 2 月 14 日上午 8 点会触发。
Cron 表达式的每个时间元素,都可以设置为具体值,这当然很简单。除此之外,笔者再介绍一下 Cron 表达式中允许出现的一些特殊字符。
**? 问号:** 仅用于 日 和 周 元素,表示不指定值,日和周这两个时间元素,必须有一个设置为 ? 。
::: tip 笔者说
之所以如此是因为每月的几号一定是星期几吗例如2021年的3月10日是星期三但4月10日就是星期六了... 。所以,**重点记住:日和周两个时间元素不能同时指定具体值,其中一方必须设置为 ? 就行了** 。
:::
**\* 星号:** 可用于所有时间元素表示每一个值。例0 0 8 * * ? 表示每天上午8点触发。
**, 逗号:** 可用于所有时间元素表示一个列表。例0 0 8,10,14 * * ? 表示每天上午8点上午10点下午14点触发。
**\- 中划线:** 可用于所有时间元素表示一个范围。例0 0 8 1-3 * ? 表示每月1日到3日的上午8点触发。
**/ 斜杠:** 可用于所有时间元素x/yx代表起始值y代表值的增量。例0 0 8/2 * * ? 表示每天上午8点开始每隔两个小时触发一次。
**L** 仅用于 日 和 周 元素表示对应时间元素上允许的最后一个值Last。例10 30 8 L * ? 表示每月最后一天的上午8点30分触发。例20 30 8 ? * 3L 表示每月最后一周的星期二触发。
::: tip 笔者说
由于国外星期是从星期日开始计算星期日的数字表示是1因此示例中 3L 的3代表的是星期二。
:::
**W** 仅用于 日 元素表示指定日期的最近工作日。例0 0 8 10W * ? 表示每月10号上午8点触发但如果10号不是工作日那就找最近的工作日。10号是周六那就找周五10号是周日那就找周一。
有一种特殊的情况是设置的是1W如果1号不是工作日哪怕这时候1号是周六它也不会去找上月的周五来执行而是找本月的周一去执行。
::: tip 笔者说
很多定时任务都需要在工作日来执行所以有这么一个符号表示这含义无可厚非。L和W在 日 元素上还可以一起使用,例:* * * LW * ? 表示本月最后一个工作日触发。
:::
**#** 仅用于 周 元素x#yy代表对应月份中的第几周x代表第几周的第几天。例0 0 8 ? * 3#2每月第2周的星期二上午8点触发。
头晕了吧?笔者再提供几个常用的 Cron 表达式给你。
| Cron 表达式 | 含义 |
| :------------------ | :------------------------------- |
| 0 0 2 1 * ? | 每月1日的凌晨2点 |
| 0 15 10 ? * MON-FRI | 周一到周五每天上午10:15 |
| 0 0/30 9-17 * * ? | 朝九晚五工作时间内每半小时 |
| 0 15 10 L * ? | 每月最后一日的上午10:15 |
| 0 15 10 ? * 6L | 每月的最后一个星期五上午10:15 |
| 0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15 |
| 0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44 |
另外笔者再说一点Cron 表达式也不仅仅是用于 Quartz 框架,很多与定时任务有关的工具都需要用到 Cron 表达式,所以说熟练掌握编写 Cron 表达式很重要。
别害怕,笔者再教你一招,市面上有很多在线 Cron 表达式生成器(参考资料 [3] 就是笔者推荐给你的生成器),通过可视化的选择,就可以自动生成 Cron 表达式,而且还可以提供测试执行效果。
![202103101322366](../../../../../public/img/2021/03/10/202103101322366.png)
### 案例实现
认识完了 Cron 表达式,我们再用 Cron 表达式实现一下刚才的案例效果吧。
```java
public class Test2 {
public static void main(String[] args) throws Exception {
// 创建任务对象,绑定任务类并为任务命名及分组
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "group1")
.build();
// 创建触发器对象,通过 Cron 表达式指定每隔 3 秒执行一次任务
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * ? * *"))
.build();
// 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 绑定任务和触发器
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
// Quartz会开启子线程而为了防止主线程结束我们为主线程设置下休眠
Thread.sleep(60000);
}
}
```
## Spring Boot整合
Spring 作为 Java 界的顶流框架,怎么可能对 Quartz 没有提供整合呢?但是原生 Spring 整合 Quartz ,配置文件让人头疼,所以咱们使用 Spring Boot 来实现一下 Quartz 整合。
### 引入依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
```
### 创建任务
```java
@Component
public class SimpleJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 输出当前线程和时间
System.out.println("SimpleJob:::" + Thread.currentThread().getName() + ":::" + SimpleDateFormat.getDateTimeInstance().format(new Date()));
}
}
```
之前是实现 Job 接口,这回是继承 QuartzJobBean其实没有区别。因为 QuartzJobBean 实现自 Job 接口。
```java
public abstract class QuartzJobBean implements Job {
....
}
```
### 编写配置
创建一个配置类,为任务和触发器提供两个 Bean 配置,记得别忘了对触发器指定任务。
```java
@Configuration
public class SchedulerConfig {
// 任务bean
@Bean
public JobDetail simpleJobBean() {
return JobBuilder.newJob(SimpleJob.class)
.withIdentity("job1", "group1")
// Jobs added with no trigger must be durable.
.storeDurably()
.build();
}
// 触发器bean
@Bean
public Trigger simpleTrigger() {
return TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
// 指定具体任务
.forJob(simpleJobBean())
.build();
}
}
```
## Spring Task
Spring 从 3.x 开始提供了 Spring Task可以理解为是一种轻量级的 Quartz。接下来我们也在 Spring Boot 中体验一下。
创建好的 Spring Boot 项目,不需要你添加任何额外依赖,在任何一个 Spring 组件中,创建普通方法编写好任务(可以写多个),再通过 `@Scheduled` 注解为其指定好 Cron 表达式,一个绑定了触发器的任务就新鲜出炉了。
```java
@Component
public class TestTask {
@Scheduled(cron = "0/3 * * ? * *")
public void task1() {
System.out.println("当前时间是:" + LocalDateTime.now());
}
}
```
最后,还需要在启动类/任务类上使用 @EnableScheduling 启用一下任务调度功能,是不是挺简单的?
```java
@EnableScheduling // 启用任务调度
@SpringBootApplication
public class DemoSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringbootApplication.class, args);
}
}
```
## 参考资料
[1]Quartz 官网http://www.quartz-scheduler.org/
[2]Quartz GitHub地址https://github.com/quartz-scheduler/quartz
[3]MaTools 在线Cron表达式生成器https://www.matools.com/cron
## 后记
**C** 好了Quartz 定时器的入门介绍到这儿就结束了。入门介绍中,咱们仅仅是介绍了一下 Quartz 的 RAMJobStore 模式,即 Quartz 将 Trigger 和 Job 存储在内存中,内存中存取自然是很快的,但缺陷就是内存无法持久化存储。
所以, Quartz 还有一种 JDBC JobStore 模式,即 Quartz 利用 JDBC 将 Trigger 和 Job 存储到数据库中,想要实现 Quartz 的集群也得先完成这一步进阶。
当然了Java 中还有很多定时器的实现方案。例如java.util.Timer、ScheduledThreadPoolExecutor基于线程池设计的定时任务类、延时队列等有兴趣可以先去了解了解。不然就等后面笔者有时间再介绍吧。

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB