文档站点
Skip to content

任务调度 ​

Laravel 用 routes/console.php + Kernel 的 schedule() 定义计划任务,配 cron 驱动;Spring Boot 用 @Scheduled 注解,一个方法就是一个定时任务,无需额外进程。

三步启用 ​

1. 开启调度 ​

java
@Configuration
@EnableScheduling
public class SchedulingConfig { }

2. 写定时任务 ​

java
@Component
public class ReportJob {

    @Scheduled(cron = "0 0 2 * * ?")        // 每天凌晨 2 点
    public void generateDailyReport() {
        System.out.println("生成日报表...");
    }
}

3. 就完了 ​

无需 php artisan schedule:work,Spring Boot 应用启动后自带调度器线程。

对照 Laravel

LaravelSpring Boot
Kernel schedule() + cron 触发@Scheduled 注解
php artisan schedule:run(每分钟 cron 触发)应用内调度线程,无需外部 cron
->dailyAt('02:00')@Scheduled(cron = "0 0 2 * * ?")
->everyFiveMinutes()@Scheduled(fixedRate = 300000)

cron 表达式的坑:Java 六段式 ​

Laravel 用五段式 cron,Spring 用六段式(多了「秒」),开头结尾容易写错:

秒 分 时 日 月 周
0  0  2  *  *  ?
段含义
1秒(Spring 独有!Laravel 没有)
2分
3时
4日
5月
6周

两个最常踩的坑

  1. 多了一个「秒」:Laravel 的 0 2 * * *(凌晨 2 点)在 Spring 里要写成 0 0 2 * * ?
  2. 日和周互斥:两者同时填会冲突。Spring 用 ? 表示「不指定」:
    • 每天凌晨 2 点:0 0 2 * * ?
    • 每周一凌晨 2 点:0 0 2 ? * MON

常用调度写法 ​

需求Spring 写法
每天 02:00@Scheduled(cron = "0 0 2 * * ?")
每小时整点@Scheduled(cron = "0 0 * * * ?")
每分钟@Scheduled(fixedRate = 60000)
每 5 分钟@Scheduled(cron = "0 */5 * * * ?")
启动后 3 秒执行一次,之后每 10 秒@Scheduled(initialDelay = 3000, fixedRate = 10000)
每 10 秒(间隔从上一次完成算)@Scheduled(fixedDelay = 10000)
周一到周五 09:00@Scheduled(cron = "0 0 9 ? * MON-FRI")

fixedRate vs fixedDelay

  • fixedRate:固定频率,到点就执行(上一个没跑完也可能并发触发)
  • fixedDelay:上一个执行完后间隔再执行,天然串行 默认单线程,任务耗时长的选 fixedDelay 或配置线程池。

任务执行在哪个线程 ​

默认单线程执行所有定时任务,一个任务卡住会阻塞其他任务。配置线程池:

yaml
spring:
  task:
    scheduling:
      pool:
        size: 5

从数据库读取 cron(动态调度) ​

@Scheduled 的 cron 在编译期写死。要「运行时可改」的定时任务(比如后台配置),用 SchedulingConfigurer:

java
@Component
public class DynamicJob implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addCronTask(() -> System.out.println("动态任务执行"),
                () -> "0 */30 * * * ?");      // 每次执行前查库拿最新 cron
    }
}

TIP

configureTasks 里的 cron 提供函数每次执行前都会调用,所以你可以在 lambda 里查数据库/配置中心拿到最新 cron。这是 Spring 应对「配置化定时任务」的标准姿势。

分布式部署:别重复执行 ​

单实例没问题;多实例部署时,每个实例都会跑同一份 @Scheduled,任务会重复执行。

解决:引入分布式锁。轻量方案用 Redis 锁:

java
@Scheduled(cron = "0 0 2 * * ?")
public void dailyJob() {
    // 用 Redis setnx 抢锁,抢到才执行(伪代码)
    boolean locked = redisLock.tryLock("job:daily", Duration.ofMinutes(10));
    if (!locked) return;
    try {
        generateReport();
    } finally {
        redisLock.unlock("job:daily");
    }
}

更省心的选择

引入 spring-boot-starter-quartz,用 Quartz 的集群模式(基于数据库锁)天然避免重复调度。但若项目只是简单任务,Redis 锁就够了,别为调度器引入重量级依赖。

对照速查 ​

需求LaravelSpring Boot
定时任务Kernel schedule()@Scheduled
cron 格式五段式六段式(多秒)
指定时间->dailyAt('02:00')cron = "0 0 2 * * ?"
固定间隔->everyFiveMinutes()fixedRate = 300000
启动延迟->onOneServer() 等initialDelay
单实例限制->onOneServer()Redis 锁 / Quartz 集群
动态 cron较麻烦SchedulingConfigurer

面向 PHP / Laravel 开发者的 Spring Boot 中文文档