SpringBoot?ScheduledTaskRegistrar解決動(dòng)態(tài)定時(shí)任務(wù)思路詳解
前言
在做SpringBoot項(xiàng)目的過程中,有時(shí)客戶會(huì)提出按照指定時(shí)間執(zhí)行一次業(yè)務(wù)的需求。
如果客戶需要改動(dòng)業(yè)務(wù)的執(zhí)行時(shí)間,即動(dòng)態(tài)地調(diào)整定時(shí)任務(wù)的執(zhí)行時(shí)間,那么可以采用SpringBoot自帶的ScheduledTaskRegistrar類作為解決方案來實(shí)現(xiàn)。
在單一使用ScheduledTaskRegistrar類解決定時(shí)任務(wù)問題的時(shí)候,可能會(huì)達(dá)不到預(yù)期的動(dòng)態(tài)調(diào)整定時(shí)任務(wù)的效果。
如果靈活配合使用對應(yīng)的工具類(ThreadPoolTaskScheduler類),則可以方便地對動(dòng)態(tài)調(diào)整定時(shí)任務(wù)進(jìn)行管理。
本文會(huì)從問題出發(fā),詳細(xì)介紹ScheduledTaskRegistrar類是如何解決動(dòng)態(tài)調(diào)整定時(shí)任務(wù)的思路,并給出關(guān)鍵的代碼示例,幫助大家快速地上手學(xué)習(xí)。
一、問題背景
在指定的某一時(shí)刻執(zhí)行業(yè)務(wù);
可以手動(dòng)地更改執(zhí)行時(shí)間。
在實(shí)際項(xiàng)目中,很少會(huì)有傻瓜式地去指定某一時(shí)間就觸發(fā)某個(gè)業(yè)務(wù)的場景,執(zhí)行業(yè)務(wù)的時(shí)間不是一成不變的,而是動(dòng)態(tài)地隨著客戶所指定的時(shí)間進(jìn)行調(diào)整的。
二、痛點(diǎn)所在
如果單一地使用SpringBoot自帶的ScheduledTaskRegistrar去實(shí)現(xiàn),那么可能會(huì)有以下問題:
- 只能按照指定的時(shí)間去執(zhí)行,更改執(zhí)行時(shí)間需要重啟服務(wù);
- 無法刪除該定時(shí)任務(wù),或者刪除后無法再啟動(dòng)新的定時(shí)任務(wù)。
- 業(yè)務(wù)邏輯與觸發(fā)器的代碼耦合度太高,無法將業(yè)務(wù)代碼從ScheduledTaskRegistrar類中抽離出去。
/**
* @author Created by zhuzqc on 2023/1/30 15:28
*/
@Slf4j
@Component
@EnableScheduling
public class ScheduleTaskDemo implements SchedulingConfigurer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//Runnable線程注冊任務(wù)
Runnable taskOne = () -> {
//需要執(zhí)行的業(yè)務(wù)邏輯,一般會(huì)在這里封裝好
logger.info("----------業(yè)務(wù)執(zhí)行結(jié)束----------");
};
//任務(wù)的觸發(fā)時(shí)間,一般使用 cron 表達(dá)式
Trigger triggerOne = triggerContext -> {
Date nextExecTime = null;
try {
// 此處指定 cron 表達(dá)式
String cron = "0 00 12 ? * *";
if (StringUtils.isBlank(cron)) {
// 提示參數(shù)為空
logger.info("trigger定時(shí)器的 cron 參數(shù)為空!");
// 如果為空則賦默認(rèn)值,每天中午12點(diǎn)
cron = "0 00 12 ? * *";
}
logger.info("---------->定時(shí)任務(wù)執(zhí)行中<---------");
CronTrigger cronTrigger = new CronTrigger(cron);
nextExecTime = cronTrigger.nextExecutionTime(triggerContext);
} catch (Exception e) {
e.printStackTrace();
log.info(e.getMessage());
}
return nextExecTime;
};
taskRegistrar.addTriggerTask(taskOne, triggerOne);
}
}
上述代碼只能實(shí)現(xiàn)在指定的時(shí)間去觸發(fā)定時(shí)任務(wù),無法對 cron 表達(dá)式進(jìn)行更改,如果更改則需要重新啟動(dòng)服務(wù),非常地“傻瓜”。
而在實(shí)際的編碼過程中,業(yè)務(wù)邏輯代碼需要單獨(dú)地剝離開(解耦),如何做到業(yè)務(wù)邏輯代碼和觸發(fā)器代碼都能訪問到外部業(yè)務(wù)數(shù)據(jù),是設(shè)計(jì)過程中需要考慮到的關(guān)鍵。
三、解決思路
- TODO:如果要在此處將業(yè)務(wù)邏輯和時(shí)間觸發(fā)器進(jìn)行捆綁,那么在這個(gè)實(shí)現(xiàn)類中無法獲取到來自該類外部的業(yè)務(wù)數(shù)據(jù);
- TODO:要解決這樣的問題,就要找到一個(gè)辦法:既能將兩者抽離,又能實(shí)現(xiàn)靈活觸發(fā)定時(shí)任務(wù)。
在這里介紹一個(gè)名為ThreadPoolTaskScheduler類,通過源碼得知,該類實(shí)現(xiàn)了SchedulingTaskExecutor和TaskScheduler接口。
該類中schedule(Runnable task, Trigger trigger)方法,通過分別傳入線程任務(wù)(業(yè)務(wù)邏輯)和Trigger觸發(fā)器對象作為參數(shù),支持動(dòng)態(tài)創(chuàng)建指定 cron 表達(dá)式的定時(shí)任務(wù)。
該方法源碼如下:
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
以下部分是對該方法的具體使用,核心思路如下 :
- 實(shí)例化ThreadPoolTaskScheduler類對象;
- 實(shí)例化ScheduledFuture類對象,用于初始化調(diào)用schedule()后的值。
- 將攜帶有Runnable和Trigger的ScheduledFuture類對象作為Map的value進(jìn)行裝配。
- 根據(jù)Map的key對定時(shí)任務(wù)進(jìn)行管理,達(dá)到添加和刪除的目的。
四、代碼示例
代碼示例分為兩部分:
第一部分是關(guān)于ThreadPoolTaskScheduler類和schedule()方法的使用;
/**
* @author @author Created by zhuzqc on 2023/1/30 15:39
* 任務(wù)線程池管理工具
*/
public class TaskSchedulerUtil {
private static final Logger logger = LoggerFactory.getLogger(TaskSchedulerUtil.class);
/**
* 線程調(diào)度工具對象,作為該類的成員變量
*/
private static ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
/**
*初始化 map 對象,裝配 schedule 方法的返回對象為 value 值
*/
private static Map<String, ScheduledFuture<?>> scheduledFutureMap = new HashMap<String, ScheduledFuture<?>>();
static {
threadPoolTaskScheduler.initialize();
}
/**
* 將Runnable對象和Trigger對象作為參數(shù)傳入該靜態(tài)方法
* @param runnable
* @param trigger
* @param 定時(shí)任務(wù)id
*/
public static void put(Runnable runnable, Trigger trigger, String id) {
// 將攜帶有Runnable和Trigger的ScheduledFuture類對象作為 Map 的 value 進(jìn)行裝配
ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(runnable, trigger);
// 放入 Map 中作為 value
scheduledFutureMap.put(id, scheduledFuture);
logger.info("---添加定時(shí)任務(wù)--->" + id);
}
/**
* 通過上述 put 方法的參數(shù)id(定時(shí)任務(wù)id)標(biāo)識(shí),將定時(shí)任務(wù)移除出 map
* @param id
*/
public static void delete(String id) {
ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(id);
// 條件判斷
if (scheduledFuture != null && scheduledFuture.isCancelled()) {
scheduledFuture.cancel(true);
}
scheduledFutureMap.remove(id);
logger.info("---取消定時(shí)任務(wù)--->" + id);
}
}
第二部分是關(guān)于結(jié)合實(shí)際業(yè)務(wù),引入實(shí)際業(yè)務(wù)數(shù)據(jù)的代碼demo。
/**
* @author Created by zhuzqc on 2023/1/30 15:58
*/
@Slf4j
@Component
@EnableScheduling
public class TaskScheduleDemo{
@Resource
private AAAMapper aaaMapper;
@Resource
private BBBService bbbService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 引入外部的業(yè)務(wù)數(shù)據(jù)對象
public void putHiredTask(CCCEntity cccEntity){
//TODO: 將業(yè)務(wù)線程和定時(shí)觸發(fā)器交由線程池工具管理:創(chuàng)建業(yè)務(wù)線程對象,并對屬性賦初始化值(有參構(gòu)造)
TaskSchedulerUtil.put(new TaskThreadDemo(cccEntity,aaaMapper,bbbService), new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
//獲取定時(shí)觸發(fā)器,這里可以獲取頁面的更新記錄,實(shí)現(xiàn)定時(shí)間隔的動(dòng)態(tài)調(diào)整
Date nextExecTime = TaskTransUtils.StringToDateTime(cccEntity.getSendTime());
//cron 表達(dá)式轉(zhuǎn)換工具類
String cron = TaskTransUtils.getDateCronTime(nextExecTime);
try {
if (StringUtils.isBlank(cron)) {
// 提示參數(shù)為空
logger.info("trackScheduler定時(shí)器的 cron 參數(shù)為空!");
// 如果為空則賦默認(rèn)值,每天早上9:00
cron = "0 00 09 ? * *";
}
logger.info("-------定時(shí)任務(wù)執(zhí)行中:" + cron + "--------");
CronTrigger cronTrigger = new CronTrigger(cron);
nextExecTime = cronTrigger.nextExecutionTime(triggerContext);
} catch (Exception e) {
e.printStackTrace();
log.info(e.getMessage());
}
return nextExecTime;
}
},"該定時(shí)任務(wù)的id");
}
}
五、文章小結(jié)
動(dòng)態(tài)定時(shí)任務(wù)的總結(jié)如下:
- 單一使用ScheduledTaskRegistrar類,無法達(dá)到預(yù)期動(dòng)態(tài)調(diào)整定時(shí)任務(wù)的效果;
- 實(shí)際的開發(fā)場景中,需要業(yè)務(wù)邏輯代碼和觸發(fā)器代碼都能訪問到外部業(yè)務(wù)數(shù)據(jù);
- 配合ThreadPoolTaskScheduler類和該類中的schedule()方法可以達(dá)到動(dòng)態(tài)調(diào)整定時(shí)任務(wù)的效果。
以上就是SpringBoot ScheduledTaskRegistrar解決動(dòng)態(tài)定時(shí)任務(wù)思路詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot ScheduledTaskRegistrar的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot中定時(shí)任務(wù)@Scheduled的多線程使用詳解
- SpringBoot通過@Scheduled實(shí)現(xiàn)定時(shí)任務(wù)及單線程運(yùn)行問題解決
- SpringBoot定時(shí)任務(wù)動(dòng)態(tài)擴(kuò)展ScheduledTaskRegistrar詳解
- SpringBoot中定時(shí)任務(wù)@Scheduled注解的使用解讀
- 解決SpringBoot中的Scheduled單線程執(zhí)行問題
- SpringBoot中使用@scheduled定時(shí)執(zhí)行任務(wù)的坑
- springboot使用定時(shí)器@Scheduled不管用的解決
- 帶你3分鐘帶你搞定Spring Boot中Schedule
相關(guān)文章
Spring bean的實(shí)例化和IOC依賴注入詳解
這篇文章主要介紹了Spring bean的實(shí)例化和IOC依賴注入詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-03-03
Java?BigDecimal類的一般使用、BigDecimal轉(zhuǎn)double方式
這篇文章主要介紹了Java?BigDecimal類的一般使用、BigDecimal轉(zhuǎn)double方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Spring Boot配置特定屬性spring.profiles的方法
這篇文章主要介紹了Spring Boot配置特定屬性spring.profiles的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11
Spring boot詳解緩存redis實(shí)現(xiàn)定時(shí)過期方法
本篇文章分享的就是spring boot中的一個(gè)輪子,spring cache注解的方式實(shí)現(xiàn)接口數(shù)據(jù)緩存。默認(rèn)的配置想非常簡單,但是有一個(gè)弊端是緩存數(shù)據(jù)為永久緩存,本次將介紹如何設(shè)置接口緩存數(shù)據(jù)的過期時(shí)間2022-07-07
解決idea中maven項(xiàng)目打包成jar報(bào)錯(cuò):沒有主清單屬性的問題
這篇文章主要給大家分享了idea中maven項(xiàng)目打包成jar,報(bào)錯(cuò)沒有主清單屬性解決方法,文中有詳細(xì)的解決方法,如果又遇到同樣問題的朋友可以參考一下本文2023-09-09
深入學(xué)習(xí)Spring Boot排查 @Transactional 引起的 NullPointerException問題
這篇文章主要介紹了深入學(xué)習(xí)Spring Boot排查 @Transactional 引起的 NullPointerException問題,需要的朋友可以參考下2018-01-01

