基於SchedulingConfigurer實現多定時任務註冊及動態修改執行週期
基於SchedulingConfigurer實現多定時任務註冊及動態修改執行週期
Java中實現定時任務有三種方式:
1.JDK自帶的Timer
2.Spring中的Spring Task
3.藉助第三方的Quartz
工作中常用的是Spring Task和Quartz,今天主要用一下Spring Task。
Spring Task有兩種實現方式:
[email protected](cron表示式)
2.基於SchedulingConfigurer註冊定時任務
這兩者的區別主要有
[email protected]不支援動態修改定時週期,只能停止伺服器,修改cron表示式,再啟動伺服器;SchedulingConfigurer可以動態修改
接下來我們一步步操作下基於SchedulingConfigurer的定時任務實現
一、在Spring Boot中實現一個簡單的定時任務,熟悉ScheduledTaskRegistrar的用法
/**
* 先註冊,後執行(根據返回的時間執行)
**/
@EnableScheduling
@Slf4j
@Configuration
public class TaskConfig implements SchedulingConfigurer {
private String cron = "0 0/5 * * * ?";
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
log.info("***************初始化定時任務設定開始*************");
//lambda表示式實現簡單的任務邏輯
scheduledTaskRegistrar.addTriggerTask(() -> {
log.info("執行了定時任務");
},sheduledConfig->{
//設定定時任務的執行頻率並返回下次執行時間
Date date = new CronTrigger(cron).nextExecutionTime(sheduledConfig);
log.info("執行定時任務的時間:" + formatDate(date));
return date;
});
}
}
}
注意幾個地方:
1.新增@EnableScheduling註解,使得當前類支援定時任務
2.實現SchedulingConfigurer介面,重寫configureTasks方法
我們看下addTriggerTask方法及其兩個引數:
public void addTriggerTask(Runnable task, Trigger trigger) {
this.addTriggerTask(new TriggerTask(task, trigger));
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Runnable是一個函式式介面,用來定義定時任務的執行邏輯,內部的run方法會被定時任務觸發器回撥執行,單個簡單的任務邏輯我們可以採用上面的Lambda表示式實現,多個定時任務且邏輯複雜的話,採用另外的方式實現,下面會講。
public interface Trigger {
@Nullable
Date nextExecutionTime(TriggerContext var1);
}
Trigger:定時任務的週期設定,該介面的實現類有CronTrigger和PeriodicTrigger,一般使用CronTrigger來設定定時任務的執行頻率,介面會返回下次定時任務的執行時間。
二、實現多工,不同頻率的定時任務註冊和執行
addTriggerTask(Runnable task, Trigger trigger)
分析這個方法,實現上面功能的大致邏輯如下:
1.因為Runnable:任務執行邏輯,只能傳入一個任務,所以如果是多個任務的話,可以採用迴圈一個個註冊,那既然要迴圈的話,我們就需要獲取所有的定時任務類,那如何獲取所有的定時任務類呢,還是通過入參引數思考,定時任務類需要實現Runable介面,那麼我們就可以通過Bean的型別來獲取所有實現Runable介面的類,遍歷執行就可以了
2.定時任務執行週期:跟著1思考,每個定時任務的執行頻率不同,我們就把這個引數放到定時任務類中作為屬性,迴圈註冊時,可以拿到定時任務類,也就可以通過類拿到定時任務的執行頻率了
按照上面的思路,開始擼程式碼
1.定義介面
public interface BaskTask extends Runnable {
//獲取執行頻率
String getCron();
//執行任務邏輯
void execute();
}
2.定義兩個定時任務類
@Slf4j
@Component
@Data
public class TaskOne implements BaskTask {
//從配置檔案獲取任務執行頻率
@Value("${task.taskOneCron}")
private String taskCron;
//外部獲取執行頻率
@Override
public String getCron() {
return taskCron;
}
@Override
public void execute() {
//可寫複雜的任務邏輯
log.info("taskOne在"+ formatDate(new Date()) +"執行了")
}
//執行任務
@Override
public void run() {
execute();
}
}
這裡說一下任務執行頻率,其實將執行頻率寫在配置檔案中不能體現出可以動態修改執行頻率的效果,因為配置檔案修改就得重啟,最理想的做法就是通過頁面輸入執行頻率,同步到這裡(可以實現)
@Slf4j
@Component
@Data
public class TaskTwo implements BaskTask {
@Value("${task.taskTwoCron}")
private String taskCron;
@Override
public String getCron() {
return taskCron;
}
@Override
public void execute() {
log.info("taskTwo在"+ formatDate(new Date()) +"執行了");
}
@Override
public void run() {
execute();
}
}
3.定義定時任務註冊器
@EnableScheduling
@Slf4j
@Configuration
public class TaskConfig implements SchedulingConfigurer {
@Autowired
private ApplicationContext applicationContext;
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
//獲取所有的定時任務
Map<String,BaskTask> map = applicationContext.getBeansOfType(BaskTask.class);
//遍歷註冊
for(String key :map.keySet()){
BaskTask baskTask = map.get(key);
//baskTask:回撥內部的run方法
//baskTask.getCron() 獲取各個任務的執行頻率
scheduledTaskRegistrar.addTriggerTask(baskTask,sheduledConfig->{
Date date = new CronTrigger(baskTask.getCron()).nextExecutionTime(sheduledConfig);
log.info(key + "執行定時任務的時間:" + formatDate(date));
return date;
});
}
}
}
4.執行結果如下:
5.細心的小夥伴可以發現,兩個定時任務是在同一個執行緒中執行的,那麼可不可以實現在不同的執行緒中執行呢,答案是肯定的,因為SchedulingConfigurer是支援多執行緒的,只需要向定時任務註冊器中新增執行緒池就好了,如下:
//在註冊器新增定時任務前新增執行緒池
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();
scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
再執行結果如下:
最後留個懸念吧:如果某個定時任務不想讓繼續運行了,又該怎麼做呢,有沒有小夥伴注意到這行日誌:No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
和這個有關係,剩下的內容下篇再寫!