定时任务在业务生产中有着十分重要的作用,Spring Boot中,基于@Scheduled注解可以快速地实现定时任务。,一般定时任务都是按固定的频率来执行,但是因为一些外部因素需要调整频率时,就得重新走一遍编码、打包、部署的流程。本文将与读者一探索如何解决定时器硬编码问题。
定时器中常用Cron表达式来配置定时任务执行时间,Cron是一种描述定时任务调度的字符串,非常灵活,我们本文的核心就是消除Cron表达式硬编码。对Cron表达式感兴趣的朋友,可以阅读之前的文章。
一、常规定时器配置
在了解动态定时器配置之前,我们先一起回顾一下,常规定时器任务配置是怎么实现的。
1、启动定时任务
在Spring Boot中可以通过 @EnableScheduling 注解开启定时任务。一般是在Spring Boot的这一类上添加此注解。
@SpringBootApplication
@EnableScheduling
public class CustomApplication {
public static void main(String[] args) {
SpringApplication.run(DuckdbApplication.class, args);
}
}
2、创建定时任务
创建定时任务是使用 @Scheduled 注解指定执行频率。我们将使用Cron表达式描述任务的调度时间。这里我们配置每20秒执行一次。
@Component
public class FixedScheduler {
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
@Scheduled(cron = "0/20 * * * * ?")
public void task1() {
System.out.println("任务1执行。当前时间:"+ dateTimeFormatter.format(LocalDateTime.now()));
}
}
项目启动后,执行结果如下:
任务1执行。当前时间:13:38:40
任务1执行。当前时间:13:39:00
任务1执行。当前时间:13:39:20
任务1执行。当前时间:13:39:40
任务1执行。当前时间:13:40:00
任务1执行。当前时间:13:40:20
任务1执行。当前时间:13:40:40
....
3、调度分析
上述示例是平时大家创建定时任务使用比较多的方式,这种方式,也是最常用的,但是我们想要更灵活的配置,比较增加一个状态位,根据状态位打开或关闭任务,或是改变执行频率就不能实现了,原因在于Cron表达式是硬编码。不能动态改变。
二、动态定时任务配置
动态定时任务配置与硬编码定时任务不同的地方在于任务的创建,第一步都是通过 @EnableScheduling 注解开启定时任务。下面我们创建一个动态定时任务。
/**
* @Author: 黑曜石
* @Date: 2024-11-12
* @Description: 创建动态定时任务
* @VERSION: 0.1
*/
@Component
public class DynamicScheduler {
@Autowired
ThreadPoolTaskScheduler taskScheduler;
private ScheduledFuture<?> scheduledFuture = null;
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
// 当前执行的Cron表达式
private String currCron;
/**
* 设置任务
* @param cron cron表达式
*/
public void setTask(String cron) {
this.currCron = cron;
pause();
System.out.println("==== 设置新任务("+cron+") =====");
// 创建定时任务
scheduledFuture = taskScheduler.schedule(
() -> System.out.println("任务2执行。当前时间:"+ dateTimeFormatter.format(LocalDateTime.now()))
, new CronTrigger(cron));
}
/**
* 暂停当前任务
*/
public void pause() {
if (scheduledFuture != null) {
// 停止之前的任务
scheduledFuture.cancel(false);
scheduledFuture = null;
}
}
/**
* 启动当前任务
*/
public void start() {
if (scheduledFuture == null && StringUtils.hasText(this.currCron)) {
setTask(currCron);
}
}
}
上述示例中,核心在于停止之前的任务和动态创建定时任务。
// 停止之前的定时任务
scheduledFuture.cancel(false);
// 创建新的定时任务
scheduledFuture = taskScheduler.schedule(
() -> System.out.println("任务2执行。当前时间:"+ dateTimeFormatter.format(LocalDateTime.now()))
, new CronTrigger(cron));
现在我们在项目启动后,调用 setTask 方法设置默认的定时任务,假设我们设置默认调度为20秒一次("0/20 * * * * ?")。在运行一段时间后我们修改为10秒一次("0/10 * * * * ?")。输出如下:
==== 设置新任务(0/20 * * * * ?) =====
任务2执行。当前时间:21:28:00
任务2执行。当前时间:21:28:20
任务2执行。当前时间:21:28:40
任务2执行。当前时间:21:29:00
==== 设置新任务(0/10 * * * * ?) =====
任务2执行。当前时间:21:29:20
任务2执行。当前时间:21:29:30
任务2执行。当前时间:21:29:40
任务2执行。当前时间:21:29:50
......
上述示例仅供参与,在实际生产中,大家可根据实际情况包装自己的类。在上述示例中,任务的实现是使用Lambda表达式,感兴趣的读者,可以参考之前的文章:Java后端:通过Function函数式接口与Lambda表达式简化代码