持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
引言
使用Spring的任务调度实现定时任务能够给我们的开发带来了极大的便利,不过当我们的任务调度配置完成后,需要在之前设定下的下次任务执行新的cron表达式,新的配置才能生效,做不到立即生效。如果要立即生效就只能停止服务器。修改源码,再重启服务器等一连串繁琐的操作。下面为大家提供一种Spring内置任务调度实现添加、取消、重置的方法。
传统scheduledTaskRegistrar.addTriggerTask()的缺陷
我们先看看使用scheduledTaskRegistrar.addTriggerTask()方法的代码
scheduledTaskRegistrar.addTriggerTask(baskTask,sheduledConfig->{
Date date = new CronTrigger(baskTask.getCron()).nextExecutionTime(sheduledConfig);
return date;
});
使用scheduledTaskRegistrar.addTriggerTask()确实可以帮我们实现定时任务,但是也存在上述缺陷,需要等定时任务下次执行获取到最新的cron表达式才可以实现修改调度的时间。于是,这种方法不太推荐使用,我们需要一种可添加可删除可修改的方法去实现新的时间调度。
解决思路
我们可以基于 SchedulingConfigurer 的源码,捕获 ScheduledTaskRegistrar 类的实例,通过该类中的 TaskScheduler 实例操作定时任务的增删,而非采用 ScheduledTaskRegistrar.addTriggerTask 方法维护定时任务。
通过查看一下ScheduledTaskRegistrar
源码,我们发现该对象初始化完成后会执行scheduleTasks()
方法,在该方法中添加任务调度信息,最终所有的任务信息都存放在名为scheduledFutures
的集合中。
再通过查看scheduledFutures的方法
我们可以清楚的看到cancel()的方法。所以,我们可以将所有的feature统一存在一个map中,再通过任务的名字去获取对应的feature,实现任务调度的取消和增加
/**
* 定时任务类
* @author lyxrunrunrun
* Date:2022/9/26 10:34
*/
@EnableScheduling
@Slf4j
@Configuration
public class TaskConfig implements SchedulingConfigurer {
@Autowired
private ApplicationContext applicationContext;
// 句柄,方便后期获取 future
private TaskScheduler taskScheduler;
private static final Map<String, ScheduledFuture> FUTURE_MAP = new ConcurrentHashMap<>();
/**
* 线程池任务调度器
* <p>
* 支持注解方式,@Scheduled(cron = "0/5 * * * * ?")
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1);
scheduler.setThreadNamePrefix("TaskScheduler-");
scheduler.setRemoveOnCancelPolicy(true); // 保证能立刻丢弃运行中的任务
taskScheduler = scheduler; // 获取 句柄,方便后期获取 future
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(taskScheduler()); // 不用担心,这里的scheduler跟 上面的注解 Bean 是同一个对象,亲自打断点验证
//获取所有的定时任务
Map<String, BaskTask> map = applicationContext.getBeansOfType(BaskTask.class);
//遍历注册
for(String key :map.keySet()){
BaskTask baskTask = map.get(key);
//baskTask:回调内部的run方法
//baskTask.getCron() 获取各个任务的执行频率
// scheduledTaskRegistrar.addTriggerTask 不推荐,因为无法获取 调度任务的 future 对象。
ScheduledFuture<?> schedule = taskScheduler.schedule(baskTask,sheduledConfig->{
Date date = new CronTrigger(baskTask.getCron()).nextExecutionTime(sheduledConfig);
return date;
});
FUTURE_MAP.put(key, schedule);
}
}
//取消限时任务
public void cancel(String type){
if (!FUTURE_MAP.containsKey(type)) {
return;
}
//BUG 修复
ScheduledFuture future = FUTURE_MAP.get(type);
if (future != null) {
future.cancel(true);
}
System.out.println("删除成功");
}
//重启该限时任务
public void addTask(String type){
//获取所有的定时任务
Map<String, BaskTask> map = applicationContext.getBeansOfType(BaskTask.class);
BaskTask baskTask = map.get(type);
//baskTask:回调内部的run方法
//baskTask.getCron() 获取各个任务的执行频率
// scheduledTaskRegistrar.addTriggerTask 不推荐,因为无法获取 调度任务的 future 对象。
ScheduledFuture<?> schedule = taskScheduler.schedule(baskTask,sheduledConfig->{
Date date = new CronTrigger(baskTask.getCron()).nextExecutionTime(sheduledConfig);
return date;
});
FUTURE_MAP.put(type, schedule);
System.out.println("修改成功");
}
//修改限时任务
public void updateTask(String type){
//取消该定时任务
cancel(type);
//重启该定时任务
addTask(type);
}
}
通过这样的配置就可以实现对任务的修改和删除了,相比scheduledTaskRegistrar.addTriggerTask需要自行写代码维护定时任务列表,控制任务的删减,代码的实现比较繁琐,但也能很方便的实现定时任务的调度。
BaskTask源码
public interface BaskTask extends Runnable {
//获取执行频率
String getCron();
//执行任务逻辑
void execute();
}
TaskOne源码
@Slf4j
@Component
@Data
public class TaskOne implements BaskTask {
private String cron;
//外部获取执行频率
@Override
public String getCron() {
return cron;
}
@Override
public void execute() {
//可写复杂的任务逻辑
System.out.println("执行定时任务");
}
//执行任务
@Override
public void run() {
execute();
}
}
查看结果
通过ApiFox发送请求
这样我们就实现了动态任务的添加、取消、重置。通过查看源代码的方式实现,对于初学者有一定难度,需要积极而且耐心的试错,如有更好的解决方案,欢迎指正。