探索動態執行的計劃任務-DynamicSchedule
背景
在現代軟件開發中,計劃任務是一種常見的需求。無論是定時發送郵件、定期清理緩存,還是執行數據同步,計劃任務都能幫助我們自動化這些重復性工作。
最近有一個需求,用戶想要自己設定定時時間,來動態的執行定時任務。 很離譜,原來每天晚上12點定時執行的幾個數據同步、數據清理任務,想不通用戶要這個功能干啥!!!
探索歷程
原本的cron表達式,是直接寫死到代碼里的,顯然不能動態的修改。
如果采用配置文件的方式,每次改動要重啟項目,或者再寫個定時任務,每秒讀取文件內容,也不太合適。
如果引入分布式任務調度平臺,比如xxl-job、power-job、snail-job,又覺得太復雜。
選擇采用放到數據庫的方式,實現過程中,發現并不是很順利,寫一篇文章記錄一下這次的過程。
原本的實現
@Scheduled(cron = "0/5 * * * * *")
public void demo() {
System.out.println(LocalDateTime.now());
}結果
圖片
動態設置
配置類
@Component
@RequiredArgsConstructor
public class JobConfig implements SchedulingConfigurer {
private final ITestJobService jobService;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
//1.添加任務內容(Runnable)
() -> System.out.println("執行動態定時任務1: " + LocalDateTime.now()),
//2.設置執行周期(Trigger)
triggerContext -> {
TestJob job = jobService.getById(1L);
return new CronTrigger(job.getCron()).nextExecutionTime(triggerContext).toInstant();
}
);
}
}修改入口
@GetMapping("upd")
public String upd(@RequestParam("cron") String cron) {
jobService.updateById(new TestJob(1, cron));
System.out.println("修改時間:"+ LocalDateTime.now());
return "success";
}將 0/10 * * * * * 改為 0/5 * * * * *
結果
圖片
可以看出來 修改的時間是 15:01 ,但是下次執行時間還是間隔了10秒,第二次之后的時間才是間隔5秒。 更新結果有一個周期的延遲。
在這種情況下,延遲還算可以接收,但是周期如果是一天、一周,那生效周期就太長了,需要一種即時生效的方法。
即時生效
實現方案是,以事件驅動,動態修改定時任務。
定義事件
@Getter
public class ScheduleTaskUpdateEvent extends ApplicationEvent {
private final Integer taskId;
public ScheduleTaskUpdateEvent(Object source, Integer taskId) {
super(source);
this.taskId = taskId;
}
}構造調度任務程序
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 設置線程池大小
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
return scheduler;
}
}動態任務配置
@Component
public class DynamicScheduleTaskConfig implements ApplicationListener<ScheduleTaskUpdateEvent> {
@Resource
private ITestJobService jobService;
@Resource
private TaskScheduler taskScheduler;
private final Map<Integer, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
@PostConstruct
private void initializeTasks() {
List<TestJob> list = jobService.list();
list.forEach(job -> {
ScheduledFuture<?> future = scheduleTask(job);
scheduledTasks.put(job.getId(), future);
});
}
// 根據任務配置創建任務
private ScheduledFuture<?> scheduleTask(TestJob job) {
System.out.println("創建新的定時任務,id:" + job.getId() + ", cron: " + job.getCron());
return taskScheduler.schedule(
() -> System.out.println("執行動態定時任務2: " + LocalDateTime.now()),
triggerContext -> {
return new CronTrigger(job.getCron()).nextExecutionTime(triggerContext).toInstant();
}
);
}
@Override
public void onApplicationEvent(ScheduleTaskUpdateEvent event) {
System.out.println("收到修改定時任務事件,任務id:" + event.getTaskId());
// 取消并移除舊任務
ScheduledFuture<?> future = scheduledTasks.get(event.getTaskId());
if (future != null) {
future.cancel(false);
scheduledTasks.remove(event.getTaskId());
}
// 獲取最新的任務配置并重新注冊該任務
TestJob job = jobService.getById(event.getTaskId());
ScheduledFuture<?> newFuture = scheduleTask(job);
scheduledTasks.put(job.getId(), newFuture);
}
}修改接口,增加事件
@GetMapping("upd")
public String upd(@RequestParam("cron") String cron) {
jobService.updateById(new TestJob(1, cron));
eventPublisher.publishEvent(new ScheduleTaskUpdateEvent(this, 1));
System.out.println("修改時間:"+ LocalDateTime.now());
return "success";
}結果
圖片
可以看到,在收到修改任務的事件后,直接刪除了原來的定時任務,創建了一個新的執行任務,即時生效,不需要等待一個執行周期就可立即執行。
小結
通過上述方法,我們可以在 Spring Boot 應用中實現動態計劃任務,使得任務的執行更加靈活可控。
還實驗了幾種不同的方式,比如每秒輪詢數據庫、手動計算cron表達式 的執行時間。感覺就屬這個事件驅動的方式最優雅。

























