做后台系统的时候,最烦的就是定时任务调度,用Spring自带的@Scheduled吧,功能太简单,不支持持久化,应用重启任务就没了;用Timer吧,功能单一,不支持复杂调度;后来听说Quartz这玩意儿不错,是Java领域最成熟的任务调度框架,支持Cron表达式、任务持久化、集群部署、监听器、插件扩展,功能贼强大;但是直接用Quartz写,那叫一个复杂,Job、Trigger、Scheduler、JobStore,一堆概念搞得人头大;后来发现Spring Boot直接把这些都封装好了,用起来贼简单;现在Spring Boot 4出来了,整合Quartz更是方便得不行,自动配置给你整得明明白白,咱今天就聊聊Spring Boot 4咋整合Quartz的。
其实Quartz在Spring Boot里早就支持了,你只要加个spring-boot-starter-quartz依赖,基本上就能用;但是很多兄弟不知道里面的门道,也不知道咋配置JobStore、持久化、集群、监听器这些高级功能,所以鹏磊今天就给兄弟们掰扯掰扯。
Quartz基础概念
Quartz是啥玩意儿
Quartz是OpenSymphony开源的一个功能强大的任务调度框架,用于在Java应用中执行定时任务;Quartz的核心特性包括:
- 灵活的任务调度: 支持Cron表达式、简单触发、日历触发等多种调度方式
- 任务持久化: 支持内存存储(RAMJobStore)和数据库存储(JDBCJobStore),任务不会因为应用重启而丢失
- 集群支持: 支持多节点部署,任务自动负载均衡,避免重复执行
- 监听器和插件: 支持任务执行前后监听、触发器监听、插件扩展
- 事务支持: 支持JTA事务,可以和其他事务一起管理
- 动态管理: 支持动态添加、删除、暂停、恢复任务
Quartz的核心概念
- Job(任务): 要执行的具体工作,实现
Job接口,定义execute方法 - JobDetail(任务详情): Job的元数据,包含Job类、Job名称、组名、参数等
- Trigger(触发器): 定义Job何时执行,支持SimpleTrigger(简单触发)和CronTrigger(Cron表达式触发)
- Scheduler(调度器): Quartz的核心,负责调度Job的执行
- JobStore(任务存储): 存储Job和Trigger的信息,支持内存存储和数据库存储
- JobDataMap(任务数据映射): 用于在Job执行时传递参数
Quartz的JobStore类型
- RAMJobStore: 内存存储,速度快但数据不持久化,应用重启后任务丢失
- JobStoreTX: JDBC存储,使用JDBC事务管理,适合独立应用
- JobStoreCMT: JDBC存储,使用容器管理事务(JTA),适合EJB或J2EE环境
Quartz和Spring Task的区别
- 功能: Quartz功能更全面,支持持久化、集群、监听器;Spring Task只支持简单调度
- 持久化: Quartz支持数据库持久化;Spring Task不支持
- 集群: Quartz支持集群部署;Spring Task不支持
- 灵活性: Quartz支持Cron表达式、复杂调度;Spring Task功能相对简单
项目搭建和依赖配置
创建Maven项目
首先你得有个Maven项目,用IDEA或者Eclipse都行,或者直接用Spring Initializr生成;项目结构大概是这样:
spring-boot-quartz-demo/
├── pom.xml # Maven配置文件
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── demo/
│ │ │ ├── Application.java # 启动类
│ │ │ ├── job/ # Job目录
│ │ │ ├── config/ # 配置类目录
│ │ │ └── controller/ # 控制器目录
│ │ └── resources/
│ │ ├── application.yml # 配置文件
│ │ └── quartz.sql # Quartz数据库脚本(可选)
│ └── test/
└── README.md
添加Maven依赖
在pom.xml中添加Spring Boot 4和Quartz的依赖:
<?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
http://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>4.0.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-quartz-demo</artifactId>
<version>1.0.0</version>
<name>Spring Boot Quartz Demo</name>
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Quartz Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- Spring Boot Actuator(可选,用于监控) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- MySQL驱动(如果使用JDBC JobStore) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- H2数据库(开发测试用,可选) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok(可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
基础配置(内存模式)
application.yml配置
最简单的配置就是使用内存模式(RAMJobStore),适合开发测试环境:
spring:
application:
name: spring-boot-quartz-demo
# Quartz配置
quartz:
# JobStore类型: memory(内存)或jdbc(数据库)
job-store-type: memory
# 调度器名称
scheduler-name: MyScheduler
# 是否自动启动
auto-startup: true
# 启动延迟时间(秒)
startup-delay: 0s
# 关闭时是否等待任务完成
wait-for-jobs-to-complete-on-shutdown: false
# 是否覆盖已存在的任务
overwrite-existing-jobs: false
# 自定义属性(可选)
properties:
org.quartz.threadPool.threadCount: 10 # 线程池大小
org.quartz.threadPool.threadPriority: 5 # 线程优先级
application.properties配置
如果喜欢用properties格式:
# Quartz配置
spring.quartz.job-store-type=memory
spring.quartz.scheduler-name=MyScheduler
spring.quartz.auto-startup=true
spring.quartz.startup-delay=0s
spring.quartz.wait-for-jobs-to-complete-on-shutdown=false
spring.quartz.overwrite-existing-jobs=false
# 线程池配置
spring.quartz.properties.org.quartz.threadPool.threadCount=10
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
创建Job和Trigger
创建简单的Job
首先创建一个简单的Job,实现Job接口:
package com.example.demo.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* 简单的定时任务示例
* 实现Job接口,定义execute方法执行具体任务
*
* @author penglei
*/
@Slf4j
public class SimpleJob implements Job {
/**
* 任务执行方法
* Quartz会调用这个方法执行任务
*
* @param context Job执行上下文,包含JobDetail、Trigger等信息
* @throws JobExecutionException 任务执行异常
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取JobDetail中的参数
String jobName = context.getJobDetail().getKey().getName(); // 获取任务名称
String jobGroup = context.getJobDetail().getKey().getGroup(); // 获取任务组名
// 获取JobDataMap中的参数
String message = context.getJobDetail().getJobDataMap().getString("message"); // 获取自定义参数
log.info("执行任务: {}.{}, 参数: {}", jobGroup, jobName, message);
log.info("执行时间: {}", System.currentTimeMillis());
// 这里写你的业务逻辑
try {
// 模拟业务处理
Thread.sleep(1000);
log.info("任务执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new JobExecutionException("任务执行被中断", e);
}
}
}
使用Spring Bean的Job
如果Job需要注入Spring Bean,可以使用@DisallowConcurrentExecution和@PersistJobDataAfterExecution注解:
package com.example.demo.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
/**
* 使用Spring Bean的Job
* 继承QuartzJobBean,可以注入Spring Bean
*
* @author penglei
*/
@Slf4j
@Component
@PersistJobDataAfterExecution // 执行后持久化JobDataMap
public class SpringBeanJob extends QuartzJobBean {
/**
* 可以注入Spring Bean
*/
// @Autowired
// private UserService userService;
/**
* 执行任务
*
* @param context Job执行上下文
* @throws JobExecutionException 任务执行异常
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String jobName = context.getJobDetail().getKey().getName();
log.info("Spring Bean Job执行: {}", jobName);
// 可以使用注入的Spring Bean
// userService.doSomething();
}
}
配置Job和Trigger
使用@Configuration配置Job和Trigger:
package com.example.demo.config;
import com.example.demo.job.SimpleJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Quartz配置类
* 配置Job和Trigger
*
* @author penglei
*/
@Slf4j
@Configuration
public class QuartzConfig {
/**
* 配置SimpleJob的JobDetail
* JobDetail包含Job的元数据信息
*
* @return JobDetail对象
*/
@Bean
public JobDetail simpleJobDetail() {
return JobBuilder.newJob(SimpleJob.class) // 指定Job类
.withIdentity("simpleJob", "default") // 任务名称和组名
.withDescription("简单的定时任务") // 任务描述
.storeDurably() // 即使没有Trigger关联也保留Job
.usingJobData("message", "Hello Quartz") // 设置JobDataMap参数
.build();
}
/**
* 配置SimpleTrigger
* SimpleTrigger用于简单的定时触发,比如每5秒执行一次
*
* @return Trigger对象
*/
@Bean
public Trigger simpleTrigger() {
return TriggerBuilder.newTrigger()
.forJob(simpleJobDetail()) // 关联JobDetail
.withIdentity("simpleTrigger", "default") // 触发器名称和组名
.withDescription("简单触发器") // 触发器描述
.startNow() // 立即开始
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 每5秒执行一次
.repeatForever()) // 无限重复
.build();
}
/**
* 配置CronTrigger
* CronTrigger用于复杂的定时触发,支持Cron表达式
*
* @return Trigger对象
*/
@Bean
public Trigger cronTrigger() {
return TriggerBuilder.newTrigger()
.forJob(simpleJobDetail()) // 关联JobDetail
.withIdentity("cronTrigger", "default") // 触发器名称和组名
.withDescription("Cron触发器") // 触发器描述
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) // 每10秒执行一次
.build();
}
}
Cron表达式说明
Cron表达式用于定义任务执行时间,格式为:秒 分 时 日 月 周 [年]
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
时 0-23 , - * /
日 1-31 , - * ? / L W
月 1-12 或 JAN-DEC , - * /
周 0-7 或 SUN-SAT , - * ? / L #
年(可选) 1970-2099 , - * /
常用Cron表达式示例:
0 0 12 * * ? 每天12点执行
0 0 12 * * ? 每天12点执行
0 15 10 ? * * 每天10点15分执行
0 15 10 * * ? 每天10点15分执行
0 15 10 * * ? * 每天10点15分执行
0 15 10 * * ? 2005 2005年每天10点15分执行
0 * 14 * * ? 每天14点到14点59分,每分钟执行一次
0 0/5 14 * * ? 每天14点到14点55分,每5分钟执行一次
0 0/5 14,18 * * ? 每天14点到14点55分和18点到18点55分,每5分钟执行一次
0 0-5 14 * * ? 每天14点到14点05分,每分钟执行一次
0 10,44 14 ? 3 WED 3月每周三14点10分和14点44分执行
0 15 10 ? * MON-FRI 周一到周五10点15分执行
0 15 10 15 * ? 每月15号10点15分执行
0 15 10 L * ? 每月最后一天10点15分执行
0 15 10 ? * 6L 每月最后一个周五10点15分执行
0 15 10 ? * 6L 2002-2005 2002到2005年每月最后一个周五10点15分执行
0 15 10 ? * 6#3 每月第三个周五10点15分执行
0 0 12 1/5 * ? 每月1号开始,每5天12点执行
0 11 11 11 11 ? 每年11月11号11点11分执行
JDBC模式配置(持久化)
数据库初始化
如果使用JDBC JobStore,需要先初始化Quartz数据库表;Spring Boot会自动执行初始化脚本,你只需要配置数据源:
spring:
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Quartz JDBC配置
quartz:
job-store-type: jdbc # 使用JDBC存储
# JDBC配置
jdbc:
# 是否初始化数据库表
initialize-schema: always # always(总是初始化),embedded(嵌入式数据库),never(不初始化)
# 数据库平台(可选,Spring Boot会自动检测)
platform: mysql
# 调度器配置
scheduler-name: MyScheduler
auto-startup: true
startup-delay: 0s
wait-for-jobs-to-complete-on-shutdown: false
overwrite-existing-jobs: false
# 自定义属性
properties:
# 线程池配置
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
# JobStore配置
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix: QRTZ_ # 表前缀
org.quartz.jobStore.isClustered: false # 是否集群模式
org.quartz.jobStore.clusterCheckinInterval: 20000 # 集群检查间隔(毫秒)
org.quartz.jobStore.maxMisfiresToHandleAtATime: 1 # 最大错过触发数
org.quartz.jobStore.misfireThreshold: 120000 # 错过触发阈值(毫秒)
org.quartz.jobStore.txIsolationLevelSerializable: false # 事务隔离级别
手动初始化数据库
如果Spring Boot没有自动初始化,可以手动执行Quartz数据库脚本;脚本位置在Quartz的jar包中:org/quartz/impl/jdbcjobstore/tables_mysql.sql
创建数据库和表:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS quartz DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE quartz;
-- Quartz表结构(MySQL版本)
-- 这些表用于存储Job、Trigger、调度信息等
-- 调度器状态表
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 触发器表
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 简单触发器表
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(10) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Cron触发器表
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Blob触发器表(用于存储序列化的Trigger)
CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 日历表
CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 暂停触发器表
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 触发器错误日志表
CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
SCHED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 调度器状态表
CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Job详情表
CREATE TABLE QRTZ_JOB_DETAILS (
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_NONCONCURRENT VARCHAR(1) NOT NULL,
IS_UPDATE_DATA VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
动态管理任务
使用Scheduler动态添加任务
package com.example.demo.service;
import com.example.demo.job.SimpleJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Quartz任务管理服务
* 用于动态添加、删除、暂停、恢复任务
*
* @author penglei
*/
@Slf4j
@Service
public class QuartzJobService {
@Autowired
private Scheduler scheduler; // 注入Scheduler
/**
* 添加一个简单的定时任务
*
* @param jobName 任务名称
* @param jobGroup 任务组名
* @param cronExpression Cron表达式
* @param message 任务参数
* @throws SchedulerException 调度异常
*/
public void addJob(String jobName, String jobGroup, String cronExpression, String message) throws SchedulerException {
// 创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity(jobName, jobGroup)
.withDescription("动态添加的任务")
.usingJobData("message", message)
.storeDurably()
.build();
// 创建Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.withIdentity(jobName + "Trigger", jobGroup)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
// 调度任务
scheduler.scheduleJob(jobDetail, trigger);
log.info("添加任务成功: {}.{}", jobGroup, jobName);
}
/**
* 删除任务
*
* @param jobName 任务名称
* @param jobGroup 任务组名
* @throws SchedulerException 调度异常
*/
public void deleteJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.deleteJob(jobKey);
log.info("删除任务成功: {}.{}", jobGroup, jobName);
}
/**
* 暂停任务
*
* @param jobName 任务名称
* @param jobGroup 任务组名
* @throws SchedulerException 调度异常
*/
public void pauseJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.pauseJob(jobKey);
log.info("暂停任务成功: {}.{}", jobGroup, jobName);
}
/**
* 恢复任务
*
* @param jobName 任务名称
* @param jobGroup 任务组名
* @throws SchedulerException 调度异常
*/
public void resumeJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.resumeJob(jobKey);
log.info("恢复任务成功: {}.{}", jobGroup, jobName);
}
/**
* 立即触发任务
*
* @param jobName 任务名称
* @param jobGroup 任务组名
* @throws SchedulerException 调度异常
*/
public void triggerJob(String jobName, String jobGroup) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.triggerJob(jobKey);
log.info("触发任务成功: {}.{}", jobGroup, jobName);
}
/**
* 更新任务的Cron表达式
*
* @param jobName 任务名称
* @param jobGroup 任务组名
* @param cronExpression 新的Cron表达式
* @throws SchedulerException 调度异常
*/
public void updateJobCron(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger != null) {
// 创建新的Trigger
CronTrigger newTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
// 更新Trigger
scheduler.rescheduleJob(triggerKey, newTrigger);
log.info("更新任务Cron表达式成功: {}.{}", jobGroup, jobName);
}
}
}
Controller测试接口
package com.example.demo.controller;
import com.example.demo.service.QuartzJobService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* Quartz任务管理Controller
*
* @author penglei
*/
@Slf4j
@RestController
@RequestMapping("/api/quartz")
public class QuartzController {
@Autowired
private QuartzJobService quartzJobService;
/**
* 添加任务
*/
@PostMapping("/job")
public Map<String, Object> addJob(@RequestParam String jobName,
@RequestParam String jobGroup,
@RequestParam String cronExpression,
@RequestParam(required = false, defaultValue = "") String message) throws SchedulerException {
quartzJobService.addJob(jobName, jobGroup, cronExpression, message);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "添加任务成功");
return result;
}
/**
* 删除任务
*/
@DeleteMapping("/job")
public Map<String, Object> deleteJob(@RequestParam String jobName,
@RequestParam String jobGroup) throws SchedulerException {
quartzJobService.deleteJob(jobName, jobGroup);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "删除任务成功");
return result;
}
/**
* 暂停任务
*/
@PostMapping("/job/pause")
public Map<String, Object> pauseJob(@RequestParam String jobName,
@RequestParam String jobGroup) throws SchedulerException {
quartzJobService.pauseJob(jobName, jobGroup);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "暂停任务成功");
return result;
}
/**
* 恢复任务
*/
@PostMapping("/job/resume")
public Map<String, Object> resumeJob(@RequestParam String jobName,
@RequestParam String jobGroup) throws SchedulerException {
quartzJobService.resumeJob(jobName, jobGroup);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "恢复任务成功");
return result;
}
/**
* 立即触发任务
*/
@PostMapping("/job/trigger")
public Map<String, Object> triggerJob(@RequestParam String jobName,
@RequestParam String jobGroup) throws SchedulerException {
quartzJobService.triggerJob(jobName, jobGroup);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "触发任务成功");
return result;
}
/**
* 更新任务Cron表达式
*/
@PutMapping("/job/cron")
public Map<String, Object> updateJobCron(@RequestParam String jobName,
@RequestParam String jobGroup,
@RequestParam String cronExpression) throws SchedulerException {
quartzJobService.updateJobCron(jobName, jobGroup, cronExpression);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "更新任务Cron表达式成功");
return result;
}
}
监听器和插件
Job监听器
监听Job的执行:
package com.example.demo.listener;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.stereotype.Component;
/**
* Job监听器
* 监听Job的执行前后
*
* @author penglei
*/
@Slf4j
@Component
public class MyJobListener implements JobListener {
@Override
public String getName() {
return "MyJobListener"; // 监听器名称
}
/**
* Job即将执行时调用
*/
@Override
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
log.info("Job即将执行: {}", jobName);
}
/**
* Job被Vetoed时调用(TriggerListener返回true时)
*/
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
log.warn("Job执行被否决: {}", jobName);
}
/**
* Job执行完成后调用
*/
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String jobName = context.getJobDetail().getKey().getName();
if (jobException != null) {
log.error("Job执行失败: {}, 异常: {}", jobName, jobException.getMessage());
} else {
log.info("Job执行完成: {}", jobName);
}
}
}
Trigger监听器
监听Trigger的触发:
package com.example.demo.listener;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.springframework.stereotype.Component;
/**
* Trigger监听器
* 监听Trigger的触发前后
*
* @author penglei
*/
@Slf4j
@Component
public class MyTriggerListener implements TriggerListener {
@Override
public String getName() {
return "MyTriggerListener"; // 监听器名称
}
/**
* Trigger即将触发时调用
* 返回true会阻止Job执行
*/
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
String triggerName = trigger.getKey().getName();
log.info("Trigger即将触发: {}", triggerName);
}
/**
* Trigger触发被否决时调用
* 返回true会阻止Job执行
*/
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
// 返回true会阻止Job执行
return false;
}
/**
* Trigger触发完成时调用
*/
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext context,
Trigger.CompletedExecutionInstruction triggerInstructionCode) {
String triggerName = trigger.getKey().getName();
log.info("Trigger触发完成: {}", triggerName);
}
/**
* Trigger错过触发时调用
*/
@Override
public void triggerMisfired(Trigger trigger) {
String triggerName = trigger.getKey().getName();
log.warn("Trigger错过触发: {}", triggerName);
}
}
注册监听器
在配置类中注册监听器:
package com.example.demo.config;
import com.example.demo.listener.MyJobListener;
import com.example.demo.listener.MyTriggerListener;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* 监听器配置类
*
* @author penglei
*/
@Slf4j
@Configuration
public class ListenerConfig {
@Autowired
private Scheduler scheduler;
@Autowired
private MyJobListener myJobListener;
@Autowired
private MyTriggerListener myTriggerListener;
/**
* 注册监听器
*/
@PostConstruct
public void registerListeners() throws SchedulerException {
// 注册全局Job监听器
scheduler.getListenerManager().addJobListener(myJobListener);
// 注册全局Trigger监听器
scheduler.getListenerManager().addTriggerListener(myTriggerListener);
log.info("监听器注册成功");
}
}
Actuator端点
Spring Boot 4提供了Quartz的Actuator端点,可以查看和管理任务:
配置Actuator
spring:
# Actuator配置
actuator:
endpoints:
web:
exposure:
include: health,info,quartz # 暴露quartz端点
endpoint:
quartz:
enabled: true # 启用quartz端点
访问Quartz端点
# 查看所有Job组
curl http://localhost:8080/actuator/quartz/jobGroups
# 查看指定组的Jobs
curl http://localhost:8080/actuator/quartz/jobGroups/default
# 查看Job详情
curl http://localhost:8080/actuator/quartz/jobs/default/simpleJob
# 查看所有Trigger组
curl http://localhost:8080/actuator/quartz/triggerGroups
# 查看指定组的Triggers
curl http://localhost:8080/actuator/quartz/triggerGroups/default
# 查看Quartz报告
curl http://localhost:8080/actuator/quartz/report
# 立即触发Job
curl -X POST http://localhost:8080/actuator/quartz/jobs/default/simpleJob/trigger
高级配置
自定义SchedulerFactoryBean
如果需要更细粒度的控制,可以实现SchedulerFactoryBeanCustomizer:
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerFactoryBean;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
/**
* SchedulerFactoryBean自定义配置
*
* @author penglei
*/
@Slf4j
@Configuration
public class SchedulerCustomizer implements SchedulerFactoryBeanCustomizer {
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
// 自定义配置
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true); // 关闭时等待任务完成
schedulerFactoryBean.setOverwriteExistingJobs(true); // 覆盖已存在的任务
schedulerFactoryBean.setStartupDelay(5); // 启动延迟5秒
log.info("SchedulerFactoryBean自定义配置完成");
}
}
集群配置
如果使用JDBC JobStore,可以配置集群模式:
spring:
quartz:
job-store-type: jdbc
properties:
# 集群配置
org.quartz.jobStore.isClustered: true # 启用集群模式
org.quartz.jobStore.clusterCheckinInterval: 20000 # 集群检查间隔(毫秒)
org.quartz.scheduler.instanceId: AUTO # 实例ID,集群模式下自动生成
org.quartz.scheduler.instanceName: MyScheduler # 调度器名称
启动类和测试
启动类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot 4 Quartz示例应用启动类
*
* @author penglei
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
测试
启动应用后,可以看到任务自动执行;也可以通过API动态管理任务:
# 添加任务
curl -X POST "http://localhost:8080/api/quartz/job?jobName=testJob&jobGroup=test&cronExpression=0/5 * * * * ?&message=测试任务"
# 暂停任务
curl -X POST "http://localhost:8080/api/quartz/job/pause?jobName=testJob&jobGroup=test"
# 恢复任务
curl -X POST "http://localhost:8080/api/quartz/job/resume?jobName=testJob&jobGroup=test"
# 立即触发任务
curl -X POST "http://localhost:8080/api/quartz/job/trigger?jobName=testJob&jobGroup=test"
# 更新Cron表达式
curl -X PUT "http://localhost:8080/api/quartz/job/cron?jobName=testJob&jobGroup=test&cronExpression=0/10 * * * * ?"
# 删除任务
curl -X DELETE "http://localhost:8080/api/quartz/job?jobName=testJob&jobGroup=test"
最佳实践
- 生产环境使用JDBC JobStore: 内存模式适合开发测试,生产环境必须使用JDBC模式保证任务持久化
- 合理设置线程池大小: 根据任务数量和执行时间合理设置线程池大小,避免任务堆积
- 使用监听器监控任务: 通过监听器记录任务执行日志,方便问题排查
- 任务幂等性: 确保任务可以重复执行而不产生副作用
- 异常处理: 任务中要做好异常处理,避免任务失败影响调度器
- 避免长时间运行的任务: 长时间运行的任务会占用线程,影响其他任务执行
- 集群部署: 生产环境建议使用集群模式,提高可用性
- 监控和告警: 使用Actuator端点监控任务状态,设置告警规则
总结
Spring Boot 4整合Quartz非常方便,自动配置给你整得明明白白;内存模式适合开发测试,JDBC模式适合生产环境;通过监听器和插件可以扩展功能,Actuator端点可以方便地监控和管理任务;兄弟们根据实际需求选择合适的配置,就能轻松搞定定时任务调度了。