杰瑞科技汇

Java Quartz 时间设置如何精准配置?

  1. 核心概念:CronTrigger 和 Cron 表达式
  2. Cron 表达式详解
  3. Quartz 中 Cron 表达式的特殊之处
  4. Java 代码示例
  5. 常用 Cron 表达式速查表
  6. 最佳实践和常见问题

核心概念:CronTrigger 和 Cron 表达式

在 Quartz 中,触发器 决定了 Job(任务) 何时以及如何执行。CronTrigger 是最常用的一种触发器,它通过一个字符串表达式来定义一个复杂的调度规则,这个字符串就是 Cron 表达式

Java Quartz 时间设置如何精准配置?-图1
(图片来源网络,侵删)

一个 Cron 表达式由 6-7 个子表达式组成,分别代表不同的时间单位。


Cron 表达式详解

标准的 Cron 表达式由 6 个或 7 个字段组成,格式如下:

[秒] [分] [时] [日] [月] [周] [年]

注意: Quartz 的 Cron 表达式比标准的 Unix V7 Cron 表达式多了一个 “秒” 字段,这是最需要注意的区别。

字段 允许的值 特殊字符
0-59
0-59
0-23
1-31 , - * ? / L W
1-12JAN-DEC
1-7SUN-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 表示 “该周的最后一天”,即星期六(7SAT)。2L 表示 “每月的最后一个星期一”。
  • W (Weekday):仅用于“日”字段,表示离指定日期最近的工作日(周一到周五)。15W 表示 “离15号最近的工作日”,如果15号是周六,则触发会在14号(周五);如果是周日,则触发会在16号(周一)。
  • (井号):仅用于“周”字段,表示该月的第几个星期几,格式为 #XX 是第几个。6#3 表示 “该月的第三个星期五”(因为周五是第6天)。2#1 表示 “该月的第一个星期一”。

Quartz 中 Cron 表达式的特殊之处

这是最关键的一点,也是很多人容易出错的地方。

Java Quartz 时间设置如何精准配置?-图2
(图片来源网络,侵删)

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执行

最佳实践和常见问题

  1. 测试 Cron 表达式:手动编写复杂的 Cron 表达式很容易出错,强烈建议使用在线 Cron 表达式生成器和测试工具,

    • CronMaker
    • Cron Guru 这些工具可以让你直观地看到表达式对应的未来执行时间。
  2. 处理时区:默认情况下,Quartz 使用调度器实例所在的 JVM 的默认时区来解析 Cron 表达式,这在分布式环境中可能会导致问题,因为不同服务器可能有不同的默认时区。 最佳实践是显式地设置时区

    // 在创建 Trigger 时设置时区
    CronTrigger cronTrigger = TriggerBuilder.newTrigger()
            .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)
                    .inTimeZone(TimeZone.getTimeZone("Asia/Shanghai"))) // 显式设置时区为上海
            .build();
  3. 避免过于频繁的 Job:不要设置每秒执行多次的 Job,这可能会给系统带来巨大压力,对于高频任务,考虑使用消息队列或其他更适合的技术。

  4. Job 的状态管理:默认情况下,Job 实例在执行完毕后会被 Quartz 丢弃,如果需要在 Job 中保持状态,可以将状态存储在数据库或外部缓存中,而不是依赖 Job 实例本身。

希望这份详细的指南能帮助你完全掌握 Java Quartz 的时间设置!

分享:
扫描分享到社交APP
上一篇
下一篇