SchedulingConfigurer實(shí)現(xiàn)動(dòng)態(tài)定時(shí),導(dǎo)致ApplicationRunner無(wú)效解決
SchedulingConfigurer實(shí)現(xiàn)動(dòng)態(tài)定時(shí),導(dǎo)致ApplicationRunner無(wú)效
問(wèn)題描述
當(dāng)通過(guò)SchedulingConfigurer接口實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)后,發(fā)現(xiàn)ApplicationRunner接口實(shí)現(xiàn)的邏輯不生效了,斷點(diǎn)不進(jìn),說(shuō)明ApplicationRunner接口實(shí)現(xiàn)的方法并沒(méi)有執(zhí)行。
問(wèn)題解釋
SchedulingConfigurer接口是使用Spring實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)必然的一步,而ApplicationRunner接口為的是在容器(服務(wù))啟動(dòng)完成后,進(jìn)行一些操作,同樣效果的還有接口CommandLineRunner,那么是因?yàn)樯秾?dǎo)致實(shí)現(xiàn)SchedulingConfigurer接口后ApplicationRunner和CommandLineRunner的接口實(shí)現(xiàn)就不生效了呢?
原因剖析
導(dǎo)致ApplicationRunner和CommandLineRunner接口失效的原因還要看他倆的實(shí)現(xiàn)原理。
首先我們要明確一個(gè)概念,那就是雖然它倆名字含有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注解說(shuō)明了ApplicationRunner是個(gè)函數(shù)式接口,不了解的童鞋看下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ū)別就是入?yún)⒉煌?那么它倆的實(shí)現(xiàn)方法在什么時(shí)候執(zhí)行的呢?簡(jiǎn)單說(shuō)就是在ApplicationContext.run()方法中,會(huì)調(diào)用callRunners方法。
該方法獲取所有實(shí)現(xiàn)了ApplicationRunner和CommandLineRunner的接口bean,然后依次執(zhí)行對(duì)應(yīng)的run方法,并且是在同一個(gè)線程中執(zhí)行。因此如果有某個(gè)實(shí)現(xiàn)了ApplicationRunner接口的bean的run方法一直循環(huán)不返回的話,后續(xù)的代碼將不會(huì)被執(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);
?? ??? ??? ?}
?? ??? ?}
?? ?}所以存在猜測(cè),SchedulingConfigurer的實(shí)現(xiàn)方式影響了這倆,看看SchedulingConfigurer的實(shí)現(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表數(shù)據(jù)" + timedTaskEntity.getTaskPath() + "有誤", e);
? ? ? ? ? ? } catch (BeansException e) {
? ? ? ? ? ? ? ? throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "未納入到spring管理", e);
? ? ? ? ? ? }
? ? ? ? ? ? Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定時(shí)任務(wù)類必須實(shí)現(xiàn)ScheduledOfTask接口");
? ? ? ? ? ? // 可以通過(guò)改變數(shù)據(jù)庫(kù)數(shù)據(jù)進(jìn)而實(shí)現(xiàn)動(dòng)態(tài)改變執(zhí)行周期
? ? ? ? ? ? taskRegistrar.addTriggerTask(((Runnable) task),
? ? ? ? ? ? ? ? ? ? triggerContext -> {
? ? ? ? ? ? ? ? ? ? ? ? String cronExpression = timedTaskEntity.getTaskCron();
? ? ? ? ? ? ? ? ? ? ? ? return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? );
? ? ? ? }
? ? }其實(shí)原因很簡(jiǎn)單,就是因?yàn)镾chedulingConfigurer使用的是單線程的方式,taskRegistrar.addTriggerTask添加完會(huì)阻塞,導(dǎo)致后面的ApplicationRunner和CommandLineRunner無(wú)法執(zhí)行。
解決辦法
1.只需要修改下SchedulingConfigurer實(shí)現(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表數(shù)據(jù)" + timedTaskEntity.getTaskPath() + "有誤", e);
? ? ? ? ? ? } catch (BeansException e) {
? ? ? ? ? ? ? ? throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "未納入到spring管理", e);
? ? ? ? ? ? }
? ? ? ? ? ? Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定時(shí)任務(wù)類必須實(shí)現(xiàn)ScheduledOfTask接口");
? ? ? ? ? ? // 可以通過(guò)改變數(shù)據(jù)庫(kù)數(shù)據(jù)進(jìn)而實(shí)現(xiàn)動(dòng)態(tài)改變執(zhí)行周期
? ? ? ? ? ? taskRegistrar.addTriggerTask(((Runnable) task),
? ? ? ? ? ? ? ? ? ? triggerContext -> {
? ? ? ? ? ? ? ? ? ? ? ? String cronExpression = timedTaskEntity.getTaskCron();
? ? ? ? ? ? ? ? ? ? ? ? return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? );
? ? ? ? ? ? /** 解決辦法如下 */?
? ? ? ? ? ? // 手動(dòng)創(chuàng)建線程池,防止SchedulingConfigurer導(dǎo)致系統(tǒng)線程阻塞
? ? ? ? ? ? taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(10, new ThreadFactory() {
? ? ? ? ? ? ? ? int counter = 0;
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public Thread newThread(Runnable r) {
? ? ? ? ? ? ? ? ? ? Thread thread = new Thread(r,"數(shù)據(jù)統(tǒng)計(jì)-Thread-"+counter);
? ? ? ? ? ? ? ? ? ? counter++;
? ? ? ? ? ? ? ? ? ? return thread;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }));
? ? ? ? }
? ? }SpringBoot的ApplicationRunner問(wèn)題
在開(kāi)發(fā)中可能會(huì)有這樣的情景。需要在容器啟動(dòng)的時(shí)候執(zhí)行一些內(nèi)容。比如讀取配置文件,數(shù)據(jù)庫(kù)連接之類的。
SpringBoot給我們提供了兩個(gè)接口來(lái)幫助我們實(shí)現(xiàn)這種需求。
這兩個(gè)接口分別為CommandLineRunner和ApplicationRunner。他們的執(zhí)行時(shí)機(jī)為容器啟動(dòng)完成的時(shí)候。
這兩個(gè)接口中有一個(gè)run方法,我們只需要實(shí)現(xiàn)這個(gè)方法即可。
這兩個(gè)接口的不同之處在于:ApplicationRunner中run方法的參數(shù)為ApplicationArguments,而CommandLineRunner接口中run方法的參數(shù)為String數(shù)組。
目前我在項(xiàng)目中用的是ApplicationRunner。是這么實(shí)現(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("這個(gè)是測(cè)試ApplicationRunner接口");
}
}執(zhí)行結(jié)果如下:

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Druid數(shù)據(jù)庫(kù)連接池的方法
Druid是Java語(yǔ)言中最好的數(shù)據(jù)庫(kù)連接池。Druid能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能。這篇文章主要介紹了SpringBoot整合Druid數(shù)據(jù)庫(kù)連接池的方法,需要的朋友可以參考下2020-07-07
spring?jpa設(shè)置多個(gè)主鍵遇到的小坑及解決
這篇文章主要介紹了spring?jpa設(shè)置多個(gè)主鍵遇到的小坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
Struts2 ActionContext 中的數(shù)據(jù)詳解
這篇文章主要介紹了Struts2 ActionContext 中的數(shù)據(jù)詳解,需要的朋友可以參考下2016-07-07
Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件 FTP連接管理模塊實(shí)現(xiàn)(8)
這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件,F(xiàn)TP連接管理模塊的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Spring IOC:CreateBean環(huán)節(jié)中的流程轉(zhuǎn)換
Spring IOC 體系是一個(gè)很值得深入和研究的結(jié)構(gòu) , 只有自己真正的讀一遍 , 才能有更好的理解.這篇文章主要說(shuō)明一下 CreateBean 整個(gè)環(huán)節(jié)中的大流程轉(zhuǎn)換 , 便于查找問(wèn)題的原因2021-05-05
Java練習(xí)之潛艇小游戲的實(shí)現(xiàn)
這篇文章主要和大家分享一個(gè)Java小練習(xí)——利用Java編寫(xiě)一個(gè)潛艇小游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-03-03
使用@ConfigurationProperties實(shí)現(xiàn)類型安全的配置過(guò)程
這篇文章主要介紹了使用@ConfigurationProperties實(shí)現(xiàn)類型安全的配置過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
Spring AOP 自定義注解的實(shí)現(xiàn)代碼
本篇文章主要介紹了Spring AOP 自定義注解的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
jvm細(xì)節(jié)探索之synchronized及實(shí)現(xiàn)問(wèn)題分析
這篇文章主要介紹了jvm細(xì)節(jié)探索之synchronized及實(shí)現(xiàn)問(wèn)題分析,涉及synchronized的字節(jié)碼表示,JVM中鎖的優(yōu)化,對(duì)象頭的介紹等相關(guān)內(nèi)容,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-11-11

