日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一張圖幫你記憶,SpringBoot應(yīng)用在啟動(dòng)階段執(zhí)行代碼的幾種方式

前言

有時(shí)候我們需要在應(yīng)用啟動(dòng)時(shí)執(zhí)行一些代碼片段,這些片段可能是僅僅是為了記錄 log,也可能是在啟動(dòng)時(shí)檢查與安裝證書 ,諸如上述業(yè)務(wù)要求我們可能會(huì)經(jīng)常碰到

Spring Boot 提供了至少 5 種方式用于在應(yīng)用啟動(dòng)時(shí)執(zhí)行代碼。我們應(yīng)該如何選擇?本文將會(huì)逐步解釋與分析這幾種不同方式

CommandLineRunner

CommandLineRunner 是一個(gè)接口,通過實(shí)現(xiàn)它,我們可以在 Spring 應(yīng)用成功啟動(dòng)之后 執(zhí)行一些代碼片段

 
 
 
 
  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.     }  

當(dāng) Spring Boot 在應(yīng)用上下文中找到 CommandLineRunner bean,它將會(huì)在應(yīng)用成功啟動(dòng)之后調(diào)用 run() 方法,并傳遞用于啟動(dòng)應(yīng)用程序的命令行參數(shù)

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

 
 
 
 
  1. mvn clean package 

通過終端命令啟動(dòng)應(yīng)用,并傳遞參數(shù):

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

查看運(yùn)行結(jié)果:

到這里我們可以看出幾個(gè)問題:

  1.  命令行傳入的參數(shù)并沒有被解析,而只是顯示出我們傳入的字符串內(nèi)容 --foo=bar,--name=rgyb,我們可以通過 ApplicationRunner 解析,我們稍后看
  2.  在重寫的 run() 方法上有 throws Exception 標(biāo)記,Spring Boot 會(huì)將 CommandLineRunner 作為應(yīng)用啟動(dòng)的一部分,如果運(yùn)行 run() 方法時(shí)拋出 Exception,應(yīng)用將會(huì)終止啟動(dòng)
  3.  我們?cè)陬惿咸砑恿?@Order(2) 注解,當(dāng)有多個(gè) CommandLineRunner 時(shí),將會(huì)按照 @Order 注解中的數(shù)字從小到大排序 (數(shù)字當(dāng)然也可以用復(fù)數(shù))

不要使用 @Order 太多

看到 order 這個(gè) "黑科技" 我們會(huì)覺得它可以非常方便將啟動(dòng)邏輯按照指定順序執(zhí)行,但如果你這么寫,說明多個(gè)代碼片段是有相互依賴關(guān)系的,為了讓我們的代碼更好維護(hù),我們應(yīng)該減少這種依賴使用

小結(jié)

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

ApplicationRunner

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

 
 
 
 
  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 包,運(yùn)行如下命令:

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

運(yùn)行結(jié)果如下:

到這里我們可以看出:

  1.  同 MyCommandLineRunner 相似,但 ApplicationRunner 可以通過 run 方法的 ApplicationArguments 對(duì)象解析出命令行參數(shù),并且每個(gè)參數(shù)可以有多個(gè)值在里面,因?yàn)?getOptionValues 方法返回 List 數(shù)組

      2.  在重寫的 run() 方法上有 throws Exception 標(biāo)記,Spring Boot 會(huì)將 CommandLineRunner 作為應(yīng)用啟動(dòng)的一部分,如果運(yùn)行 run() 方法時(shí)拋出 Exception,應(yīng)用將會(huì)終止啟動(dòng)

      3.  ApplicationRunner 也可以使用 @Order 注解進(jìn)行排序,從啟動(dòng)結(jié)果來看,它與 CommandLineRunner 共享 order 的順序,稍后我們通過源碼來驗(yàn)證這個(gè)結(jié)論

小結(jié)

如果我們想獲取復(fù)雜的命令行參數(shù)時(shí),我們可以使用 ApplicationRunner

ApplicationListener

如果我們不需要獲取命令行參數(shù)時(shí),我們可以將啟動(dòng)邏輯綁定到 Spring 的 ApplicationReadyEvent 上

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

