Quartz是一个功能强大、开源的作业调度库,它可以集成到几乎任何Java应用程序中——从最小的独立应用程序到最大的电子商务系统。

本教程将涵盖以下内容:
- Quartz核心概念
- 快速入门:一个简单的 "Hello World" 示例
- 进阶:使用
JobDataMap传递数据 - 进阶:使用 Cron 表达式设置复杂调度
- 进阶:Job 依赖管理与
JobBuilder、TriggerBuilder - 如何优雅地关闭调度器
- Quartz 与 Spring/Spring Boot 集成
- 总结与最佳实践
Quartz 核心概念
要使用Quartz,必须先理解它的几个核心组件:
Job(作业): 这是一个接口,你只需要实现execute(JobExecutionContext context)方法,这个接口代表一个“可执行的工作单元”,即你想要定时执行的具体任务逻辑,发送邮件、生成报表等。JobDetail(作业详情): 这是一个Job的详细配置信息,它包含了Job的实现类名、以及一个JobDataMap(用于传递数据给Job)。JobDetail是Job的实例配置,而不是Job本身,每次调度器执行一个Job,它都会根据JobDetail创建一个新的Job实例。Trigger(触发器): 这个组件定义了Job的执行计划,它决定了Job何时以及如何执行,可以设置它在某个特定时间点执行,或者每隔5秒执行一次。SimpleTrigger: 用于简单的调度场景,比如在指定时间执行一次,或重复执行N次。CronTrigger: 用于更复杂的调度场景,基于 Cron 表达式来定义执行时间(每天凌晨1点执行)。
Scheduler(调度器): 这是Quartz的核心控制器,它负责将Job和Trigger绑定在一起,并根据Trigger的定义来执行Job,你可以通过Scheduler来启动、停止、暂停或恢复任务。
简单比喻:
Job:是你想要执行的“工作内容”(打扫卫生”)。JobDetail:是这份工作的“工作单”,上面写着“打扫卫生”这个任务,以及需要传递给清洁工的“数据”(使用绿色拖把”)。Trigger:是“工作时间表”,上面写着“每周一、周三、周五下午5点”。Scheduler:是“项目经理”,他拿着工作单和时间表,确保清洁工在正确的时间和正确的方式下完成工作。
快速入门:一个简单的 "Hello World" 示例
下面是一个最简单的Quartz程序,它会每隔5秒打印一次 "Hello, Quartz World!"。

