Springboot實現(xiàn)動態(tài)定時任務(wù)流程詳解
一、靜態(tài)
靜態(tài)的定時任務(wù)可以直接使用注解@Scheduled,并在啟動類上配置@EnableScheduling即可
@PostMapping("/list/test1") @Async @Scheduled(cron = "0 * * * * ?") public void test1() throws Exception { Object obj = this.getClass().newInstance(); log.info("執(zhí)行靜態(tài)定時任務(wù)時間/test1:param:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())); }
二、動態(tài)
動態(tài)的定時任務(wù)需要根據(jù)ScheduledTaskRegistrar的addTriggerTask方法進(jìn)行實現(xiàn),網(wǎng)上大多數(shù)資料是在Trigger中動態(tài)查詢cron表達(dá)式來實現(xiàn),但存在一個問題就是只有在下一次執(zhí)行的時候才會刷新,比如一開始設(shè)置的每天12點執(zhí)行一次,如果項目啟動后,修改為每小時執(zhí)行一次的話,需要等到下一次12點執(zhí)行之后,才會刷新cron表達(dá)式,所以通過對ScheduledTaskRegistrar的源碼分析,構(gòu)建了以下解決方案,可以實現(xiàn)對定時任務(wù)的額單次額外執(zhí)行、停止、啟動三個基本功能。在此只列出關(guān)鍵代碼,關(guān)于項目啟動、數(shù)據(jù)庫連接的代碼等就不過多說明了。
1、基本代碼
首先新建定時任務(wù)信息的庫表及實體類
@Data @EqualsAndHashCode(callSuper = false) @TableName("cron") public class Cron implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.ASSIGN_UUID) private String id; private String cronFlag; private String cronExpression; private String className; private String methodName; }
用于獲取bean實例的工具類
@Component public class BeansUtils implements ApplicationContextAware { private static ApplicationContext context; public static <T> T getBean(Class<T> bean) { return context.getBean(bean); } public static <T> T getBean(String var1, @Nullable Class<T> var2){ return context.getBean(var1, var2); } public static ApplicationContext getContext() { return context; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } }
實現(xiàn)定時任務(wù)類
@Configuration @EnableScheduling @EnableAsync @Slf4j public class RCScheduleTask implements SchedulingConfigurer { @Autowired private ICronService iCronService; private static ScheduledTaskRegistrar scheduledTaskRegistrar; public static Map<String, TriggerTask> triggerTaskMap; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { scheduledTaskRegistrar = taskRegistrar; Cron cron = new Cron(); cron.setCronFlag("1"); List<Cron> list = iCronService.getList(cron); if (list != null) { initTriggerTask(list); } } public void initTriggerTask(List<Cron> list) { triggerTaskMap = new HashMap<>(); for (Cron cron : list) { TriggerTask triggerTask = new TriggerTask(getRunnable(cron), getTrigger(cron)); scheduledTaskRegistrar.addTriggerTask(triggerTask); triggerTaskMap.put(cron.getId(), triggerTask); } } private static Runnable getRunnable(Cron cron) { return new Runnable() { @Override public void run() { Class<?> clazz; try { clazz = Class.forName(cron.getClassName()); Object bean = BeansUtils.getBean(clazz); Method method = ReflectionUtils.findMethod(bean.getClass(), cron.getMethodName()); ReflectionUtils.invokeMethod(method, bean); } catch (Exception e) { e.printStackTrace(); } } }; } private static Trigger getTrigger(Cron cron) { return new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { CronTrigger trigger = new CronTrigger(cron.getCronExpression()); Date nextExec = trigger.nextExecutionTime(triggerContext); return nextExec; } }; } public static boolean run(String id) { TriggerTask tt = triggerTaskMap.get(id); if (tt != null) { tt.getRunnable().run(); return true; } return false; } public static boolean stop(String id) { TriggerTask tt = triggerTaskMap.get(id); if (tt != null) { Set<ScheduledTask> scheduledTasks = scheduledTaskRegistrar.getScheduledTasks(); for (ScheduledTask st:scheduledTasks) { boolean b = st.getTask().getRunnable() == tt.getRunnable(); if (b) { st.cancel(); return true; } } } return false; } public static boolean start(Cron cron) throws Exception { try { triggerTaskMap.remove(cron.getId()); TriggerTask tt = new TriggerTask(getRunnable(cron), getTrigger(cron)); triggerTaskMap.put(cron.getId(), tt); scheduledTaskRegistrar.scheduleTriggerTask(tt); return true; } catch (Exception e) { e.printStackTrace(); } return false; } }
控制調(diào)用類,RCResult為自定義封裝的返回結(jié)果類,可自行定義
@RestController @RequestMapping("/cron") @Slf4j public class CronController { @Autowired private ICronService iCronService; @Autowired private RCScheduleTask rcScheduleTask; @GetMapping("/task/1") @Async public void task1() { log.info("模擬任務(wù)1-執(zhí)行時間:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())); } @GetMapping("/task/2") @Async public void task2() { log.info("模擬任務(wù)2-執(zhí)行時間:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())); } @GetMapping("/task/3") @Async public void task3() { log.info("模擬任務(wù)3-執(zhí)行時間:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())); } @GetMapping("/run/{id}") public RCResult run(@PathVariable("id") String id) { Boolean b = RCScheduleTask.run(id); if (b) { return RCResult.ok("執(zhí)行任務(wù)" + id + "成功"); } return RCResult.error("執(zhí)行任務(wù)" + id + "失敗"); } @GetMapping("/stop/{id}") public RCResult stop(@PathVariable("id") String id) { Boolean b = RCScheduleTask.stop(id); if (b) { return RCResult.ok("停止任務(wù)" + id + "成功"); } return RCResult.error("停止任務(wù)" + id + "失敗"); } @GetMapping("/start/{id}") public RCResult start(@PathVariable("id") String id) throws Exception { Cron cron = iCronService.getById(id); if (cron != null) { Boolean b = RCScheduleTask.start(cron); if (b) { return RCResult.ok("開始任務(wù)" + id + "成功"); } } return RCResult.error("開始任務(wù)" + id + "失敗"); } }
2、方案詳解
2.1 初始化
通過@EnableScheduling和@EnableAsync兩個標(biāo)簽,開啟定時任務(wù)和多線程
@Configuration @EnableScheduling @EnableAsync @Slf4j public class RCScheduleTask implements SchedulingConfigurer {
重寫SchedulingConfigurer的configureTasks方法,可以在項目啟動時自動加載狀態(tài)為啟用(cron_flag為1)定時任務(wù)列表
@Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { scheduledTaskRegistrar = taskRegistrar; Cron cron = new Cron(); cron.setCronFlag("1"); List<Cron> list = iCronService.getList(cron); if (list != null) { initTriggerTask(list); } }
TriggerTask創(chuàng)建的兩個參數(shù)Runnable和Trigger可以理解為實現(xiàn)的操作和執(zhí)行的周期,在Runnable中我們通過反射的方式,從庫中取到調(diào)用方法的類名和方法名,來執(zhí)行接口操作,在Trigger中我們根據(jù)庫中的cron表達(dá)式來設(shè)置執(zhí)行周期
public void initTriggerTask(List<Cron> list) { triggerTaskMap = new HashMap<>(); for (Cron cron : list) { TriggerTask triggerTask = new TriggerTask(getRunnable(cron), getTrigger(cron)); scheduledTaskRegistrar.addTriggerTask(triggerTask); triggerTaskMap.put(cron.getId(), triggerTask); } } private static Runnable getRunnable(Cron cron) { return new Runnable() { @Override public void run() { Class<?> clazz; try { clazz = Class.forName(cron.getClassName()); Object bean = BeansUtils.getBean(clazz); Method method = ReflectionUtils.findMethod(bean.getClass(), cron.getMethodName()); ReflectionUtils.invokeMethod(method, bean); } catch (Exception e) { e.printStackTrace(); } } }; } private static Trigger getTrigger(Cron cron) { return new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { CronTrigger trigger = new CronTrigger(cron.getCronExpression()); Date nextExec = trigger.nextExecutionTime(triggerContext); return nextExec; } }; }
庫表數(shù)據(jù)
從執(zhí)行結(jié)果中可以看到狀態(tài)為已啟用的task1和task2執(zhí)行了,而task3并沒有執(zhí)行
2.2 單次執(zhí)行
我們可以通過獲取TriggerTask的Runnable來執(zhí)行run()方法,來單次執(zhí)行定時任務(wù)
任務(wù)類中定義run方法
public static boolean run(String id) { TriggerTask tt = triggerTaskMap.get(id); if (tt != null) { tt.getRunnable().run(); return true; } return false; }
接口調(diào)用
@GetMapping("/run/{id}") public RCResult run(@PathVariable("id") String id) { Boolean b = RCScheduleTask.run(id); if (b) { return RCResult.ok("執(zhí)行任務(wù)" + id + "成功"); } return RCResult.error("執(zhí)行任務(wù)" + id + "失敗"); }
模擬接口調(diào)用
從調(diào)用結(jié)果中可以看到,task1在06秒的時候單獨執(zhí)行了一次,并且沒有影響后續(xù)執(zhí)行
2.3 停止任務(wù)
停止任務(wù)需要執(zhí)行ScheduledTask類的cancel()方法,由于在初始化時通過addTriggerTask方法并不會立刻加入到ScheduledTask列表中,所以需要在調(diào)用時通過ScheduledTaskRegistrar獲取ScheduledTask列表,然后與TriggerTask的Runnable進(jìn)行比較判斷是否一致
任務(wù)類中定義stop
public static boolean stop(String id) { TriggerTask tt = triggerTaskMap.get(id); if (tt != null) { Set<ScheduledTask> scheduledTasks = scheduledTaskRegistrar.getScheduledTasks(); for (ScheduledTask st:scheduledTasks) { boolean b = st.getTask().getRunnable() == tt.getRunnable(); if (b) { st.cancel(); return true; } } } return false; }
調(diào)用
@GetMapping("/stop/{id}") public RCResult stop(@PathVariable("id") String id) { Boolean b = RCScheduleTask.stop(id); if (b) { return RCResult.ok("停止任務(wù)" + id + "成功"); } return RCResult.error("停止任務(wù)" + id + "失敗"); }
在調(diào)用結(jié)果中可以看到,task1不再執(zhí)行了
2.4 啟用任務(wù)
啟用任務(wù)需要通過ScheduledTaskRegistrar的scheduleTriggerTask()方法進(jìn)行調(diào)用,由于源碼中并未提供針對Runnable或Trigger的單獨修改方法,所以在這里我們通過新建實例進(jìn)行替換,在執(zhí)行前先停止任務(wù)
public static boolean start(Cron cron) throws Exception { try { stop(cron.getId()); triggerTaskMap.remove(cron.getId()); TriggerTask tt = new TriggerTask(getRunnable(cron), getTrigger(cron)); triggerTaskMap.put(cron.getId(), tt); scheduledTaskRegistrar.scheduleTriggerTask(tt); return true; } catch (Exception e) { e.printStackTrace(); } return false; }
調(diào)用
@GetMapping("/start/{id}") public RCResult start(@PathVariable("id") String id) throws Exception { Cron cron = iCronService.getById(id); if (cron != null) { Boolean b = RCScheduleTask.start(cron); if (b) { return RCResult.ok("開始任務(wù)" + id + "成功"); } } return RCResult.error("開始任務(wù)" + id + "失敗"); }
通過調(diào)用結(jié)果,可以看到在啟用后,task1重新加入到定時任務(wù)隊列中了
在修改task1的執(zhí)行周期后再次調(diào)用start方法
從調(diào)用結(jié)果中可以看到,task1的周期已被更改
三、小結(jié)
昨天剛接觸定時任務(wù),一開始也是在網(wǎng)上搜索資料,后來發(fā)現(xiàn)案例都無法滿足自身需求,或者只是講解怎么使用,根本無法映射到實際業(yè)務(wù)上,所以通過對源碼的分析,一層層的梳理,模擬了此篇關(guān)于定時任務(wù)的解決方案,由于時間原因,在此省略了前端界面的編寫,但大多數(shù)實際需求無非也就是通過一個開關(guān)或按鈕來對任務(wù)進(jìn)行啟用、停止操作,或者額外需要單次執(zhí)行任務(wù),在此對動態(tài)定時任務(wù)的方案進(jìn)行記錄,同時也希望能幫助到遇到同樣問題的同學(xué)們
到此這篇關(guān)于Springboot實現(xiàn)動態(tài)定時任務(wù)流程詳解的文章就介紹到這了,更多相關(guān)Springboot動態(tài)定時任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring源碼BeanFactoryPostProcessor詳解
BeanFactoryPostProcessor的執(zhí)行時機是在Spring掃描完成后,Bean初始化前,當(dāng)我們實現(xiàn)BeanFactoryPostProcessor接口,可以在Bean的初始化之前對Bean進(jìn)行屬性的修改,下面通過本文看下Spring源碼分析-BeanFactoryPostProcessor的實例代碼,感興趣的朋友一起看看吧2021-11-11JAVA并發(fā)中VOLATILE關(guān)鍵字的神奇之處詳解
這篇文章主要給大家介紹了關(guān)于JAVA并發(fā)中VOLATILE關(guān)鍵字的神奇之處的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05Java實現(xiàn)在線編輯預(yù)覽office文檔詳解
PageOffice是一款在線的office編輯軟件,幫助Web應(yīng)用系統(tǒng)或Web網(wǎng)站實現(xiàn)用戶在線編輯Word、Excel、PowerPoint文檔,下面我們就來看看如何使用Java實現(xiàn)在線預(yù)覽office吧2024-01-01SpringBoot項目打jar包與war包的詳細(xì)步驟
SpringBoot和我們之前學(xué)習(xí)的web應(yīng)用程序不一樣,其本質(zhì)上是一個 Java應(yīng)用程序,那么又如何部署呢?這篇文章主要給大家介紹了關(guān)于SpringBoot項目打jar包與war包的詳細(xì)步驟,需要的朋友可以參考下2023-02-02