成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一張圖幫你記憶,Spring Boot應用在啟動階段執行代碼的幾種方式

開發 后端
Spring Boot 提供了至少 5 種方式用于在應用啟動時執行代碼。我們應該如何選擇?本文將會逐步解釋與分析這幾種不同方式

前言

有時候我們需要在應用啟動時執行一些代碼片段,這些片段可能是僅僅是為了記錄 log,也可能是在啟動時檢查與安裝證書 ,諸如上述業務要求我們可能會經常碰到

Spring Boot 提供了至少 5 種方式用于在應用啟動時執行代碼。我們應該如何選擇?本文將會逐步解釋與分析這幾種不同方式

CommandLineRunner

CommandLineRunner 是一個接口,通過實現它,我們可以在 Spring 應用成功啟動之后 執行一些代碼片段 

  1. @Slf4j  
  2. @Component  
  3. @Order(2)  
  4. public class MyCommandLineRunner implements CommandLineRunner {   
  5.     @Override  
  6.     public void run(String... args) throws Exception {  
  7.         log.info("MyCommandLineRunner order is 2");  
  8.         if (args.length > 0){  
  9.             for (int i = 0; i < args.length; i++) {  
  10.                 log.info("MyCommandLineRunner current parameter is: {}", args[i]);  
  11.             }  
  12.         }  
  13.     }  

當 Spring Boot 在應用上下文中找到 CommandLineRunner bean,它將會在應用成功啟動之后調用 run() 方法,并傳遞用于啟動應用程序的命令行參數

通過如下 maven 命令生成 jar 包: 

  1. mvn clean package 

通過終端命令啟動應用,并傳遞參數: 

  1. java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar --name=rgyb 

查看運行結果:

到這里我們可以看出幾個問題:

  1.  命令行傳入的參數并沒有被解析,而只是顯示出我們傳入的字符串內容 --foo=bar,--name=rgyb,我們可以通過 ApplicationRunner 解析,我們稍后看
  2.  在重寫的 run() 方法上有 throws Exception 標記,Spring Boot 會將 CommandLineRunner 作為應用啟動的一部分,如果運行 run() 方法時拋出 Exception,應用將會終止啟動
  3.  我們在類上添加了 @Order(2) 注解,當有多個 CommandLineRunner 時,將會按照 @Order 注解中的數字從小到大排序 (數字當然也可以用復數)

不要使用 @Order 太多

看到 order 這個 "黑科技" 我們會覺得它可以非常方便將啟動邏輯按照指定順序執行,但如果你這么寫,說明多個代碼片段是有相互依賴關系的,為了讓我們的代碼更好維護,我們應該減少這種依賴使用

小結

如果我們只是想簡單的獲取以空格分隔的命令行參數,那 MyCommandLineRunner 就足夠使用了

ApplicationRunner

上面提到,通過命令行啟動并傳遞參數,MyCommandLineRunner 不能解析參數,如果要解析參數,那我們就要用到 ApplicationRunner 參數了 

  1. @Component  
  2. @Slf4j 
  3.  @Order(1)  
  4. public class MyApplicationRunner implements ApplicationRunner {  
  5.     @Override 
  6.     public void run(ApplicationArguments args) throws Exception {  
  7.         log.info("MyApplicationRunner order is 1");  
  8.         log.info("MyApplicationRunner Current parameter is {}:", args.getOptionValues("foo"));  
  9.     }  

重新打 jar 包,運行如下命令: 

  1. java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar,rgyb 

運行結果如下:

到這里我們可以看出:

  1.  同 MyCommandLineRunner 相似,但 ApplicationRunner 可以通過 run 方法的 ApplicationArguments 對象解析出命令行參數,并且每個參數可以有多個值在里面,因為 getOptionValues 方法返回 List<String> 數組

      2.  在重寫的 run() 方法上有 throws Exception 標記,Spring Boot 會將 CommandLineRunner 作為應用啟動的一部分,如果運行 run() 方法時拋出 Exception,應用將會終止啟動

      3.  ApplicationRunner 也可以使用 @Order 注解進行排序,從啟動結果來看,它與 CommandLineRunner 共享 order 的順序,稍后我們通過源碼來驗證這個結論

小結

如果我們想獲取復雜的命令行參數時,我們可以使用 ApplicationRunner

ApplicationListener

如果我們不需要獲取命令行參數時,我們可以將啟動邏輯綁定到 Spring 的 ApplicationReadyEvent 上 

  1. @Slf4j  
  2. @Component  
  3. @Order(0)  
  4. public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {  
  5.     @Override  
  6.     public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {  
  7.         log.info("MyApplicationListener is started up");  
  8.     }  

運行程序查看結果:

到這我們可以看出:

  1.  ApplicationReadyEvent 當且僅當 在應用程序就緒之后才被觸發,甚至是說上面的 Listener 要在本文說的所有解決方案都執行了之后才會被觸發,最終結論請稍后看
  2.  代碼中我用 Order(0) 來標記,顯然 ApplicationListener 也是可以用該注解進行排序的,按數字大小排序,應該是最先執行。但是,這個順序僅用于同類型的 ApplicationListener 之間的排序,與前面提到的 ApplicationRunners 和 CommandLineRunners 的排序并不共享

小結

如果我們不需要獲取命令行參數,我們可以通過 ApplicationListener<ApplicationReadyEvent> 創建一些全局的啟動邏輯,我們還可以通過它獲取 Spring Boot 支持的 configuration properties 環境變量參數

如果你看過我之前寫的 Spring Bean 生命周期三部曲:

那么你會對下面兩種方式非常熟悉了

@PostConstruct

創建啟動邏輯的另一種簡單解決方案是提供一種在 bean 創建期間由 Spring 調用的初始化方法。我們要做的就只是將 @PostConstruct 注解添加到方法中: 

  1. @Component  
  2. @Slf4j  
  3. @DependsOn("myApplicationListener")  
  4. public class MyPostConstructBean {  
  5.     @PostConstruct  
  6.     public void testPostConstruct(){  
  7.         log.info("MyPostConstructBean");  
  8.     }  

查看運行結果:

從上面運行結果可以看出:

  1.  Spring 創建完 bean之后 (在啟動之前),便會立即調用 @PostConstruct 注解標記的方法,因此我們無法使用 @Order 注解對其進行自由排序,因為它可能依賴于 @Autowired 插入到我們 bean 中的其他 Spring bean。

      2.  相反,它將在依賴于它的所有 bean 被初始化之后被調用,如果要添加人為的依賴關系并由此創建一個排序,則可以使用 @DependsOn 注解(雖然可以排序,但是不建議使用,理由和 @Order 一樣)

小結

@PostConstruct 方法固有地綁定到現有的 Spring bean,因此應僅將其用于此單個 bean 的初始化邏輯;

InitializingBean

與 @PostConstruct 解決方案非常相似,我們可以實現 InitializingBean 接口,并讓 Spring 調用某個初始化方法: 

  1. @Component  
  2. @Slf4j  
  3. public class MyInitializingBean implements InitializingBean {  
  4.     @Override  
  5.     public void afterPropertiesSet() throws Exception {  
  6.         log.info("MyInitializingBean.afterPropertiesSet()");  
  7.     }  

查看運行結果:

從上面的運行結果中,我們得到了和 @PostConstruct 一樣的效果,但二者還是有差別的

@PostConstruct 和 afterPropertiesSet 區別

  1.   afterPropertiesSet,顧名思義「在屬性設置之后」,調用該方法時,該 bean 的所有屬性已經被 Spring 填充。如果我們在某些屬性上使用 @Autowired(常規操作應該使用構造函數注入),那么 Spring 將在調用afterPropertiesSet 之前將 bean 注入這些屬性。但 @PostConstruct 并沒有這些屬性填充限制

       2.   所以 InitializingBean.afterPropertiesSet 解決方案比使用 @PostConstruct 更安全,因為如果我們依賴尚未自動注入的 @Autowired 字段,則 @PostConstruct 方法可能會遇到 NullPointerExceptions

小結

如果我們使用構造函數注入,則這兩種解決方案都是等效的

源碼分析

請打開你的 IDE (重點代碼已標記注釋):

MyCommandLineRunner 和 ApplicationRunner 是在何時被調用的呢?

打開 SpringApplication.java 類,里面有 callRunners 方法 

  1. private void callRunners(ApplicationContext context, ApplicationArguments args) {  
  2.     List<Object> runners = new ArrayList<>();  
  3.     //從上下文獲取 ApplicationRunner 類型的 bean  
  4.     runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());  
  5.     //從上下文獲取 CommandLineRunner 類型的 bean  
  6.     runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());  
  7.     //對二者進行排序,這也就是為什么二者的 order 是可以共享的了  
  8.     AnnotationAwareOrderComparator.sort(runners);  
  9.     //遍歷對其進行調用  
  10.     for (Object runner : new LinkedHashSet<>(runners)) {  
  11.         if (runner instanceof ApplicationRunner) {  
  12.             callRunner((ApplicationRunner) runner, args);  
  13.         }  
  14.         if (runner instanceof CommandLineRunner) {  
  15.             callRunner((CommandLineRunner) runner, args);  
  16.         }  
  17.     }  

強烈建議完整看一下 SpringApplication.java 的全部代碼,Spring Boot 啟動過程及原理都可以從這個類中找到一些答案

總結

最后畫一張圖用來總結這幾種方式(高清大圖請查看原文:https://dayarch.top/p/spring-...)

靈魂追問

  1.  上面程序運行結果, afterPropertiesSet 方法調用先于 @PostConstruct 方法,但這和我們在 Spring Bean 生命周期之緣起 中的調用順序恰恰相反,你知道為什么嗎?
  2.  MyPostConstructBean 通過 @DependsOn("myApplicationListener") 依賴了 MyApplicationListener,為什么調用結果前者先與后者呢?
  3.  為什么不建議 @Autowired 形式依賴注入

在寫 Spring Bean 生命周期時就有朋友問我與之相關的問題,顯然他們在概念上有一些含混,所以,仔細理解上面的問題將會幫助你加深對 Spring Bean 生命周期的理解

歡迎關注我的公眾號 「日拱一兵」,趣味原創解析Java技術棧問題,將復雜問題簡單化,將抽象問題圖形化落地

如果對我的專題內容感興趣,或搶先看更多內容,歡迎訪問我的博客 dayarch.top  

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2009-12-25 15:11:08

FTTH應用

2021-02-07 09:01:10

Java并發編程

2024-08-07 08:19:13

2021-11-07 15:04:39

機器學習人工智能數據

2022-02-17 08:20:17

Spring執行代碼SpringBoot

2019-09-11 10:12:12

華為

2015-03-10 10:15:27

AppleWatch開發Swift

2019-09-05 09:29:00

CAP理論分布式系統

2015-09-14 09:07:15

Java多線程

2012-07-30 09:48:09

HTML5

2012-12-27 13:52:48

Android開發判斷

2023-04-11 07:46:11

平臺arthas線診斷

2012-07-20 17:24:51

HTML5

2019-03-18 15:00:48

SQLJoin用法數據庫

2022-03-29 14:46:03

元宇宙工業元宇宙物聯網

2019-11-15 14:53:49

大數據平臺智慧城市城市大數據

2013-05-23 14:20:50

PoE以太網IP終端地址

2020-11-27 06:28:55

Spring循環依賴

2020-12-14 18:02:25

區塊鏈人民幣技術

2015-09-23 10:04:03

開放數據
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品国产黄a∨片高清在线 成人区精品一区二区婷婷 日本一区二区视频 | 成人午夜 | 欧美中文字幕一区 | 久久国产精品视频免费看 | 成人免费激情视频 | 91视频电影| 久久久www成人免费无遮挡大片 | 国产精品18久久久 | 亚洲精品日本 | 老外几下就让我高潮了 | 国产精品一区二区三区在线 | 成人福利| 日韩在线免费电影 | 天天操操 | 91精品国产综合久久福利软件 | 五月综合激情在线 | 欧美精品一区二区三区四区五区 | 国产男女视频网站 | 国产一区二区三区久久久久久久久 | 色久电影| 欧美日韩一区二区在线观看 | 欧美激情啪啪 | 日韩视频精品 | 九九久久精品视频 | 日韩欧美一区二区三区免费看 | 色综合一区二区三区 | 国产在线观看不卡一区二区三区 | 日本不卡一区二区 | 欧美日韩高清免费 | 亚洲人成人一区二区在线观看 | 欧美一级片免费看 | 久久精品国产一区二区三区不卡 | 国产精品国色综合久久 | 成人在线精品视频 | 久久av网站 | 性欧美精品一区二区三区在线播放 | 一级片免费视频 | 国产日韩精品在线 | 一本久久a久久精品亚洲 | 狠狠干狠狠操 | 午夜视频一区二区 |