關注公眾號 不愛總結的麥穗 將不定期推送技術好文
最近在做一個項目,需要用到動態定時任務,現在比較普遍的做法是集成第三方框架(例如Quartz、XXL-JOB),我自己在做這個項目的時候也考慮過去集成Quartz實現,但是基於項目本身的複雜度和使用場景放棄了
本文主要分享在不依賴過多的其他框架,使用springBoot自身帶有的定時任務框架來實現動態定時任務
註解實現定時任務
具體實現
主要基於@EnableScheduling和 @Scheduled註解
- 主啓動類上加上 @EnableScheduling 註解
- 寫一個類,注入到容器中,在方法上加上 @Scheduled 註解
@Slf4j
@Component
public class TimeTask {
@Scheduled(cron = "0 0/31 * * * ?")
public void refresh(){
log.info("//// 定時刷新");
//業務代碼
}
}
這樣就實現了定時任務,是不是很簡單?不難發現,這種方式定時執行時間是固定的,但是大部分業務的定時執行時間是經常在變化的,這時候我們就需要通過動態定時任務實現
實現動態定時任務
Spring實現動態定時任務的核心就是其提供的任務調度類ThreadPoolTaskScheduler,ThreadPoolTaskScheduler基於線程池來執行任務,可以按照固定的時間間隔或者指定的Cron表達式來調度任務的執行。
在我的項目中主要用到了ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法,指定的Cron表達式來調度任務的執行
具體實現
-
創建ThreadPoolTaskScheduler配置類
@Configuration public class SchedulingConfig { @Bean public TaskScheduler taskScheduler() { // 獲取系統處理器個數, 作為線程池數量 int corePoolSize = Runtime.getRuntime().availableProcessors(); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); // 定時任務執行線程池核心線程數 taskScheduler.setPoolSize(corePoolSize); taskScheduler.setRemoveOnCancelPolicy(true); taskScheduler.setThreadNamePrefix("AntiFraudSchedulerThreadPool-"); return taskScheduler; } } - 創建
ScheduledTask包裝類
ScheduledFuture是ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法的返回值。
ScheduledFuture繼承了Future接口,Future接口提供一組輔助方法,比如:
- cancel():取消任務
- isCancelled():任務是不是取消了
- isDone():任務是不是已經完成了
- get():用來獲取執行結果
當我們調用cancel方法時,會將我們的任務從workQueue中移除
public final class ScheduledTask {
public volatile ScheduledFuture<?> future;
/**
* 取消定時任務
*/
public void cancel() {
ScheduledFuture<?> scheduledFuture = this.future;
if (Objects.nonNull(scheduledFuture)) {
scheduledFuture.cancel(true);
}
}
}
- 創建
CronTaskRegistrar類
實現了DisposableBean接口的類,用於註冊定時任務。它具有添加、刪除和調度定時任務的方法。在銷燬時,會取消所有定時任務。
@Slf4j
@Component
@SuppressWarnings("all")
public class CronTaskRegistrar implements DisposableBean {
@Resource
private TaskScheduler taskScheduler;
// 保存任務Id和定時任務
private final Map<String, ScheduledTask> scheduledTaskMap = new ConcurrentHashMap<>(64);
// 添加任務
public void addTask(Runnable task, String cronExpression,String jobId) {
addTask(new CronTask(task, cronExpression),jobId);
}
public void addTask(CronTask cronTask,String jobId) {
if (Objects.nonNull(cronTask)) {
Runnable task = cronTask.getRunnable();
if (this.scheduledTaskMap.containsKey(task)) {
removeTask(jobId);
}
// 保存任務Id和定時任務
this.scheduledTaskMap.put(jobId, scheduleTask(cronTask));
}
}
// 通過任務Id,取消定時任務
public void removeTask(String jobId) {
ScheduledTask scheduledTask = this.scheduledTaskMap.remove(jobId);
if (Objects.nonNull(scheduledTask)) {
scheduledTask.cancel();
}
}
public ScheduledTask scheduleTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
// 銷燬
@Override
public void destroy() {
this.scheduledTaskMap.values().forEach(ScheduledTask::cancel);
this.scheduledTaskMap.clear();
}
}
動態定時任務的核心邏輯到這基本就已經完成了,具體的Service類代碼,這裏就不貼出來了,因為裏面基本上都是業務邏輯和CronTaskRegistrar類的編排(ps:需要的也可以私聊demo)
-
定時任務表設計
create table schedule_setting ( id varchar(32) not null comment '唯一id' primary key, job_id varchar(64) null comment '任務ID', cron_expression varchar(255) null comment 'cron表達式', job_result varchar(32) null comment '任務結果(通過 複議 拒絕)', create_date datetime null comment '創建時間', status varchar(4) null, create_by varchar(64) null comment '創建人賬號', creator varchar(64) null comment '創建人', version bigint(19) null comment '版本號' ) comment '定時任務表'; -
項目啓動時加載所有任務
@Component @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class ScheduleRunner implements CommandLineRunner { ScheduleSettingRepository scheduleSettingRepository; CronTaskRegistrar scheduledTaskRegistrar; @Override public void run(String... args) throws Exception { // 查詢所有定時任務 List<ScheduleSetting> scheduleSettingList=scheduleSettingRepository.findByStatus(EnableFlag.Y.name()); for (ScheduleSetting scheduleSetting : scheduleSettingList) { //調用CronTaskRegistrar添加任務方法 scheduledTaskRegistrar.addCronTask(() -> { ··· 任務執行方法 ... }, scheduleSetting.getCronExpression(), scheduleSetting.getJobId()); } } }至此,動態定時任務就説完啦
GitHup demo dynamic-timing-task