定时任务
开启定时任务
在配置类或启动类上加 @EnableScheduling,然后在任意受管 Bean 的方法上标注 @Scheduled 即可。
三种触发方式
@Component
public class ReportTask {
// 固定频率:上次开始执行后 5 秒再执行下一次
@Scheduled(fixedRate = 5000)
public void every5s() { }
// 固定延迟:上次执行结束后 5 秒再执行下一次
@Scheduled(fixedDelay = 5000)
public void delay5s() { }
// 首次延迟 10 秒后开始,之后按 fixedDelay 运行
@Scheduled(initialDelay = 10000, fixedDelay = 5000)
public void afterStartup() { }
// cron 表达式
@Scheduled(cron = "0 0 2 * * *") // 每天 02:00
public void dailyReport() { }
}
cron 由 6 段组成(与 Linux/Quartz 的 5 段不同):秒 分 时 日 月 周。常用示例:
| cron | 含义 |
|---|---|
0 0 2 * * * | 每天凌晨 2 点 |
0 */5 * * * * | 每 5 分钟 |
0 0 9 * * MON-FRI | 周一至周五 9 点 |
日期与星期的交叉限制等复杂规则,以及是否支持「年份」字段,以官方文档为准。需要显式指定时区时用 zone 属性:
@Scheduled(cron = "0 0 2 * * *", zone = "Asia/Shanghai")
cron 字符串支持属性占位符:@Scheduled(cron = "${task.report.cron:0 0 2 * * *}"),把表达式挪到配置文件更便于环境切换(见 05 章)。
线程模型
未自定义调度器时,Boot 自动配置的调度器默认单线程:多个 @Scheduled 任务会串行执行,一个任务耗时长会拖住其他任务。两个调整方向:
# 1) 调大自动配置的调度线程池
spring.task.scheduling.pool.size=4
// 2) 或自定义 TaskScheduler Bean,覆盖默认值
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setThreadNamePrefix("schedule-");
return scheduler;
}
相关属性的确切前缀与自动配置后退规则以官方文档为准。
常见坑
- 任务方法所在 Bean 必须被 Spring 管理;方法无参、无返回值(void),异常要在方法内自行处理避免影响调度器;
- 任务里做耗时操作(如大批量报表)会长期占用线程,考虑异步化(见 21 章)拆分;
- 多实例部署时每个实例都会执行同一份定时任务,需要分布式锁或统一调度平台(如 Quartz 集群方案)来保证只执行一次,属于部署架构问题,不在单机注解方案能力范围内;
- cron 按服务器本地时区解析,跨时区部署记得用
zone。
小结
@EnableScheduling + @Scheduled 是最轻量的定时方案:先分清 fixedRate/fixedDelay 的语义差别,再记住默认单线程、多实例会重复执行这两个坑,就可以安全落地大部分周期任务。