1 添加依赖
在你的 pom.xml (Maven) 或 build.gradle (Gradle) 中添加Quartz的核心依赖。
Maven (pom.xml)
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version> <!-- 建议使用较新稳定版本 -->
</dependency>
<!-- 为了方便日志输出,可以添加一个日志实现,如SLF4J + Logback -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
2 编写 Job
创建一个实现 Job 接口的类。
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(HelloJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 这就是你的定时任务逻辑
logger.info("Hello, Quartz World! - Current time: {}", System.currentTimeMillis());
}
}
3 编写主程序来调度任务
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
public class QuartzDemo {
private static final Logger logger = LoggerFactory.getLogger(QuartzDemo.class);
public static void main(String[] args) throws SchedulerException {
// 1. 创建一个 SchedulerFactory(调度器工厂)
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// 2. 从工厂中获取一个 Scheduler 实例
Scheduler scheduler = schedulerFactory.getScheduler();
// 3. 创建 JobDetail 实例,并与 HelloJob 类绑定
JobDetail jobDetail = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // 设置 name 和 group
.build();
// 4. 创建 Trigger 实例,定义立即执行,并且每5秒重复一次
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1") // 设置 name 和 group
.startNow() // 立即开始
.withSchedule(simpleSchedule()
.withIntervalInSeconds(5) // 间隔5秒
.repeatForever()) // 永远重复
.build();
// 5. 将 Job 和 Trigger 绑定到 Scheduler 上,并启动调度器
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
logger.info("Scheduler started. Job will run every 5 seconds.");
// 保持主线程存活,让定时任务有机会运行
try {
Thread.sleep(60000); // 睡眠60秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 关闭调度器
logger.info("Shutting down scheduler...");
scheduler.shutdown();
}
}
运行这个程序,你会看到控制台每隔5秒打印一次 "Hello, Quartz World!"。
进阶:使用 JobDataMap 传递数据
有时你需要向 Job 传递一些参数,比如用户ID、查询条件等,这时可以使用 JobDataMap。
1 修改 Job 以接收数据
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataPassingJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(DataPassingJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 从 JobExecutionContext 中获取 JobDataMap
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
// 从 map 中获取数据
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
logger.info("Job says: {}", jobSays);
logger.info("MyFloatValue is: {}", myFloatValue);
}
}
2 在调度时设置数据
// ... (SchedulerFactory 和 Scheduler 获取代码)
// 1. 创建 JobDetail
JobDetail jobDetail = newJob(DataPassingJob.class)
.withIdentity("dataJob", "group1")
.usingJobData("jobSays", "Hello from the JobDataMap!") // 设置字符串数据
.usingJobData("myFloatValue", 3.14f) // 设置浮点数数据
.build();
// 2. 创建 Trigger (也可以在 Trigger 中设置数据,如果同名,JobDetail中的数据会被覆盖)
// Trigger trigger = newTrigger()...
// .usingJobData("jobSays", "This value will override the one in JobDetail") // 覆盖数据
// .build();
// 3. 调度
scheduler.scheduleJob(jobDetail, trigger);
// ...
进阶:使用 Cron 表达式设置复杂调度
CronTrigger 非常强大,它使用 Cron 表达式来定义执行时间。
1 常用 Cron 表达式示例
| 表达式 | 说明 |
|---|---|
0/5 * * * * ? |
每隔5秒执行一次 |
0 0/1 * * * ? |
每隔1分钟执行一次 |
0 0 0/1 * * ? |
每隔1小时执行一次 |
0 0 12 * * ? |
每天12点(中午)执行一次 |
0 15 10 ? * * |
每天10:15执行一次 |
0 15 10 * * ? 2025 |
在2025年的每天10:15执行一次 |
0 0/30 9-17 * * ? |
工作日的上午9点到下午5点,每30分钟执行一次 |
0 0 0 L * ? |
每天最后一天(月底)的0点执行一次 |
2 使用 CronTrigger
只需要修改 Trigger 的构建方式即可。
import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
// ...
// 创建一个 CronTrigger,每天凌晨1点执行
Trigger cronTrigger = newTrigger()
.withIdentity("cronTrigger", "group1")
.startNow()
.withSchedule(cronSchedule("0 0 1 * * ?")) // Cron 表达式
.build();
// ...
scheduler.scheduleJob(jobDetail, cronTrigger);
进阶:Job 依赖管理与 JobBuilder, TriggerBuilder
从上面的例子中,你已经看到了 JobBuilder 和 TriggerBuilder,它们是 Quartz 2.x 引入的建造者模式API,比旧版本的 new JobDetail(...) 构造函数更安全、更易读,是目前推荐的用法。
Job 依赖管理:
Quartz 默认使用 无状态 的 Job,这意味着每次执行 Job 时,Quartz 都会创建一个新的 Job 实例,执行完毕后这个实例就会被丢弃,你不能在 Job 类中定义成员变量来保存上次执行的状态。
如果你需要管理状态(记录任务执行的次数),有以下几种方式:
- 使用
JobDataMap:将状态数据存入JobDataMap,每次执行后更新它,Quartz 会将JobDataMap持久化到数据库(如果使用JDBC JobStore),下次执行时可以取到。 - 使用
@PersistJobDataAfterExecution注解:在你的Job类上添加这个注解,Quartz 会在Job执行成功后自动将JobDataMap的内容持久化。 - 使用有状态的
Job(StatefulJob):StatefulJob是一个已废弃的接口(不推荐使用),它会禁止同一个Job的多个实例并发执行,JobDataMap会被保留,但现代更推荐使用注解方式。
示例:使用 @PersistJobDataAfterExecution
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@PersistJobDataAfterExecution // 标记此Job执行后会持久化JobDataMap
public class StatefulJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(StatefulJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int count = dataMap.getInt("count");
count++;
dataMap.put("count", count);
logger.info("This job has been executed {} times.", count);
}
}
在调度时,只需要初始化一次 count 即可。
如何优雅地关闭调度器
在应用程序关闭时(Web应用关闭时),必须正确地关闭 Scheduler,以确保所有正在执行的任务都能完成,并且资源被正确释放。
// ... 在程序关闭的地方,Spring 的 shutdown hook 或 Web 监听器中 ... // 1. 等待已存在的任务执行完毕 scheduler.shutdown(true); // 参数 true 表示等待任务完成 // 或者 2. 立即关闭,中断正在执行的任务 // scheduler.shutdown(false);
Quartz 与 Spring/Spring Boot 集成
在实际项目中,我们通常不会直接使用 StdSchedulerFactory,而是通过 Spring/Spring Boot 来管理 Scheduler,这样可以更好地利用 Spring 的依赖注入和生命周期管理。
1 Spring Boot 集成 (非常简单)
-
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>Spring Boot 会自动配置好一切。
-
创建
Job: 你的Job类可以直接作为 Spring Bean,如果它需要注入其他服务(如UserService),直接使用@Autowired即可。import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component public class SpringIntegratedJob implements Job { private static final Logger logger = LoggerFactory.getLogger(SpringIntegratedJob.class); @Autowired // Spring 会自动注入 private SomeService someService; @Override public void execute(JobExecutionContext context) throws JobExecutionException { logger.info("Executing Spring Integrated Job!"); someService.doSomething(); } } -
配置调度: 你可以通过配置类来配置你的
Job和Trigger。import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuartzConfig { // 1. 定义 JobDetail @Bean public JobDetail quartzJobDetail() { // 使用 JobBuilder 创建 JobDetail return JobBuilder.newJob(SpringIntegratedJob.class) .withIdentity("springJob", "group1") .storeDurably() // Trigger 不立即定义,需要设置为持久化 .build(); } // 2. 定义 Trigger @Bean public Trigger cronJobTrigger() { // 使用 TriggerBuilder 创建 Trigger CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?"); return TriggerBuilder.newTrigger() .forJob(quartzJobDetail()) // 关联 JobDetail .withIdentity("cronTrigger", "group1") .withSchedule(cronScheduleBuilder) .build(); } }
启动 Spring Boot 应用后,Quartz 会自动加载并启动这个定时任务。
总结与最佳实践
- 核心思想:
Scheduler调度Job,Trigger决定何时调度。 - API选择: 优先使用
JobBuilder和TriggerBuilder,它们更现代、更安全。 - 状态管理: 不要在
Job中使用成员变量,如需保存状态,使用JobDataMap和@PersistJobDataAfterExecution注解。 - 表达式: 熟练掌握 Cron 表达式,它是复杂调度的利器。
- 集成: 在企业级应用中,强烈推荐使用 Spring/Spring Boot 集成,以简化配置并利用 Spring 生态。
- 持久化: 对于需要高可用性的应用,配置 Quartz 使用数据库作为
JobStore(如JobStoreTX或JobStoreCMT),这样即使应用重启,任务也不会丢失。 - 关闭: 应用关闭时,务必调用
scheduler.shutdown(true)优雅地关闭调度器。
Quartz 是一个非常成熟和强大的调度框架,掌握它对于任何Java开发者来说都是一项非常有用的技能,希望这份详细的指南能帮助你快速上手!