運(yùn)行程序查看結(jié)果:

到這我們可以看出:

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

小結(jié)

如果我們不需要獲取命令行參數(shù),我們可以通過 ApplicationListener 創(chuàng)建一些全局的啟動(dòng)邏輯,我們還可以通過它獲取 Spring Boot 支持的 configuration properties 環(huán)境變量參數(shù)

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

  •  Spring Bean 生命周期之緣起
  •  Spring Bean 生命周期之緣盡
  •  Spring Aware 到底是什么?

那么你會(huì)對(duì)下面兩種方式非常熟悉了

@PostConstruct

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

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

查看運(yùn)行結(jié)果:

從上面運(yùn)行結(jié)果可以看出:

  1.  Spring 創(chuàng)建完 bean之后 (在啟動(dòng)之前),便會(huì)立即調(diào)用 @PostConstruct 注解標(biāo)記的方法,因此我們無法使用 @Order 注解對(duì)其進(jìn)行自由排序,因?yàn)樗赡芤蕾囉?@Autowired 插入到我們 bean 中的其他 Spring bean。

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

小結(jié)

@PostConstruct 方法固有地綁定到現(xiàn)有的 Spring bean,因此應(yīng)僅將其用于此單個(gè) bean 的初始化邏輯;

InitializingBean

與 @PostConstruct 解決方案非常相似,我們可以實(shí)現(xiàn) InitializingBean 接口,并讓 Spring 調(diào)用某個(gè)初始化方法:

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

查看運(yùn)行結(jié)果:

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

@PostConstruct 和 afterPropertiesSet 區(qū)別

  1.   afterPropertiesSet,顧名思義「在屬性設(shè)置之后」,調(diào)用該方法時(shí),該 bean 的所有屬性已經(jīng)被 Spring 填充。如果我們?cè)谀承傩陨鲜褂?@Autowired(常規(guī)操作應(yīng)該使用構(gòu)造函數(shù)注入),那么 Spring 將在調(diào)用afterPropertiesSet 之前將 bean 注入這些屬性。但 @PostConstruct 并沒有這些屬性填充限制

       2.   所以 InitializingBean.afterPropertiesSet 解決方案比使用 @PostConstruct 更安全,因?yàn)槿绻覀円蕾嚿形醋詣?dòng)注入的 @Autowired 字段,則 @PostConstruct 方法可能會(huì)遇到 NullPointerExceptions

小結(jié)

如果我們使用構(gòu)造函數(shù)注入,則這兩種解決方案都是等效的

源碼分析

請(qǐng)打開你的 IDE (重點(diǎn)代碼已標(biāo)記注釋):

MyCommandLineRunner 和 ApplicationRunner 是在何時(shí)被調(diào)用的呢?

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

 
 
 
 
  1. private void callRunners(ApplicationContext context, ApplicationArguments args) {  
  2.     List 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.     //對(duì)二者進(jìn)行排序,這也就是為什么二者的 order 是可以共享的了  
  8.     AnnotationAwareOrderComparator.sort(runners);  
  9.     //遍歷對(duì)其進(jìn)行調(diào)用  
  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.     }  
  18. 強(qiáng)烈建議完整看一下 SpringApplication.java 的全部代碼,Spring Boot 啟動(dòng)過程及原理都可以從這個(gè)類中找到一些答案

    總結(jié)

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

    靈魂追問

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

    在寫 Spring Bean 生命周期時(shí)就有朋友問我與之相關(guān)的問題,顯然他們?cè)诟拍钌嫌幸恍┖欤?,仔?xì)理解上面的問題將會(huì)幫助你加深對(duì) Spring Bean 生命周期的理解

    歡迎關(guān)注我的公眾號(hào) 「日拱一兵」,趣味原創(chuàng)解析Java技術(shù)棧問題,將復(fù)雜問題簡單化,將抽象問題圖形化落地

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


    當(dāng)前題目:一張圖幫你記憶,SpringBoot應(yīng)用在啟動(dòng)階段執(zhí)行代碼的幾種方式
    文章分享:http://www.dlmjj.cn/article/cdsgpph.html