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