- 核心概念:CronTrigger 和 Cron 表达式
- Cron 表达式详解
- Quartz 中 Cron 表达式的特殊之处
- Java 代码示例
- 常用 Cron 表达式速查表
- 最佳实践和常见问题
核心概念:CronTrigger 和 Cron 表达式
在 Quartz 中,触发器 决定了 Job(任务) 何时以及如何执行。CronTrigger 是最常用的一种触发器,它通过一个字符串表达式来定义一个复杂的调度规则,这个字符串就是 Cron 表达式。

一个 Cron 表达式由 6-7 个子表达式组成,分别代表不同的时间单位。
Cron 表达式详解
标准的 Cron 表达式由 6 个或 7 个字段组成,格式如下:
[秒] [分] [时] [日] [月] [周] [年]
注意: Quartz 的 Cron 表达式比标准的 Unix V7 Cron 表达式多了一个 “秒” 字段,这是最需要注意的区别。
| 字段 | 允许的值 | 特殊字符 |
|---|---|---|
| 秒 | 0-59 |
|
| 分 | 0-59 |
|
| 时 | 0-23 |
|
| 日 | 1-31 |
, - * ? / L W |
| 月 | 1-12 或 JAN-DEC |
|
| 周 | 1-7 或 SUN-SAT |
, - * ? / L # |
| 年 (可选) | 1970-2099 |
特殊字符说明:
- (星号):代表该字段的所有可能值。 在 “分” 字段表示 “每一分钟”。
- (逗号):用于列出多个值。
MON,WED,FRI在 “周” 字段表示 “每周的周一、周三、周五”。 - (连字符):用于定义一个范围。
1-5在 “日” 字段表示 “从1号到5号”。 - (斜杠):用于指定一个步长。
0/15在 “分” 字段表示 “从0分钟开始,每隔15分钟”(即 0, 15, 30, 45)。*/5等同于0/5。 - (问号):仅用于“日”和“周”字段,它表示“不指定值”,用于解决这两个字段在逻辑上的冲突,如果你想在“每月的10号”执行,但不确定是星期几,日”字段填
10,“周”字段就必须填 。 L(Last):仅用于“日”和“周”字段。- 在“日”字段,
L表示 “该月的最后一天”。1L表示 “每月1号的最后一天”,这通常就是指每月最后一天。 - 在“周”字段,
L表示 “该周的最后一天”,即星期六(7或SAT)。2L表示 “每月的最后一个星期一”。
- 在“日”字段,
W(Weekday):仅用于“日”字段,表示离指定日期最近的工作日(周一到周五)。15W表示 “离15号最近的工作日”,如果15号是周六,则触发会在14号(周五);如果是周日,则触发会在16号(周一)。- (井号):仅用于“周”字段,表示该月的第几个星期几,格式为
#X,X是第几个。6#3表示 “该月的第三个星期五”(因为周五是第6天)。2#1表示 “该月的第一个星期一”。
Quartz 中 Cron 表达式的特殊之处
这是最关键的一点,也是很多人容易出错的地方。

