这篇教程将从以下几个方面展开,让你对 Quartz 有一个从入门到精通的完整理解:

- 什么是 Quartz?
- 核心概念 (Job, Trigger, Scheduler)
- 快速入门:第一个 Quartz 程序
- 进阶:使用 Cron 表达式
- JobDataMap:传递数据给 Job
- Job 的持久化与集群
- Spring/Spring Boot 集成
- 总结与最佳实践
什么是 Quartz?
Quartz 是一个功能强大、开源、轻量级的作业调度框架,它完全由 Java 编写,它可以与任何 Java 应用程序(从最小的独立应用到最大的电子商务系统)集成,用于执行定时任务。
主要特点:
- 强大的调度功能:可以定义非常复杂的调度规则,每周五下午 3 点”或“每个月的最后一天”。
- 灵活的 Trigger:通过
Trigger(触发器)来控制 Job 的执行时间。 - 持久化:可以将 Job 和 Trigger 的信息保存到数据库中,实现任务的持久化和集群部署。
- 集群支持:通过数据库锁机制,可以搭建 Quartz 集群,确保高可用性和负载均衡。
- 集成性:可以非常方便地与 Spring、Spring Boot 等主流框架集成。
核心概念
理解 Quartz 的三个核心概念是掌握它的关键:
1 Job (任务)
Job 是一个接口,代表一个被调度的任务,你只需要实现这个接口,并在 execute() 方法中编写你的业务逻辑即可。

