1. 概述
Quartz 的架构模块化设计非常清晰,核心组件包括 Job、JobDetail、Trigger 和 Scheduler,我们可以按需灵活组合。虽然本教程基于 Spring 构建应用,但每个组件既可以用原生 Quartz 方式配置,也可以使用 Spring 提供的封装类来简化操作。为了全面性,我们会对比两种方式,但实际开发中建议选择更简洁、更适合当前项目架构的方案。
目标很明确:用 Spring + Quartz 快速搭建一个可扩展的调度系统,让新增定时任务变得简单粗暴。
2. Job 与 JobDetail
定义一个 Quartz 任务,第一步是创建具体的执行逻辑。这通过实现 Job
接口完成,然后用 JobDetail
包装它,提供任务的元数据信息。
2.1. Job
Job
接口只有一个方法:execute(JobExecutionContext context)
。当触发器(Trigger)触发时,调度器会调用这个方法执行任务。
JobExecutionContext
提供了运行时上下文,包含当前调度器、触发器以及 JobDetail 的引用,非常方便做动态控制。
✅ 实际开发中,不建议把业务逻辑直接写在 Job
实现类里,而是通过 Spring 注入服务类来处理,这样更利于测试和解耦:
@Component
public class SampleJob implements Job {
@Autowired
private SampleJobService jobService;
public void execute(JobExecutionContext context) throws JobExecutionException {
jobService.executeSampleJob();
}
}
⚠️ 注意:@Autowired
在 Job 类中默认不生效,因为 Job 实例由 Quartz 创建而非 Spring 容器管理。解决办法见 5.3 节的 SpringBeanJobFactory
配置。
2.2. JobDetail
Job 是干活的,但 Quartz 不会直接保存 Job 实例。取而代之的是,通过 JobDetail
来定义任务的“蓝图”——它描述了要执行哪个 Job 类、是否持久化、是否有参数等。
换句话说,Job 是“工人”,JobDetail 是“工人的档案”。
2.3. Quartz JobBuilder
Quartz 提供了 JobBuilder
,支持链式调用创建 JobDetail
:
@Bean
public JobDetail jobDetail() {
return JobBuilder.newJob().ofType(SampleJob.class)
.storeDurably() // 即使没有关联 Trigger 也保留
.withIdentity("Qrtz_Job_Detail")
.withDescription("Invoke Sample Job service...")
.build();
}
✅ storeDurably()
很实用,尤其在动态管理任务时,避免因无 Trigger 而被自动删除。
2.4. Spring JobDetailFactoryBean
Spring 提供了 JobDetailFactoryBean
,更适合 Spring 风格的配置方式:
@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(SampleJob.class);
jobDetailFactory.setDescription("Invoke Sample Job service...");
jobDetailFactory.setDurability(true);
return jobDetailFactory;
}
✅ 优势:
- 自动使用 Spring Bean 名作为 Job 名
- 与 Spring 生命周期集成更好
- 支持更自然的 JavaConfig 配置
3. Trigger
Trigger 是任务的“闹钟”,负责定义何时、以何种频率执行 Job。Job 是“做什么”,Trigger 是“什么时候做”。
两者职责分离,一个 Job 可以绑定多个 Trigger,非常灵活。
假设我们要实现“每小时执行一次”的需求,可以使用 SimpleTrigger
,通过以下方式配置:
3.1. Quartz TriggerBuilder
@Bean
public Trigger trigger(JobDetail job) {
return TriggerBuilder.newTrigger().forJob(job)
.withIdentity("Qrtz_Trigger")
.withDescription("Sample trigger")
.withSchedule(simpleSchedule().repeatForever().withIntervalInHours(1))
.build();
}
简单直接,API 表达力强。
3.2. Spring SimpleTriggerFactoryBean
@Bean
public SimpleTriggerFactoryBean trigger(JobDetail job) {
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setJobDetail(job);
trigger.setRepeatInterval(3600000); // 1小时(毫秒)
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
return trigger;
}
✅ 优势:
- Bean 风格,与 Spring 配置统一
- 自动使用 Bean 名作为 Trigger 名
- 与 JobDetail 注入无缝衔接
4. 配置 JobStore
JobStore 是 Quartz 的“大脑记忆”,负责存储 Job 和 Trigger 的所有信息。根据是否持久化,分为内存和数据库两种模式。
4.1. 内存 JobStore(RAMJobStore)
性能最快,配置最简单,适合开发或临时任务:
spring.quartz.job-store-type=memory
⚠️ 缺点也很明显:应用重启后所有调度信息丢失。所以生产环境慎用。
底层 Quartz 配置等价于:
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
4.2. JDBC JobStore(持久化存储)
生产环境推荐使用,调度信息存入数据库,支持故障恢复。
Quartz 提供两种 JDBC 存储实现:
JobStoreTX
:自己管理事务(常用)JobStoreCMT
:依赖容器事务(如 JTA,较少用)
基本配置如下:
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource
在 Spring Boot 中启用 JDBC JobStore:
spring.quartz.job-store-type=jdbc
并配置数据源(使用 @QuartzDataSource
标记专用数据源):
@Configuration
@EnableAutoConfiguration
public class SpringQrtzScheduler {
@Bean
@QuartzDataSource
public DataSource quartzDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/quartz_db")
.username("quartz_user")
.password("quartz_pass")
.driverClassName("com.mysql.cj.jdbc.Driver")
.build();
}
}
✅ 踩坑提醒:
- 需提前执行 Quartz 提供的 SQL 脚本建表(官方脚本地址)
- 表前缀默认为
QRTZ_
,可在配置中修改 - MySQL 建议使用
InnoDB
引擎,确保事务一致性
5. Scheduler
Scheduler 是 Quartz 的核心调度接口,负责管理 Job 和 Trigger 的注册与执行。
刚创建的 Scheduler 处于“待机”状态,必须手动调用 start()
才会真正启动调度线程。
5.1. Quartz StdSchedulerFactory
原生方式通过 StdSchedulerFactory
获取 Scheduler 实例:
@Bean
public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory)
throws SchedulerException {
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();
return scheduler;
}
⚠️ 注意:这里我们借用了 Spring 的 SchedulerFactoryBean
来获取 Scheduler,避免重复初始化。
5.2. Spring SchedulerFactoryBean
更推荐使用 Spring 的 SchedulerFactoryBean
,它不仅封装了 Scheduler 的创建,还能自动注册 Job 和 Trigger,并纳入 Spring 容器生命周期管理:
@Bean
public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) {
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
schedulerFactory.setJobFactory(springBeanJobFactory());
schedulerFactory.setJobDetails(job);
schedulerFactory.setTriggers(trigger);
schedulerFactory.setDataSource(quartzDataSource);
return schedulerFactory;
}
✅ 关键点:
setJobFactory()
设置自定义 Job 工厂,解决 Spring Bean 注入问题setJobDetails()
和setTriggers()
自动注册任务和触发器setDataSource()
启用持久化存储
5.3. 配置 SpringBeanJobFactory(解决 Autowire 失效)
默认情况下,Quartz 创建的 Job 实例不受 Spring 管理,@Autowired
会失效。
解决方案:使用 SpringBeanJobFactory
并扩展支持自动注入:
@Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
其中 AutoWiringSpringBeanJobFactory
是一个自定义类,继承 SpringBeanJobFactory
并重写 createJobInstance
方法,利用 AutowireCapableBeanFactory
实现自动装配。
✅ 这一步是 Spring + Quartz 整合的关键,否则所有 Job 内的 Service 注入都会为 null。
6. 使用 Spring Boot Actuator 动态触发 Quartz 任务
从 Spring Boot 3.5.0 开始,Actuator 提供了专用的 Quartz 接口,支持通过 REST 动态管理任务,包括:触发、暂停、恢复、删除、查询等。
本节重点讲解如何手动触发一个已注册的 Quartz 任务。
6.1. 启用 Actuator 支持
首先添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
然后在 application.properties
中暴露 Quartz 接口:
management.endpoints.web.exposure.include=quartz
management.endpoint.quartz.enabled=true
✅ 启动后访问 http://localhost:8080/actuator
可看到 quartz
接口已就绪。
6.2. 手动触发任务
通过 POST 请求调用 Actuator 接口,立即执行指定任务(跳过原定调度时间):
POST /actuator/quartz/jobs/{jobGroup}/{jobName}
示例:触发 DEFAULT
组下的 Qrtz_Job_Detail
任务:
curl -X POST "http://localhost:8080/actuator/quartz/jobs/DEFAULT/Qrtz_Job_Detail" \
-H "Content-Type: application/json" \
-d '{"state":"running"}'
⚠️ 注意:
state
字段目前必须存在,虽然实际不影响执行jobGroup
和jobName
对应JobDetail
中设置的 identity
成功响应示例:
{
"group" : "DEFAULT",
"name" : "Qrtz_Job_Detail",
"className" : "org.baeldung.springquartz.basics.scheduler.SampleJob",
"triggerTime" : "2025-05-31T10:36:36.474563900Z"
}
✅ 实际用途:
- ✅ 调试任务逻辑,无需等待定时触发
- ✅ 紧急修复后立即执行补偿任务
- ✅ 手动运行运维脚本(如清理缓存、生成报表)
❌ 不建议用于高频调用,应通过消息队列或普通 REST 接口替代。
💡 提示:该接口还支持
GET /actuator/quartz
查询所有任务状态,DELETE
删除任务,PUT
更新 Trigger 等,功能非常完整。
7. 总结
本文从零搭建了一个基于 Spring + Quartz 的任务调度系统,涵盖了核心组件的配置方式(原生 vs Spring 封装),持久化存储方案,以及 Spring Boot 3.5+ 的 Actuator 动态管理能力。
关键收获:
- ✅ 使用
JobDetailFactoryBean
和SimpleTriggerFactoryBean
简化配置 - ✅ 通过
SpringBeanJobFactory
解决 Job 类中@Autowired
失效问题 - ✅ 生产环境务必使用 JDBC JobStore + 持久化表结构
- ✅ 利用 Actuator 实现任务的动态触发与运维,提升系统灵活性
完整示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-quartz