Quartz 对 Cron 表达式的解析是“严格”的,而不是“宽松”的。
- “宽松”解析 (Lenient):像一些 Linux 的
cron守护进程,如果你写0 0 31 2 *,它会自动理解为 “2月31日”,然后自动修正为 “3月3日”。 - “严格”解析 (Strict):Quartz 不会这样做,如果你写
0 0 31 2 *,Quartz 会检查2月是否有31号,发现没有,就会直接 抛出异常,导致你的调度器无法启动。
在 Quartz 中,使用 L 来表示某月的最后一天是更安全、更推荐的做法。
- 推荐:
0 0 0 L * ?(每天午夜,在该月的最后一天执行) - 不推荐:
0 0 0 31 2 ?(可能会在2月没有31号时出错)
Java 代码示例
下面是如何在 Java 代码中使用 Cron 表达式来创建和配置一个 CronTrigger。
步骤 1:创建一个 Job
你需要一个实现了 org.quartz.Job 接口的类。
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 这里是你要执行的任务逻辑
System.out.println("Hello, Quartz! MyJob is running at: " + new Date());
}
}
步骤 2:创建 Scheduler 并配置 Trigger
这是配置 Cron 表达式的核心部分。
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
public class QuartzCronExample {
public static void main(String[] args) throws SchedulerException {
// 1. 创建一个 SchedulerFactory
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// 2. 从工厂中获取一个 Scheduler 实例
Scheduler scheduler = schedulerFactory.getScheduler();
// 3. 创建 JobDetail,并绑定我们的 Job 类
// JobDetail 是一个 Job 的详细配置信息
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1") // 给 Job 一个名称和组名
.build();
// 4. 定义 Cron 表达式
// 示例:每天上午10点30分执行
String cronExpression = "0 30 10 * * ?";
// 5. 创建 Trigger,并将 Cron 表达式设置进去
// Trigger 决定了 Job 何时执行
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1") // 给 Trigger 一个名称和组名
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
// 6. 将 Job 和 Trigger 关联并调度到 Scheduler 中
scheduler.scheduleJob(jobDetail, cronTrigger);
// 7. 启动 Scheduler
scheduler.start();
System.out.println("Scheduler started. Job scheduled with cron expression: " + cronExpression);
// 为了让程序不立即退出,可以让它等待一段时间
try {
Thread.sleep(60000); // 等待60秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 8. 关闭 Scheduler
scheduler.shutdown();
System.out.println("Scheduler shutdown.");
}
}
更复杂的 Cron 表达式示例
// 每隔5秒执行一次 String cron1 = "0/5 * * * * ?"; // 每天的上午8点到下午5点,每隔半小时执行一次 String cron2 = "0 0/30 8-17 * * ?"; // 每个月的1号和15号的上午10点执行 String cron3 = "0 0 10 1,15 * ?"; // 每周的周一至周五的下午5点执行 String cron4 = "0 0 17 ? * MON-FRI"; // 每月的最后一个周五的下午3点执行 String cron5 = "0 0 15 ? * 6L"; // 6代表周五(SAT), L代表Last // 每月的第三个周三的上午11点执行 String cron6 = "0 0 11 ? * 3#3"; // 3代表周三(WED), #3代表第三个
常用 Cron 表达式速查表
| 需求 | Cron 表达式 | 说明 |
|---|---|---|
| 每秒执行 | 0/1 * * * * ? |
每秒执行一次 |
| 每5秒执行 | 0/5 * * * * ? |
从0秒开始,每5秒执行一次 |
| 每分钟执行 | 0 * * * * ? |
每分钟的0秒执行 |
| 每5分钟执行 | 0 0/5 * * * ? |
每小时的0, 5, 10, ... 55分执行 |
| 每小时执行 | 0 0 * * * ? |
每小时的0分0秒执行 |
| 每天午夜执行 | 0 0 0 * * ? |
每天凌晨0点执行 |
| 每天中午12点执行 | 0 0 12 * * ? |
每天12:00执行 |
| 每天上午10:30执行 | 0 30 10 * * ? |
每天10:30执行 |
| 每周一上午9点执行 | 0 0 9 ? * MON |
每周一9:00执行 |
| 每天上午8点到下午5点,每小时执行 | 0 0 8-17 * * ? |
每天8, 9, 10, ..., 17点整执行 |
| 每个月1号的凌晨1点执行 | 0 0 1 1 * ? |
每月1号1:00执行 |
| 每个月的最后一天23:59:59执行 | 0 59 23 L * ? |
每月最后一天的23:59:59执行 |
| 工作日(周一到周五)上午9点执行 | 0 0 9 ? * MON-FRI |
工作日的9:00执行 |
最佳实践和常见问题
-
测试 Cron 表达式:手动编写复杂的 Cron 表达式很容易出错,强烈建议使用在线 Cron 表达式生成器和测试工具,
-
处理时区:默认情况下,Quartz 使用调度器实例所在的 JVM 的默认时区来解析 Cron 表达式,这在分布式环境中可能会导致问题,因为不同服务器可能有不同的默认时区。 最佳实践是显式地设置时区:
// 在创建 Trigger 时设置时区 CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression) .inTimeZone(TimeZone.getTimeZone("Asia/Shanghai"))) // 显式设置时区为上海 .build(); -
避免过于频繁的 Job:不要设置每秒执行多次的 Job,这可能会给系统带来巨大压力,对于高频任务,考虑使用消息队列或其他更适合的技术。
-
Job 的状态管理:默认情况下,Job 实例在执行完毕后会被 Quartz 丢弃,如果需要在 Job 中保持状态,可以将状态存储在数据库或外部缓存中,而不是依赖 Job 实例本身。
希望这份详细的指南能帮助你完全掌握 Java Quartz 的时间设置!