public interface Job {
void execute(JobExecutionContext context) throws JobExecutionException;
}
示例:一个简单的打印 "Hello, World!" 的 Job
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取当前时间
Date date = new Date();
// 打印 Hello World 和当前时间
System.out.println("Hello World! - " + date);
}
}
2 Trigger (触发器)
Trigger 用于定义 Job 的执行调度规则,它决定了 Job 何时 以及 如何 被执行。
Quartz 提供了两种主要的 Trigger:
-
SimpleTrigger:用于简单的调度,在指定的时间点执行一次,或者在指定的时间间隔内重复执行 N 次。
(图片来源网络,侵删)startTime: 开始时间endTime: 结束时间repeatInterval: 重复间隔(毫秒)repeatCount: 重复次数
-
CronTrigger:功能更加强大,使用 Cron 表达式来定义复杂的调度规则,这是最常用的一种 Trigger。
cronExpression: Cron 表达式,如"0/5 * * * * ?"表示每 5 秒执行一次。
3 Scheduler (调度器)
Scheduler 是 Quartz 的核心控制器,它负责将 Job 和 Trigger 绑定在一起,并根据 Trigger 的定义来执行 Job。
你可以将 Scheduler 想象成一个工厂车间,Job 是需要完成的工作,Trigger 是工作排班表,而 Scheduler 就是根据排班表来指挥和执行工作的车间主任。
快速入门:第一个 Quartz 程序
下面我们通过一个简单的例子,来演示如何创建一个 Job,并用 SimpleTrigger 来调度它。
步骤:
-
添加 Maven 依赖
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> <!-- 建议使用较新稳定版本 --> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.3.2</version> </dependency> -
创建 Job 类
就是上面提到的
HelloJob.java。 -
编写主程序来调度 Job
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; public class QuartzSimpleDemo { public static void main(String[] args) throws SchedulerException { // 1. 创建一个 SchedulerFactory(调度器工厂) SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // 2. 从工厂中获取一个 Scheduler 实例(调度器) Scheduler scheduler = schedulerFactory.getScheduler(); // 3. 创建 JobDetail(任务详情) // JobDetail 是 Job 的详细信息,包括 Job 的类名、组名等 // 注意:这里使用 JobBuilder.newJob(HelloJob.class) 创建,Quartz 会在执行时通过反射创建 HelloJob 的实例 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity("job1", "group1") // 设置 Job 的名称和组名 .build(); // 4. 创建 Trigger(触发器) // 设置立即开始,然后每隔 2 秒执行一次,重复 5 次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") // 设置 Trigger 的名称和组名 .startNow() // 立即开始 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(2) // 每 2 秒执行一次 .withRepeatCount(5)) // 重复 5 次 (总共执行 6 次) .build(); // 5. 将 Job 和 Trigger 绑定到 Scheduler 中,并启动调度器 scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); // 主线程休眠,让调度器有足够的时间执行任务 try { Thread.sleep(20000); // 休眠 20 秒 } catch (InterruptedException e) { e.printStackTrace(); } // 关闭调度器 scheduler.shutdown(); } }
运行结果:
你会看到控制台每隔 2 秒打印一次 "Hello World!",共打印 6 次(包括立即执行的那一次),然后程序结束。
进阶:使用 Cron 表达式
Cron 表达式是 Quartz 最强大的功能,它定义了一套时间匹配规则。
1 Cron 表达式格式
一个标准的 Cron 表达式由 6-7 个子表达式组成,中间用空格隔开:
[秒] [分] [小时] [日] [月] [周] [年]
字段说明:
| 字段 | 允许值 | 允许的特殊字符 |
|---|---|---|
| 秒 | 0-59 | |
| 分 | 0-59 | |
| 小时 | 0-23 | |
| 日 | 1-31 | , - * ? / L W |
| 月 | 1-12 或 JAN-DEC | |
| 周 | 1-7 或 SUN-SAT | , - * ? / L # |
| 年 (可选) | 1970-2099 |
特殊字符含义:
- (星号):代表所有可能的值。 在“小时”字段表示“每小时”。
- (逗号):用于列出多个值。
MON, WED, FRI在“周”字段表示“周一、周三、周五”。 - (连字符):用于定义一个范围。
10-12在“小时”字段表示“10点到12点”。 - (斜杠):用于指定一个步长。
0/15在“分”字段表示“从0分开始,每15分钟一次”;*/5在“秒”字段表示“每5秒一次”。 - (问号):仅用于“日”和“周”字段,表示“不指定值”,这两个字段互斥,必须有一个为 。
L(Last):表示“。L在“日”字段表示“当月的最后一天”;L在“周”字段表示“周六(一周的最后一天)”。W(Weekday):表示“最近的工作日”。15W在“日”字段表示“离15号最近的工作日”。- (Hash):表示“第几个”。
6#3在“周”字段表示“每个月的第三个周五”。
2 Cron 表达式示例
| 表达式 | 说明 |
|---|---|
0 0 12 * * ? |
每天中午12点触发 |
0 15 10 ? * * |
每天10:15触发 |
0 15 10 * * ? |
每天10:15触发 |
0 15 10 * * ? * |
每天10:15触发 (带年) |
0 15 10 * * ? 2025 |
2025年每天10:15触发 |
0 * 14 * * ? |
每天下午2点到2点59分之间的每分钟触发 |
0 0/5 14 * * ? |
每天下午2点到2点55分之间的每5分钟触发 |
0 0/5 14,18 * * ? |
每天下午2点到2点55分和下午6点到6点55分之间的每5分钟触发 |
0 0-5 14 * * ? |
每天下午2点到2点05分之间的每分钟触发 |
0 10,44 14 ? 3 WED |
每年三月的周三下午2:10和2: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 2025-2025 |
2025年至2025年每月最后一个周五的上午10:15触发 |
0 15 10 ? * 6#3 |
每月第三个周五的上午10:15触发 |
3 使用 CronTrigger
将之前的 Quick Start 代码中的 SimpleTrigger 替换为 CronTrigger:
// ... (前面的 JobDetail 创建代码相同)
// 4. 创建 CronTrigger
// 设置 cron 表达式:每 5 秒执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
.build();
// ... (后面的绑定和启动代码相同)
JobDataMap:传递数据给 Job
有时候我们需要在调度任务时,向 Job 传递一些参数,Quartz 提供了 JobDataMap 来实现这个功能。
JobDataMap 是 java.util.Map 的一个实现,它可以在 Scheduler、JobDetail 和 Trigger 之间共享数据。
通过 JobDetail 传递
// 在主程序中
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJob", "group1")
.usingJobData("jobSays", "Hello World!") // 传递键值对
.usingJobData("myFloatValue", 3.14f) // 传递另一个键值对
.build();
// 在 Job 类中接收
public class DumbJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 从 JobExecutionContext 中获取 JobDataMap
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.out.println("Job Says: " + jobSays);
System.out.println("myFloatValue: " + myFloatValue);
}
}
通过 Trigger 传递
JobDetail 和 Trigger 中有同名的 key,Trigger 中的值会覆盖 JobDetail 中的值。
// 在主程序中
// JobDetail 中的数据
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJob", "group1")
.usingJobData("jobSays", "Hello from JobDetail")
.build();
// Trigger 中的数据
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.usingJobData("jobSays", "Hello from Trigger!") // 同名 key
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
// 在 Job 类中执行时,会获取到 Trigger 中的值
// 输出: Job Says: Hello from Trigger!
Job 的持久化与集群
默认情况下,Quartz 将 Job 和 Trigger 的信息保存在内存中,这种方式简单高效,但存在一个严重问题:如果应用程序重启,所有的定时任务都会丢失。
为了解决这个问题,Quartz 支持将数据持久化到数据库中。
1 持久化步骤
-
创建 Quartz 数据库表 Quartz 官方提供了各种数据库的 SQL 脚本,你可以在其官方仓库中找到:Quartz Database Tables 你需要根据你使用的数据库(如 MySQL, Oracle, PostgreSQL 等)选择对应的脚本并执行。
-
配置 Scheduler 在创建
SchedulerFactory时,需要指定一个配置文件,告诉 Quartz 使用数据库作为 JobStore。quartz.properties文件内容示例:# 实例化 Scheduler 时使用的属性 org.quartz.scheduler.instanceName = MyScheduler org.quartz.scheduler.instanceId = AUTO # 使用 JobStoreTX,表示使用 JDBC 事务 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX # 数据库驱动 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 数据库连接 URL org.quartz.jobStore.dataSource = myDS # 表名前缀,防止与现有表冲突 org.quartz.jobStore.tablePrefix = QRTZ_ # 是否是集群模式 org.quartz.jobStore.isClustered = true # 定义 DataSource org.quartz.dataSource.myDS.connectionProvider.driver = com.mysql.cj.jdbc.Driver org.quartz.dataSource.myDS.connectionProvider.URL = jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC org.quartz.dataSource.myDS.connectionProvider.user = root org.quartz.dataSource.myDS.connectionProvider.password = your_password
-
使用配置文件 在 Java 代码中,
StdSchedulerFactory会自动在 classpath 下查找quartz.properties文件。// 不再需要手动配置,SchedulerFactory 会加载 quartz.properties SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler();
2 集群原理
当 isClustered = true 时,Quartz 会启动集群模式,其工作原理如下:
- 数据库锁:每个节点(JVM)在执行 Job 之前,都需要在数据库的
LOCKS表中获取一个锁。 - 非阻塞获取:获取锁的过程是非阻塞的,一个节点成功获取锁后,其他节点会跳过这个 Job 的本次执行。
- 故障转移:如果一个节点宕机,它持有的锁会在超时后自动释放,其他健康的节点可以获取到锁,从而继续执行任务,实现了故障转移。
集群注意事项:
- 必须共享数据库:所有节点必须连接到同一个数据库实例。
- 时钟同步:集群中所有服务器的时钟必须尽量同步,否则可能导致调度混乱。
- 不要使用
@DisallowConcurrentExecution:在集群模式下,如果一个 Job 的执行时间很长,超过了其下次执行时间,集群中的其他节点可能会再次触发同一个 Job,导致并发执行。@DisallowConcurrentExecution只能保证单个 JVM 内不并发,无法保证集群环境。
Spring/Spring Boot 集成
在现代应用中,最常见的方式是通过 Spring 或 Spring Boot 来集成 Quartz。
1 Spring Boot 集成 (推荐)
Spring Boot 提供了 spring-boot-starter-quartz,使得集成变得异常简单。
步骤:
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> -
创建 Job 类
直接创建一个 Bean 即可,Spring Boot 会自动检测并注册它。
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.stereotype.Component; @Component // 标记为 Spring Bean public class MySpringJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Spring Boot Quartz Job is running at: " + new Date()); // 你可以在这里注入任何 Spring Bean // @Autowired private SomeService someService; } } -
配置定时任务
你可以通过配置类来动态创建和调度任务,也可以直接在
application.properties中配置简单的任务。通过配置类 (动态)
import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuartzConfig { @Bean public JobDetail myJobDetail() { // 使用 JobBuilder 创建 JobDetail return JobBuilder.newJob(MySpringJob.class) .withIdentity("myJob") // 任务名 .storeDurably() // 即使没有关联的 Trigger 也保留 .build(); } @Bean public Trigger myJobTrigger() { // 使用 TriggerBuilder 创建 Trigger return TriggerBuilder.newTrigger() .forJob(myJobDetail()) // 关联 Job .withIdentity("myTrigger") // 触发器名 .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) // 每10秒执行一次 .build(); } }通过
application.properties(静态)# 简单任务示例:每5秒执行一次名为 'simpleJob' 的任务 spring.quartz.job-name=simpleJob spring.quartz.job-group=myGroup spring.quartz.cron-expression=0/5 * * * * ?
这种方式非常适合简单的、固定的定时任务。
总结与最佳实践
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单、一次性、非关键任务 | java.util.Timer 或 ScheduledExecutorService |
轻量级,无需额外依赖,但功能简单,不支持复杂调度和持久化。 |
| 复杂调度、需要持久化、但无集群需求 | Quartz (单机模式) | 功能强大,支持 Cron 表达式,可持久化到数据库,重启后任务不丢失。 |
| 高可用、高并发的定时任务 | Quartz (集群模式) | 通过数据库锁和故障转移机制,确保任务在集群环境下稳定、不重复地执行。 |
| Spring/Spring Boot 应用 | Spring/Spring Boot 集成 Quartz | 与 Spring 生态无缝集成,可以方便地注入 Spring Bean,配置简单,是现代 Java 应用的首选。 |
最佳实践:
- 保持 Job 的无状态:尽量避免在 Job 类中使用成员变量,如果必须使用,要确保线程安全,或者在
execute方法内部使用局部变量,因为 Quartz 在集群模式下可能会复用同一个 Job 实例。 - 合理使用
@DisallowConcurrentExecution:如果你的 Job 是非线程安全的,使用此注解可以防止在同一个 JVM 内并发执行,但在集群模式下要格外小心。 - Job 执行时间不宜过长:Job 执行时间过长,可能会影响后续任务的调度,或者在集群环境下导致任务被其他节点“抢走”。
- 监控与告警:为 Quartz 集群配置监控和告警机制,例如监控数据库连接池、节点存活状态、任务执行失败情况等。
- 优雅关闭:在应用关闭时,调用
scheduler.shutdown(true),等待正在执行的任务完成,而不是直接kill进程。
希望这份详细的教程能帮助你全面掌握 Java Quartz 定时器!
