Spring實(shí)現(xiàn)定時(shí)任務(wù)的兩種方法詳解
1. 概要
時(shí)間輪的文章在上幾篇文章中就已經(jīng)介紹完了,那么 Java 中 Spring 的定時(shí)任務(wù)肯定也是跑不掉的,那首先要學(xué) Spring 里面定時(shí)任務(wù)的邏輯,就得先學(xué)會(huì)用法。關(guān)于 Spring,其實(shí)提供了兩種方式實(shí)現(xiàn)定時(shí)任務(wù),一種是注解,還有一種就是接口了,下面我就會(huì)講一下這兩種方式的用法,在下一篇文章中我們會(huì)繼續(xù)深入 Spring 源碼,去講解 Spring 里面的定時(shí)任務(wù)到底是怎么實(shí)現(xiàn)的。
2. 接口方式動(dòng)態(tài)配置
2.1 抽象類
通過接口的方式可以實(shí)現(xiàn)時(shí)間的動(dòng)態(tài)配置,先看下抽象類:
@Slf4j @Configuration @EnableScheduling public abstract class ScheduledConfig implements SchedulingConfigurer { //定時(shí)任務(wù)周期表達(dá)式 private String cron; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { //設(shè)置線程池,可開啟多線程 taskRegistrar.setScheduler(taskExecutor()); taskRegistrar.addTriggerTask( // 執(zhí)行定時(shí)任務(wù) () -> { taskService(); }, triggerContext -> { // 這里就是動(dòng)態(tài)獲取任務(wù)隊(duì)列的邏輯 cron = getCron(); if(cron == null){ throw new RuntimeException("cron not exist"); } // 重新獲取cron表達(dá)式 CronTrigger trigger = new CronTrigger(cron); return trigger.nextExecutionTime(triggerContext); } ); } private Executor taskExecutor() { return BeanUtils.getBean(ThreadPoolTaskScheduler.class); } /** * @Description: 執(zhí)行定時(shí)任務(wù) * @param: * @return: void * @Author: * @Date: 2020/8/28 */ public abstract void taskService(); /** * @Description: 獲取定時(shí)任務(wù)周期表達(dá)式 * @param: * @return: java.lang.String * @Author: * @Date: 2020/8/28 */ public abstract String getCron(); /** * 判斷某任務(wù)是否開啟 * @return */ public abstract int getOpen(); @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler(){ ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(16); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor; } }
實(shí)現(xiàn) SchedulingConfigurer 接口之后重寫 configureTasks 方法,可以設(shè)置任務(wù)的具體調(diào)度邏輯,也就是 taskRegistrar.addTriggerTask。那么里面的具體邏輯是什么呢?
// 執(zhí)行定時(shí)任務(wù) () -> { taskService(); }, triggerContext -> { // 這里就是動(dòng)態(tài)獲取任務(wù)隊(duì)列的邏輯 cron = getCron(); if(cron == null){ throw new RuntimeException("cron not exist"); } // 重新獲取cron表達(dá)式 CronTrigger trigger = new CronTrigger(cron); return trigger.nextExecutionTime(triggerContext); }
定時(shí)任務(wù)里面執(zhí)行具體的任務(wù),同時(shí)重寫觸發(fā)器的 nextExecutionTime 方法,在里面重新獲取 cron 表達(dá)式然后更新下一次的執(zhí)行時(shí)間。
同時(shí)最后設(shè)置了執(zhí)行任務(wù)的線程池,這樣定時(shí)任務(wù)執(zhí)行的邏輯就會(huì)交給線程池去執(zhí)行,不需要阻塞當(dāng)前的工作線程。
2.2 具體實(shí)現(xiàn)類
@Component public class ScheduledJob extends ScheduledConfig { String cron = "0/1 * * * * ?"; long count = 0; @Override public void taskService() { int open = getOpen(); if(open == 1){ count++; PrintUtils.printLog("執(zhí)行任務(wù)!!!, 當(dāng)前表達(dá)式:%s", cron); } } @Override public String getCron() { if(count == 5){ cron = "0/5 * * * * ?"; } return cron; } @Override public int getOpen() { return 1; } }
里面的 getOpen() 就是去獲取定時(shí)任務(wù)是否開啟的,getCron() 是獲取任務(wù)執(zhí)行 cron 表達(dá)式,這兩個(gè)配置都可以寫在數(shù)據(jù)庫里面去更改。
2.3 工具類
首先是 Beanutils,從 Spring 容器中獲取對(duì)應(yīng)的 bean
@Component public class BeanUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { synchronized (BeanUtils.class){ BeanUtils.applicationContext = applicationContext; } } public static <T> T getBean(String beanName) { synchronized (BeanUtils.class){ if(applicationContext.containsBean(beanName)){ return (T) applicationContext.getBean(beanName); }else{ return null; } } } public static <T> Map<String, T> getBeansOfType(Class<T> baseType){ synchronized (BeanUtils.class) { return applicationContext.getBeansOfType(baseType); } } public static <T> T getBean(Class<T> baseType){ synchronized (BeanUtils.class) { return applicationContext.getBean(baseType); } } }
然后就是打印的工具類 PrintUtils
public class PrintUtils { static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS"); public static String now(){ return LocalDateTime.now().format(FORMATTER); } public static void printLog(String str, Object...args){ System.out.println(now() + ": " + String.format(str, args)); } }
2.4 執(zhí)行結(jié)果
輸出結(jié)果如上所示:可以看到在第五次輸出之后 cron 被改成了 5s 執(zhí)行一次,但是注意第 6 次距離第 5 次是 3s,可能是內(nèi)部的一些其他的處理,后面源碼再看。
3. 注解方式靜態(tài)配置
在 Spring 中,默認(rèn)情況下,@Scheduled 注解標(biāo)記的任務(wù)是由 Spring 內(nèi)部的一個(gè)單線程調(diào)度器(TaskScheduler)來執(zhí)行的。但是如果我們需要用自定義的線程池來執(zhí)行這些任務(wù),可以通過配置自定義的 TaskScheduler 或 ThreadPoolTaskScheduler 來實(shí)現(xiàn)。ThreadPoolTaskScheduler 里面是通過 ScheduledExecutorService 來實(shí)現(xiàn)的。
@Scheduled 注解可以實(shí)現(xiàn)定時(shí)任務(wù)、固定速率任務(wù)、固定延時(shí)任務(wù)三種,下面就一種一種來看,不過在看任務(wù)之前,先來看下一些必要的配置。
1.首先是開啟 @EnableScheduling 注解
@SpringBootApplication @EnableScheduling public class ScheduleApplication { public static void main(String[] args) { SpringApplication.run(ScheduleApplication.class, args); } }
2.然后是配置線程池
@Configuration public class SchedulerConfig { @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); // 設(shè)置線程池大小 scheduler.setThreadNamePrefix("MyTaskScheduler-"); // 設(shè)置線程名稱前綴 scheduler.initialize(); // 初始化調(diào)度器 return scheduler; } }
3.1 定時(shí)任務(wù)
@Service public class ScheduledService { @Scheduled(cron = "0/5 * * * * ?") public void runEvery15Minutes() { PrintUtils.printLog( "This task runs every 5 seconds"); } }
定時(shí)任務(wù)通過 cron 表達(dá)式來實(shí)現(xiàn),輸出結(jié)果如下:
3.2 固定延時(shí)任務(wù)
定時(shí)/延時(shí)任務(wù)-ScheduledThreadPoolExecutor的使用,固定速率和固定延時(shí)就看上面的區(qū)別就行了,因?yàn)?Spring 底層 Debug 了下默認(rèn)創(chuàng)建出來的就是 ScheduledThreadPoolExecutor。不過其實(shí)核心思想都是一樣的,就算自己去實(shí)現(xiàn)了,固定速率和固定延時(shí)的核心都是一樣的,只是實(shí)現(xiàn)上會(huì)不一樣,可以對(duì)比下 ScheduledThreadPoolExecutor 和 Timer 的實(shí)現(xiàn)。Timer 的解析也在往期文章里面。定時(shí)/延時(shí)任務(wù)-Timer用法
代碼如下所示:
@Service public class ScheduledService { @Scheduled(fixedDelay = 5000) public void runEvery15Minutes() throws InterruptedException { Thread.sleep(6000); PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds"); } }
結(jié)果輸出如下,固定延時(shí)就是要確保本次任務(wù)執(zhí)行的時(shí)間距離上一次任務(wù)執(zhí)行完成的時(shí)間相差 5s
3.3 固定速率任務(wù)
@Service public class ScheduledService { @Scheduled(fixedRate = 5000) public void runEvery15Minutes() throws InterruptedException { Thread.sleep(6000); PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds"); } }
執(zhí)行結(jié)果如下:
固定速率就是確保本次任務(wù)執(zhí)行時(shí)間距離上次任務(wù)執(zhí)行的時(shí)間是期望時(shí)間 + fixedRate,如果看不懂可以去看上面那兩篇文章,很清晰。
3.4 動(dòng)態(tài)配置
@Scheduled 注解使用固定的時(shí)間間隔或 cron 表達(dá)式來定義任務(wù)的執(zhí)行頻率,下面就來演示下。
- 在 application.properties 或 application.yml 中定義動(dòng)態(tài)配置
- 在 @Scheduled 注解中引用這些字段
application.properties 配置文件如下:
# 動(dòng)態(tài)配置時(shí)間間隔(單位:毫秒) custom.fixedRate=5000 # 動(dòng)態(tài)配置cron表達(dá)式 custom.cronExpression=0/5 * * * * ?
@Service public class ScheduledService { @Scheduled(fixedRateString = "${custom.fixedRate}") public void runEvery15Minutes() throws InterruptedException { Thread.sleep(6000); PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds"); } } @Scheduled(cron = "${custom.cronExpression}") public void runEvery15Minutes() throws InterruptedException { Thread.sleep(6000); PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds"); }
其中一個(gè)的輸出結(jié)果如下,可以看到都是能正常調(diào)度的。
以上就是Spring實(shí)現(xiàn)定時(shí)任務(wù)的兩種方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring定時(shí)任務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java請(qǐng)求Http接口OkHttp超詳細(xì)講解(附帶工具類)
這篇文章主要給大家介紹了關(guān)于Java請(qǐng)求Http接口OkHttp超詳細(xì)講解的相關(guān)資料,OkHttp是一款優(yōu)秀的HTTP客戶端框架,文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02Java 線程的優(yōu)先級(jí)(setPriority)案例詳解
這篇文章主要介紹了Java 線程的優(yōu)先級(jí)(setPriority)案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08lombok注解@Data使用在繼承類上時(shí)出現(xiàn)警告的問題及解決
Lombok的@Data注解簡化了實(shí)體類代碼,但在子類中使用時(shí)會(huì)出現(xiàn)警告,指出equals和hashCode方法不會(huì)考慮父類屬性,解決方法有兩種:一是在父類上使用@EqualsAndHashCode(callSuper=true)注解;二是通過配置lombok.config文件,均能有效解決警告問題2024-10-10SpringBoot項(xiàng)目打成War包部署的方法步驟
這篇文章主要介紹了springboot項(xiàng)目如何打war包流程的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Springboot項(xiàng)目中單元測試時(shí)注入bean失敗的解決方案
這篇文章主要介紹了Springboot項(xiàng)目中單元測試時(shí)注入bean失敗的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11springboot對(duì)數(shù)據(jù)庫密碼加密的實(shí)現(xiàn)
這篇文章主要介紹了springboot對(duì)數(shù)據(jù)庫密碼加密的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12SpringBoot文件上傳與下載功能實(shí)現(xiàn)詳解
文件上傳與下載是Web應(yīng)用開發(fā)中常用的功能之一。接下來我們將討論如何在Spring?Boot的Web應(yīng)用開發(fā)中,如何實(shí)現(xiàn)文件的上傳與下載,感興趣的可以了解一下2022-10-10