2. Spring Boot 定时任务基础:@Scheduled 注解详解

在 Spring Boot 中,@Scheduled 注解是声明式定时任务的核心入口。只需在方法上添加该注解,并配合 cron 表达式或固定速率参数,即可让 Spring 容器在后台线程池中按计划执行该方法。本章将深入拆解 @Scheduled 的四种调度模式:cron、fixedRate、fixedDelay、initialDelay,并通过对比表格与流程图帮助理解其差异。

2.1 启用定时任务:@EnableScheduling

在使用 @Scheduled 之前,必须在任意 @Configuration 类上添加 @EnableScheduling 注解,以激活 Spring 的任务调度能力。

@Configuration
@EnableScheduling
public class SchedulerConfig {
    // 定时任务方法定义在其他 Bean 中
}
提示: 如果忘记添加 @EnableScheduling,所有 @Scheduled 方法都不会被触发,这是故障排查中最容易被忽略的第一步。

2.2 四种调度模式详解

2.2.1 cron 表达式

cron 表达式由 6 或 7 个字段组成,精确控制秒、分、时、日、月、周几的执行时间点。Spring 的 cron 支持标准 Linux cron 语法,并额外支持第 7 位(年,可省略)。

位置 字段 允许值 特殊字符
1 秒 0-59 , - * /
2 分 0-59 , - * /
3 时 0-23 , - * /
4 日 1-31 , - * ? / L W
5 月 1-12 或 JAN-DEC , - * /
6 周几 0-7 或 SUN-SAT , - * ? / L #
// 每天上午 10:15 执行
@Scheduled(cron = "0 15 10 * * ?")
public void dailyReport() {
    System.out.println("生成日报:" + LocalDateTime.now());
}

// 每 5 分钟执行一次(从 0 秒开始)
@Scheduled(cron = "0 */5 * * * ?")
public void everyFiveMinutes() {
    System.out.println("每5分钟任务:" + LocalDateTime.now());
}
注意: cron 表达式中的 ? 用于日或周几字段,表示“不指定值”,避免与 * 冲突。Spring 中 0 0 12 * * ? 表示每天中午 12 点执行,而 0 0 12 * * * 会报错,因为日和周几不能同时为 *。

2.2.2 fixedRate

fixedRate 以任务开始时间为基准,按固定间隔触发下一次任务。如果任务执行时间超过间隔,下一次任务会等待当前任务结束后立即执行(不会并发重叠)。

// 每 5 秒执行一次(从任务开始计时)
@Scheduled(fixedRate = 5000)
public void rateTask() {
    System.out.println("fixedRate 任务开始:" + LocalDateTime.now());
    // 模拟耗时 3 秒
    sleep(3000);
    System.out.println("fixedRate 任务结束:" + LocalDateTime.now());
}

2.2.3 fixedDelay

fixedDelay 以上一次任务结束时间为基准,等待指定延迟后再开始下一次任务。两次执行之间始终有固定的空闲间隔。

// 上一次任务结束后,等待 5 秒再执行下一次
@Scheduled(fixedDelay = 5000)
public void delayTask() {
    System.out.println("fixedDelay 任务开始:" + LocalDateTime.now());
    // 模拟耗时 3 秒
    sleep(3000);
    System.out.println("fixedDelay 任务结束:" + LocalDateTime.now());
}

2.2.4 initialDelay

initialDelay 用于延迟首次执行时间,常与 fixedRate 或 fixedDelay 配合使用,避免应用启动后立即触发耗时任务。

// 启动后等待 10 秒,然后每 5 秒执行一次
@Scheduled(fixedRate = 5000, initialDelay = 10000)
public void delayedRateTask() {
    System.out.println("延迟启动的 fixedRate 任务:" + LocalDateTime.now());
}

2.3 四种模式对比

模式 触发基准 任务超长时行为 典型场景
cron 绝对时间点 错过执行点,等待下一个周期 固定时间点执行(如每天凌晨 2 点)
fixedRate 任务开始时间 当前任务结束后立即执行下一次 心跳检测、状态上报
fixedDelay 任务结束时间 等待固定延迟后再执行 数据同步、批量处理
initialDelay 应用启动时间 仅影响首次执行,后续按主模式 预热后启动的定时任务

2.4 调度时序对比图

下图展示了 fixedRate 与 fixedDelay 在任务耗时 3 秒、间隔 5 秒时的执行时序差异:

fixedRate vs fixedDelay 时序对比(任务耗时 3s,间隔 5s) fixedRate 任务1 任务2 任务3 间隔 5s 间隔 5s fixedDelay 任务1 等待 5s 任务2 等待 5s 任务3 🔵 fixedRate:任务1开始后 5s 触发任务2,若任务1耗时 3s,则任务2在任务1结束后 2s 开始。 🟢 fixedDelay:任务1结束后等待 5s 再触发任务2,两次执行之间始终有 5s 空闲间隔。

2.5 常见故障场景与排查要点

  • 任务未执行: 检查是否添加 @EnableScheduling;确认 cron 表达式是否正确(尤其注意 ? 与 * 的混用)。
  • 任务执行间隔异常: 如果使用 fixedRate 但任务耗时超过间隔,实际间隔会缩短为 0(连续执行);改用 fixedDelay 可保证固定空闲时间。
  • 首次执行延迟: 应用启动后立即执行的任务可能因依赖未就绪而失败,使用 initialDelay 给系统预热时间。
  • 时区问题: cron 表达式默认使用服务器时区,可通过 zone 属性指定时区:@Scheduled(cron = "0 0 8 * * ?", zone = "Asia/Shanghai")。
核心原则: 选择调度模式时,先问自己“触发基准是什么?”——是绝对时间点(cron)、任务开始时间(fixedRate)还是任务结束时间(fixedDelay)。理解这个基准,就能准确预测任务的实际执行行为。