SchedulingConfigurer實現(xiàn)動態(tài)定時,導致ApplicationRunner無效解決
SchedulingConfigurer實現(xiàn)動態(tài)定時,導致ApplicationRunner無效
問題描述
當通過SchedulingConfigurer接口實現(xiàn)動態(tài)定時任務后,發(fā)現(xiàn)ApplicationRunner接口實現(xiàn)的邏輯不生效了,斷點不進,說明ApplicationRunner接口實現(xiàn)的方法并沒有執(zhí)行。
問題解釋
SchedulingConfigurer接口是使用Spring實現(xiàn)動態(tài)定時任務必然的一步,而ApplicationRunner接口為的是在容器(服務)啟動完成后,進行一些操作,同樣效果的還有接口CommandLineRunner,那么是因為啥導致實現(xiàn)SchedulingConfigurer接口后ApplicationRunner和CommandLineRunner的接口實現(xiàn)就不生效了呢?
原因剖析
導致ApplicationRunner和CommandLineRunner接口失效的原因還要看他倆的實現(xiàn)原理。
首先我們要明確一個概念,那就是雖然它倆名字含有Runner也有run方法,但它并不是Runnable,先看下源碼
package org.springframework.boot; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; /** ?* Interface used to indicate that a bean should <em>run</em> when it is contained within ?* a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined ?* within the same application context and can be ordered using the {@link Ordered} ?* interface or {@link Order @Order} annotation. ?* ?* @author Phillip Webb ?* @since 1.3.0 ?* @see CommandLineRunner ?*/ @FunctionalInterface public interface ApplicationRunner { ?? ?/** ?? ? * Callback used to run the bean. ?? ? * @param args incoming application arguments ?? ? * @throws Exception on error ?? ? */ ?? ?void run(ApplicationArguments args) throws Exception; }
@FunctionalInterface注解說明了ApplicationRunner是個函數式接口,不了解的童鞋看下java8
而CommandLineRunner源碼同樣也是這樣,源碼如下
package org.springframework.boot; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; /** ?* Interface used to indicate that a bean should <em>run</em> when it is contained within ?* a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined ?* within the same application context and can be ordered using the {@link Ordered} ?* interface or {@link Order @Order} annotation. ?* <p> ?* If you need access to {@link ApplicationArguments} instead of the raw String array ?* consider using {@link ApplicationRunner}. ?* ?* @author Dave Syer ?* @see ApplicationRunner ?*/ @FunctionalInterface public interface CommandLineRunner { ?? ?/** ?? ? * Callback used to run the bean. ?? ? * @param args incoming main method arguments ?? ? * @throws Exception on error ?? ? */ ?? ?void run(String... args) throws Exception; }
所以ApplicationRunner與CommandLineRunner除了名字以外的唯一區(qū)別就是入參不同 那么它倆的實現(xiàn)方法在什么時候執(zhí)行的呢?簡單說就是在ApplicationContext.run()方法中,會調用callRunners方法。
該方法獲取所有實現(xiàn)了ApplicationRunner和CommandLineRunner的接口bean,然后依次執(zhí)行對應的run方法,并且是在同一個線程中執(zhí)行。因此如果有某個實現(xiàn)了ApplicationRunner接口的bean的run方法一直循環(huán)不返回的話,后續(xù)的代碼將不會被執(zhí)行。
private void callRunners(ApplicationContext context, ApplicationArguments args) { ?? ??? ?List<Object> runners = new ArrayList<>(); ?? ??? ?runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); ?? ??? ?runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); ?? ??? ?AnnotationAwareOrderComparator.sort(runners); ?? ??? ?for (Object runner : new LinkedHashSet<>(runners)) { ?? ??? ??? ?if (runner instanceof ApplicationRunner) { ?? ??? ??? ??? ?callRunner((ApplicationRunner) runner, args); ?? ??? ??? ?} ?? ??? ??? ?if (runner instanceof CommandLineRunner) { ?? ??? ??? ??? ?callRunner((CommandLineRunner) runner, args); ?? ??? ??? ?} ?? ??? ?} ?? ?}
所以存在猜測,SchedulingConfigurer的實現(xiàn)方式影響了這倆,看看SchedulingConfigurer的實現(xiàn)方法
@Override ? ? public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ? ? ? ? for(SysTimedTaskEntity timedTaskEntity : timedTaskDao.selectAll()){ ? ? ? ? ? ? Class<?> clazz; ? ? ? ? ? ? Object task; ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? clazz = Class.forName(timedTaskEntity.getTaskPath()); ? ? ? ? ? ? ? ? task = context.getBean(clazz); ? ? ? ? ? ? } catch (ClassNotFoundException e) { ? ? ? ? ? ? ? ? throw new IllegalArgumentException("sys_timed_task表數據" + timedTaskEntity.getTaskPath() + "有誤", e); ? ? ? ? ? ? } catch (BeansException e) { ? ? ? ? ? ? ? ? throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "未納入到spring管理", e); ? ? ? ? ? ? } ? ? ? ? ? ? Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定時任務類必須實現(xiàn)ScheduledOfTask接口"); ? ? ? ? ? ? // 可以通過改變數據庫數據進而實現(xiàn)動態(tài)改變執(zhí)行周期 ? ? ? ? ? ? taskRegistrar.addTriggerTask(((Runnable) task), ? ? ? ? ? ? ? ? ? ? triggerContext -> { ? ? ? ? ? ? ? ? ? ? ? ? String cronExpression = timedTaskEntity.getTaskCron(); ? ? ? ? ? ? ? ? ? ? ? ? return new CronTrigger(cronExpression).nextExecutionTime(triggerContext); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ); ? ? ? ? } ? ? }
其實原因很簡單,就是因為SchedulingConfigurer使用的是單線程的方式,taskRegistrar.addTriggerTask添加完會阻塞,導致后面的ApplicationRunner和CommandLineRunner無法執(zhí)行。
解決辦法
1.只需要修改下SchedulingConfigurer實現(xiàn)
@Override ? ? public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ? ? ? ? for(SysTimedTaskEntity timedTaskEntity : timedTaskDao.selectAll()){ ? ? ? ? ? ? Class<?> clazz; ? ? ? ? ? ? Object task; ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? clazz = Class.forName(timedTaskEntity.getTaskPath()); ? ? ? ? ? ? ? ? task = context.getBean(clazz); ? ? ? ? ? ? } catch (ClassNotFoundException e) { ? ? ? ? ? ? ? ? throw new IllegalArgumentException("sys_timed_task表數據" + timedTaskEntity.getTaskPath() + "有誤", e); ? ? ? ? ? ? } catch (BeansException e) { ? ? ? ? ? ? ? ? throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "未納入到spring管理", e); ? ? ? ? ? ? } ? ? ? ? ? ? Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定時任務類必須實現(xiàn)ScheduledOfTask接口"); ? ? ? ? ? ? // 可以通過改變數據庫數據進而實現(xiàn)動態(tài)改變執(zhí)行周期 ? ? ? ? ? ? taskRegistrar.addTriggerTask(((Runnable) task), ? ? ? ? ? ? ? ? ? ? triggerContext -> { ? ? ? ? ? ? ? ? ? ? ? ? String cronExpression = timedTaskEntity.getTaskCron(); ? ? ? ? ? ? ? ? ? ? ? ? return new CronTrigger(cronExpression).nextExecutionTime(triggerContext); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ); ? ? ? ? ? ? /** 解決辦法如下 */? ? ? ? ? ? ? // 手動創(chuàng)建線程池,防止SchedulingConfigurer導致系統(tǒng)線程阻塞 ? ? ? ? ? ? taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(10, new ThreadFactory() { ? ? ? ? ? ? ? ? int counter = 0; ? ? ? ? ? ? ? ? @Override ? ? ? ? ? ? ? ? public Thread newThread(Runnable r) { ? ? ? ? ? ? ? ? ? ? Thread thread = new Thread(r,"數據統(tǒng)計-Thread-"+counter); ? ? ? ? ? ? ? ? ? ? counter++; ? ? ? ? ? ? ? ? ? ? return thread; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? })); ? ? ? ? } ? ? }
SpringBoot的ApplicationRunner問題
在開發(fā)中可能會有這樣的情景。需要在容器啟動的時候執(zhí)行一些內容。比如讀取配置文件,數據庫連接之類的。
SpringBoot給我們提供了兩個接口來幫助我們實現(xiàn)這種需求。
這兩個接口分別為CommandLineRunner和ApplicationRunner。他們的執(zhí)行時機為容器啟動完成的時候。
這兩個接口中有一個run方法,我們只需要實現(xiàn)這個方法即可。
這兩個接口的不同之處在于:ApplicationRunner中run方法的參數為ApplicationArguments,而CommandLineRunner接口中run方法的參數為String數組。
目前我在項目中用的是ApplicationRunner。是這么實現(xiàn)的:
package com.jdddemo.demo.controller; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; @Component public class JDDRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println(args); System.out.println("這個是測試ApplicationRunner接口"); } }
執(zhí)行結果如下:
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java語言實現(xiàn)簡單FTP軟件 FTP連接管理模塊實現(xiàn)(8)
這篇文章主要為大家詳細介紹了Java語言實現(xiàn)簡單FTP軟件,F(xiàn)TP連接管理模塊的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04Spring IOC:CreateBean環(huán)節(jié)中的流程轉換
Spring IOC 體系是一個很值得深入和研究的結構 , 只有自己真正的讀一遍 , 才能有更好的理解.這篇文章主要說明一下 CreateBean 整個環(huán)節(jié)中的大流程轉換 , 便于查找問題的原因2021-05-05使用@ConfigurationProperties實現(xiàn)類型安全的配置過程
這篇文章主要介紹了使用@ConfigurationProperties實現(xiàn)類型安全的配置過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02jvm細節(jié)探索之synchronized及實現(xiàn)問題分析
這篇文章主要介紹了jvm細節(jié)探索之synchronized及實現(xiàn)問題分析,涉及synchronized的字節(jié)碼表示,JVM中鎖的優(yōu)化,對象頭的介紹等相關內容,具有一定借鑒價值,需要的朋友可以參考下。2017-11-11