一、什么是 Quartz

Quartz 是一个完全由 Java 编写的开源作业调度框架,是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目。Quartz 可以用来执行定时任务,类似于 java.util.Timer。

Quartz 核心概念

  • Job & JobDetail: 定义任务具体执行的逻辑
  • Trigger:触发器,定义任务执行的方式、间隔
  • Scheduler:任务调度器,所有的任务都是从这里开始。

Job 表示一个工作,要执行的具体内容。此接口中只有一个方法

1
void execute(JobExecutionContext context)

JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。

简单类比一下,假设当你在 12306 抢到票后,你需要在 30 分钟内完成付款,否则订单将会被取消。对于这个过程来说后台就会插入一条待支付的 Job,到了 30min 后就会执行这个 Job,去判断你是否支付,未支付就会取消此次订单;而当你支付完成之后,后台拿到支付回调后就会再插入一条待使用的 Job,触发时间为火车票上的出发日期,到了出发时间后就会执行这个 Job,判断是否被使用。而这些不同的Job,都需要一个能够实现触发任务去执行的触发器(Trigger),最后使用调度容器(Schedule)来调度这两者。

在使用 Scheduler 之前,需要实例化 , scheduler 实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。

Scheduler 的生命期,从 SchedulerFactory 创建它时开始,到 Scheduler 调用 shutdown()方法时结束;Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。但是,Scheduler 只有在调用 start()方法后,才会真正地触发 trigger(即执行 job)

maven 坐标

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>

二、Quartz 入门案例

1、创建 QuartzTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* @author: ShiGuang
* @create: 2021-05-30 23:02
* @description:
*/

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzTest {
public static void main(String[] args) {
try {
//创建一个JobDetail
JobDetail jobDetail = JobBuilder.newJob(HelloQuartz.class)
//定义name和group 给触发器一些属性 比如名字,组名。
.withIdentity("SendMessageJbo1", "SendMessageGroup1")
//job需要传递的内容 具体job传递参数。
.usingJobData("name", "ZhangSan")
.build();
//定义一个Trigger
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("SendMessageTrigger1", "SendMessageTriggerGroup1")
//加入 scheduler之后立刻执行 立刻启动
.startNow()
//定时 ,每隔1秒钟执行一次 以某种触发器触发。
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
//重复执行
.repeatForever()).build();
//创建scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
// Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)
scheduler.start(); //运行一段时间后关闭
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Scheduler调用shutdown()方法时结束
scheduler.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}

2、创建 HelloQuartz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;

/**
* @author: ShiGuang
* @create: 2021-05-30 23:07
* @description:
*/
public class HelloQuartz implements Job {

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
String name = detail.getJobDataMap().getString("name");
System.out.println("my job name is " + name + " at " + new Date());
}
}

三、spring 整合 Quartz

  1. 创建 maven 工程 quartzdemo,打包方式为 war,导入 jar 包
  2. 自定义一个 Job
  3. 提供 Spring 配置文件 application-jobs.xml,配置自定义 Job、任务描述、触发器、调度工厂等
  4. web.xml 中定义
  5. 启动 tomcat 完成测试

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>8080</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

测试用 Job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.atguigu;

import java.util.Date;

/**
* @author: ShiGuang
* @create: 2021-05-31 18:20
* @description:
*/
public class JobDemo {
public void run(){
// 测试打印当前时间
System.out.println(new Date());
}
}

application-jobs.xml

Spring 配置文件:在 application-jobs.xml 中配置自定义 Job、任务描述、触发器、调度工厂等

  1. 创建 JobDetail 对象,作用是负责通过反射调用指定的 Job,注入目标对象,注入目标方法
  2. 注册一个触发器,指定任务触发的时间
  3. 注册一个统一的调度工厂,通过这个调度工厂调度任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 注册自定义Job -->
<bean id="jobDemo" class="com.atguigu.JobDemo"></bean>

<!-- 1.创建JobDetail对象,作用是负责通过反射调用指定的Job,注入目标对象,注入目标方法 -->
<bean id="jobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 注入目标对象 -->
<property name="targetObject" ref="jobDemo"/>
<!-- 注入目标方法 -->
<property name="targetMethod" value="run"/>
</bean>

<!-- 2.注册一个触发器,指定任务触发的时间 -->
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!-- 注入JobDetail -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 指定触发的时间,基于Cron表达式(0/10表示从0秒开始,每10秒执行一次) -->
<property name="cronExpression">
<value>0/10 * * * * ?</value>
</property>
</bean>

<!-- 3.注册一个统一的调度工厂,通过这个调度工厂调度任务 -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- 注入多个触发器 -->
<property name="triggers">
<list>
<ref bean="myTrigger"/>
</list>
</property>
</bean>

</beans>

web.xml

启动 web,加载 spring 容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-jobs.xml</param-value>
</context-param>
</web-app>

启动 tomcat

控制台查看打印效果

Mon May 31 18:46:20 CST 2021
Mon May 31 18:46:30 CST 2021
Mon May 31 18:46:40 CST 2021
Mon May 31 18:46:50 CST 2021
Mon May 31 18:47:00 CST 2021
Mon May 31 18:47:10 CST 2021
Mon May 31 18:47:20 CST 2021

四、cron 表达式

Cron 表达式是一个字符串,字符串以 5 或 6 个空格隔开,分为 6 或 7 个域(年可选),每一个域代表一个含义

字段允许值允许的特殊字符
秒(Seconds)0~59 的整数,   -   *   / 四个字符
分(Minutes)0~59 的整数,   -   *   / 四个字符
小时(Hours)0~23 的整数,   -   *   / 四个字符
日期(DayofMonth)1~31 的整数(需要考虑当月的天数),   -   *   ?  /  L   W   C 八个字符
月份(Month)1~12 的整数或者 JAN-DEC,   -   *   / 四个字符
星期(DayofWeek)1~7 的整数或者 SUN-SAT (1=SUN),   -   *   ?  /   L  C  # 八个字符
年(Year,可选)1970~2099,   -   *   / 四个字符

逗号(,):指定一个值列表,例如使用在月域上 1,4,5,7 表示 1 月、4 月、5 月和 7 月

横杠(-):指定一个范围,例如在时域上 3-6 表示 3 点到 6 点(即 3 点、4 点、5 点、6 点)

星号(*):表示这个域上包含所有合法的值。例如,在月份域上使用星号意味着每个月都会触发

斜线(/):表示递增,例如使用在秒域上 0/15 表示每 15 秒

问号(?):只能用在日和周域上,但是不能在这两个域上同时使用。表示不指定,例如想在每月的 20 日触发调度,不管 20 日到底是星期几,则只能使用如下写法: 13 13 15 20 _ ?, 其中最后一位只能用?,而不能使用 _,如果使用 * 表示不管星期几都会触发,实际上并不是这样。

井号(#):只能使用在周域上,用于指定月份中的第几周的哪一天,例如 6#3,意思是某月的第三个周五 (6=星期五,3 意味着月份中的第三周)

L:某域上允许的最后一个值。只能使用在日和周域上。当用在日域上,表示的是在月域上指定的月份的最后一天。用于周域上时,表示周的最后一天,就是星期六

W:W 字符代表着工作日 (星期一到星期五),只能用在日域上,它用来指定离指定日的最近的一个工作日