目錄
1、CommandLineRunner
SpringBoot中CommandLineRunner的作用
簡單例子
多個類實現CommandLineRunner接口執行順序的保證
通過實現Ordered接口實現控制執行順序
通過@Order注解實現控制執行順序
@Order?作用
2、ApplicationRunner
3、傳遞參數
4、源碼跟蹤
run()方法
callRunners方法
1、CommandLineRunner
SpringBoot中CommandLineRunner
的作用
平常開發中有可能需要實現在項目啟動后執行的功能,SpringBoot提供的一種簡單的實現方案就是添加一個model并實現CommandLineRunner
接口,實現功能的代碼放在實現的run
方法中。也就是項目一啟動之后,就立即需要執行的動作。只需要在項目里面簡單的配置,就可以實現這個功能。?
簡單例子
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyStartupRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("項目已經啟動");}
}
多個類實現CommandLineRunner
接口執行順序的保證
通過實現Ordered
接口實現控制執行順序
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/*** 優先級最高* 該類期望在springboot 啟動后第一順位執行* @since 12:57**/
@Slf4j
@Component
public class HighOrderCommandLineRunner implements CommandLineRunner, Ordered {@Overridepublic void run(String... args) throws Exception {for (String arg : args) {log.info("arg = " + arg);}log.info("i am highOrderRunner");}@Overridepublic int getOrder() {return Integer.MIN_VALUE+1;}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/*** 優先級低于{@code HighOrderCommandLineRunner}* @since 12:59**/
@Slf4j
@Component
public class LowOrderCommandLineRunner implements CommandLineRunner, Ordered {@Overridepublic void run(String... args) throws Exception {log.info("i am lowOrderRunner");}@Overridepublic int getOrder() {return Integer.MIN_VALUE+1;}
}
啟動Spring Boot程序后,控制臺按照預定的順序打印出了結果:
2020-05-30 23:11:03.685 INFO 11976 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-30 23:11:03.701 INFO 11976 --- [ main] c.f.Application : Started SpringBootApplication in 4.272 seconds (JVM running for 6.316)
2020-05-30 23:11:03.706 INFO 11976 --- [ main] c.f.HighOrderCommandLineRunner : i am highOrderRunner
2020-05-30 23:11:03.706 INFO 11976 --- [ main] c.f.LowOrderCommandLineRunner : i am lowOrderRunner
通過@Order
注解實現控制執行順序
SpringBoot在項目啟動后會遍歷所有實現CommandLineRunner
的實體類并執行run
方法,如果需要按照一定的順序去執行,那么就需要在實體類上使用一個@Order
注解(或者實現Order
接口)來表明順序
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=2)
public class MyStartupRunner1 implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("執行2");}
}
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=1)
public class MyStartupRunner2 implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("執行1");}
}
控制臺顯示
執行1
執行2
根據控制臺結果可判斷,
@Order
?注解的執行優先級是按value值從小到大順序。
@Order
?作用
項目啟動之后,要執行的動作是比較的多,那么到底先執行哪個,那么就可以利用這個注解限定優先級。 :::danger?Ordered
接口并不能被?@Order
注解所代替。
2、ApplicationRunner
在Spring Boot 1.3.0又引入了一個和CommandLineRunner
功能一樣的接口ApplicationRunner
。CommandLineRunner
接收可變參數String... args
,而ApplicationRunner
?接收一個封裝好的對象參數ApplicationArguments
。除此之外它們功能完全一樣,甚至連方法名都一樣。聲明一個ApplicationRunner
并讓它優先級最低:
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/*** 優先級最低**/
@Slf4j
@Component
public class DefaultApplicationRunner implements ApplicationRunner, Ordered {@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("i am applicationRunner");Set<String> optionNames = args.getOptionNames();log.info("optionNames = " + optionNames);String[] sourceArgs = args.getSourceArgs();log.info("sourceArgs = " + Arrays.toString(sourceArgs));List<String> nonOptionArgs = args.getNonOptionArgs();log.info("nonOptionArgs = " + nonOptionArgs);List<String> optionValues = args.getOptionValues("foo");log.info("optionValues = " + optionValues);}@Overridepublic int getOrder() {return Integer.MIN_VALUE+2;}
}
按照順序打印了三個類的執行結果:
2020-06-01 13:02:39.420 INFO 19032 --- [ main] c.f.MybatisResultmapApplication : Started MybatisResultmapApplication in 1.801 seconds (JVM running for 2.266)
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.HighOrderCommandLineRunner : i am highOrderRunner
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.LowOrderCommandLineRunner : i am lowOrderRunner
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.DefaultApplicationRunner : i am applicationRunner
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.DefaultApplicationRunner : optionNames = []
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.DefaultApplicationRunner : sourceArgs = []
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.DefaultApplicationRunner : nonOptionArgs = []
2020-06-01 13:02:39.423 INFO 19032 --- [ main] c.f.DefaultApplicationRunner : optionValues = null
optionValues = null
Ordered
接口并不能被?@Order
注解所代替。
3、傳遞參數
Spring Boot應用啟動時是可以接受參數的,換句話說也就是Spring Boot
的main
方法是可以接受參數的。這些參數通過命令行?java -jar yourapp.jar
?來傳遞。CommandLineRunner
會原封不動照單全收這些接口,這些參數也可以封裝到ApplicationArguments
對象中供ApplicationRunner
調用。看一下ApplicationArguments
的相關方法:
getSourceArgs()
?被傳遞給應用程序的原始參數,返回這些參數的字符串數組。getOptionNames()
?獲取選項名稱的Set
字符串集合。如?--spring.profiles.active=dev --debug
?將返回["spring.profiles.active","debug"]
?。getOptionValues(String name)
?通過名稱來獲取該名稱對應的選項值。如--foo=bar --foo=baz
?將返回["bar","baz"]
。containsOption(String name)
?用來判斷是否包含某個選項的名稱。getNonOptionArgs()
?用來獲取所有的無選項參數。
可以通過下面的命令運行一個 Spring Boot應用 Jar
java -jar yourapp.jar --foo=bar --foo=baz --dev.name=fcant java fcantcn
或者在IDEA開發工具中打開Spring Boot應用main
方法的配置項,進行命令行參數的配置,其他IDE工具同理。
運行Spring Boot應用,將會打印出:
2020-06-01 15:04:31.490 INFO 13208 --- [ main] c.f.HighOrderCommandLineRunner : arg = --foo=bar
2020-06-01 15:04:31.490 INFO 13208 --- [ main] c.f.HighOrderCommandLineRunner : arg = --foo=baz
2020-06-01 15:04:31.490 INFO 13208 --- [ main] c.f.HighOrderCommandLineRunner : arg = --dev.name=fcant
2020-06-01 15:04:31.490 INFO 13208 --- [ main] c.f.HighOrderCommandLineRunner : arg = java
2020-06-01 15:04:31.490 INFO 13208 --- [ main] c.f.HighOrderCommandLineRunner : arg = fcantcn
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.HighOrderCommandLineRunner : i am highOrderRunner
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.LowOrderCommandLineRunner : i am lowOrderRunner
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.DefaultApplicationRunner : i am applicationRunner
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.DefaultApplicationRunner : optionNames = [dev.name, foo]
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.DefaultApplicationRunner : sourceArgs = [--foo=bar, --foo=baz, --dev.name=fcant, java, fcantcn]
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.DefaultApplicationRunner : nonOptionArgs = [java, fcantcn]
2020-06-01 15:04:31.491 INFO 13208 --- [ main] c.f.DefaultApplicationRunner : optionValues = [bar, baz]
4、源碼跟蹤
run()
方法
跟進run
方法后,一路F6直達以下方法
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();//設置線程啟動計時器stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();//配置系統屬性:默認缺失外部顯示屏等允許啟動configureHeadlessProperty();//獲取并啟動事件監聽器,如果項目中沒有其他監聽器,則默認只有EventPublishingRunListenerSpringApplicationRunListeners listeners = getRunListeners(args);//將事件廣播給listenerslisteners.starting();try {//對于實現ApplicationRunner接口,用戶設置ApplicationArguments參數進行封裝ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//配置運行環境:例如激活應用***.yml配置文件 ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);//加載配置的banner(gif,txt...),即控制臺圖樣Banner printedBanner = printBanner(environment);//創建上下文對象,并實例化context = createApplicationContext();exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);//配置SPring容器 prepareContext(context, environment, listeners, applicationArguments,printedBanner);//刷新Spring上下文,創建bean過程中 refreshContext(context);//空方法,子類實現afterRefresh(context, applicationArguments);//停止計時器:計算線程啟動共用時間stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}//停止事件監聽器listeners.started(context);//開始加載資源callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, listeners, exceptionReporters, ex);throw new IllegalStateException(ex);}listeners.running(context);return context;
}
主要是熟悉SpringBoot的CommandLineRunner
接口實現原理。因此上面SpringBoot啟動過程方法不做過多介紹。直接進入CallRunners()
方法內部。?
callRunners
方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {//將實現ApplicationRunner和CommandLineRunner接口的類,存儲到集合中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);}}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {try {//調用各個實現類中的邏輯實現(runner).run(args.getSourceArgs());}catch (Exception ex) {throw new IllegalStateException("Failed to execute CommandLineRunner", ex);}
}
到此結束,再跟進run()
方法,就可以看到資源加載邏輯。
如果小假的內容對你有幫助,請點贊,評論,收藏。創作不易,大家的支持就是我堅持下去的動力!