首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Spring Boot 动态定时任务,别再写死 cron 表达式了!

在 Spring Boot 项目里玩定时任务这事,真不是什么新鲜玩意儿了。大多数人一开始都是这么搞的:@Scheduled(cron = "..."),然后 cron 表达式直接写死在注解里,或者干脆放在配置文件里,启动时读取。

这么写没啥问题,任务能跑就行,但只适合那种“写死就完事”的场景。比如每天凌晨三点执行一个清理脚本,或者每小时跑一遍日志归档,这种频率不怎么改动的任务,固定配置确实很香。

但问题也随之而来:一旦你想在项目运行时修改任务的执行频率,比如从每 10 秒执行一次改成每 15 秒,咋办?重新改配置?重启服务?太原始了,哥!

那有没有一种办法,既能在项目启动时设置默认频率,又能在运行中随时动态修改,还不用重启?有的,今天咱就来搞定这事儿!

最普通的写法,问题在哪?

先说说常规操作,我们平时一般是这么写:

@Scheduled(cron = "0/10 * * * * ?")

public void task() {

  log.info("当前时间:" + LocalDateTime.now());

}

这种写法有什么问题?一旦 cron 需要变更,比如业务方说:

“现在流量变大了,任务不要 10 秒跑一次了,隔 30 秒跑一遍就行。”

你改了 cron,再怎么也得重启服务吧?这在很多生产环境下是完全不允许的,尤其是一些对可用性要求很高的系统,重启就是原罪。

所以,我们得搞一个可以“动态修改执行频率”的定时任务。

思路:自己管理调度器,让 cron 可修改

Spring Boot 本身提供了接口SchedulingConfigurer,通过实现它可以动态注册任务。

我们不直接用@Scheduled注解了,而是使用ScheduledTaskRegistrar来手动注册任务,并配合CronTrigger控制执行频率。

这个接口长这样:

public interface SchedulingConfigurer {

  void configureTasks(ScheduledTaskRegistrar taskRegistrar);

}

在configureTasks这个方法里,你可以随心所欲地指定任务内容、触发器类型、执行频率等等。比如下面这样,我们从配置文件里读取一个 cron 表达式:

@Component

@PropertySource("classpath:/task-config.ini")

@Slf4j

publicclass ScheduleTask implements SchedulingConfigurer {

  @Value("${printTime.cron}")

  private String cron;

  @Override

  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

      taskRegistrar.addTriggerTask(

          () -> log.info("当前时间:{}", LocalDateTime.now()),

          triggerContext -> {

              CronTrigger cronTrigger = new CronTrigger(cron);

              return cronTrigger.nextExecutionTime(triggerContext);

          });

  }

}

看清楚了没?关键是这个CronTrigger,它会在每次任务调度前根据当前的 cron 表达式来重新计算下一次的执行时间。也就是说,只要这个 cron 是个变量,你随时改它,任务执行频率就跟着变了。

那怎么改这个 cron 呢?来个接口啊!

说到这,不就是一个 set 方法的事嘛?写个 controller 让前端传个 cron 表达式,set 进去不就完了?

@RestController

@RequestMapping("/test")

publicclass TestController {

  privatefinal ScheduleTask scheduleTask;

  @Autowired

  public TestController(ScheduleTask scheduleTask) {

      this.scheduleTask = scheduleTask;

  }

  @GetMapping("/updateCron")

  public String updateCron(String cron) {

      log.info("新 cron 表达式:{}", cron);

      scheduleTask.setCron(cron);

      return"cron 修改成功!";

  }

}

你随便调用这个接口,比如:

GET /test/updateCron?cron=0/15 * * * * ?

然后后台任务就会变成每 15 秒执行一次,立刻生效,根本不用重启,优雅极了!

CronTrigger 的局限:只能精确到秒

不过,这种方案也有它的小缺陷。cron 表达式本质上是基于“时间点”触发的,而不是“时间间隔”触发的。

比如你写0/10 * * * * ?,意思是“每分钟的第 0 秒开始,每隔 10 秒执行一次”。那要是你服务在 00:00:07 启动,它可能要等到 00:00:10 才会第一次触发。

还有个问题是,它不太适合设置大于 59 秒的时间间隔。你要是想每 5 分钟执行一次,还得自己写成0 */5 * * * ?,不够直观。

所以,如果你想更精确地控制间隔时间,比如“启动后每隔 15 秒执行一次”,用 cron 反而不太灵活。

更灵活的选择:PeriodicTrigger

这时候,另一个 Trigger 登场了:PeriodicTrigger。这个触发器不像 cron 表达式那么死板,直接设置时间间隔,单位是毫秒,实用性超强。

我们来看代码:

@Component

@Slf4j

publicclass ScheduleTask implements SchedulingConfigurer {

  private Long timer = 10000L; // 默认10秒

  @Override

  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

      taskRegistrar.addTriggerTask(

          () -> log.info("当前时间:{}", LocalDateTime.now()),

          triggerContext -> {

              PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);

              return periodicTrigger.nextExecutionTime(triggerContext);

          });

  }

  public void setTimer(Long timer) {

      this.timer = timer;

  }

}

然后 controller 也照着加一个接口:

@GetMapping("/updateTimer")

public String updateTimer(Long timer) {

  log.info("新的定时时间间隔:{}", timer);

  scheduleTask.setTimer(timer);

  return "定时时间修改成功!";

}

你可以这样调用:

GET /test/updateTimer?timer=15000

后台任务立刻就变成 15 秒执行一次,配合日志一看,非常清楚。

到这一步,基本就搞定了

这套方案说白了,就是:不要用写死的注解,自己注册任务 + 自己控制调度器 + 自己开放配置接口,既灵活又可控。

你既可以用 cron 表达式实现复杂的时间点调度,比如“每个月最后一天23:59执行”,也可以用 PeriodicTrigger 实现纯间隔的任务,比如“每隔 20 秒执行一次”。

最后

这类需求其实在业务中还挺常见的,比如:

某个接口调用频率不稳定,需要根据系统压力动态调整任务间隔;

运维要求上线后能随时调整定时任务,而不希望改代码;

想做一个定时任务控制面板,让非技术人员都能操作调度频率。

这时候,如果你还在靠@Scheduled注解加个 cron 写死,真就是时代的眼泪了。

还是那句话:代码就该写得灵活点,自己爽,别人用起来也顺。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OLCmuzSJ68_MnCD2HzgLJIdg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券