自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一張長圖透徹理解 SpringBoot 啟動原理

開發(fā) 前端
SpringBoot 會在Spring完全啟動完成后,才開啟Http流量。這給了我們啟示:應該在Spring啟動完成后開啟入口流量。Rpc和 MQ流量 也應該如此,所以建議大家 在 SmartLifecype? 或者 ContextRefreshedEvent 等位置 注冊服務,開啟流量。

雖然Java程序員大部分工作都是CRUD,但是工作中常用的中間件必須和Spring集成,如果不知道Spring的原理,很難理解這些中間件和框架的原理。

一張長圖透徹解釋 Spring啟動順序

圖片圖片

測試對Spring啟動原理的理解程度

我舉個例子,測試一下,你對Spring啟動原理的理解程度。

  • Rpc框架和Spring的集成問題。Rpc框架何時注冊暴露服務,在哪個Spring擴展點注冊呢?init-method 中行不行?
  • MQ 消費組和Spring的集成問題。MQ消費者何時開始消費,在哪個Spring擴展點”注冊“自己?init-method 中行不行?
  • SpringBoot 集成Tomcat問題。如果出現(xiàn)已開啟Http流量,Spring還未啟動完成,怎么辦?Tomcat何時開啟端口,對外服務?

SpringBoot項目常見的流量入口無外乎 Rpc、Http、MQ 三種方式。一名合格的架構師必須精通服務的入口流量何時開啟,如何正確開啟?最近我遇到的兩次線上故障都和Spring啟動過程相關。

故障的具體表現(xiàn)是:Kafka消費組已經(jīng)開始消費,已開啟流量,然而Spring 還未啟動完成。因為業(yè)務代碼中使用的Spring Event事件訂閱組件還未啟動(訂閱者還未注冊到Spring),所以處理異常,出了線上故障。根本原因是————項目在錯誤的時機開啟 MQ 流量,然而Spring還未啟動完成,導致出現(xiàn)故障。

正確的做法是:項目在Spring啟動完成后開啟入口流量,然而我司的Kafka消費組在Spring init-method bean 實例化階段就開啟了流量,導致故障發(fā)生。

接下來,我再次拋出 11 個問題,說明這個問題————深入理解Spring啟動原理的重要性。

  1. Spring還未完全啟動,在 PostConstruct 中調(diào)用 getBeanByAnnotation 能否獲得準確的結果?
  2. 項目應該如何監(jiān)聽 Spring 的啟動就緒事件?
  3. 項目如何監(jiān)聽Spring 刷新事件?
  4. Spring就緒事件和刷新事件的執(zhí)行順序和區(qū)別?
  5. Http 流量入口何時啟動完成?
  6. 項目中在 init-method 方法中注冊 Rpc 是否合理?什么是合理的時機?
  7. 項目中在 init-method 方法中注冊 MQ 消費組是否合理?什么是合理的時機?
  8. PostConstruct 中方法依賴ApplicationContextAware拿到 ApplicationContext,兩者的順序誰先誰后?是否會出現(xiàn)空指針!
  9. init-method、PostConstruct、afterPropertiesSet 三個方法的執(zhí)行順序?
  10. 有兩個 Bean聲明了初始化方法。A使用 PostConstruct注解聲明,B使用 init-method 聲明。Spring一定先執(zhí)行 A 的PostConstruct 方法嗎?
  11. Spring 何時裝配Autowire屬性,PostConstruct 方法中引用 Autowired 字段什么場景會空指針?

精通Spring 啟動原理,以上問題則迎刃而解。接下來,大家一起學習Spring的啟動原理,看看Spring的擴展點分別在何時執(zhí)行。

一起數(shù)數(shù) Spring啟動過程的擴展點有幾個?

Spring的擴展點極多,這里為了講清楚啟動原理,所以只列舉和啟動過程有關的擴展點。

  1. BeanFactoryAware 可在Bean 中獲取 BeanFactory 實例
  2. ApplicationContextAware 可在Bean 中獲取 ApplicationContext 實例
  3. BeanNameAware  可以在Bean中得到它在IOC容器中的Bean的實例的名字。
  4. ApplicationListener 可監(jiān)聽 ContextRefreshedEvent等。
  5. CommandLineRunner 整個項目啟動完畢后,自動執(zhí)行
  6. SmartLifecycle#start 在Spring Bean實例化完成后,執(zhí)行start 方法。
  7. 使用@PostConstruct注解,用于Bean實例初始化
  8. 實現(xiàn)InitializingBean接口,用于Bean實例初始化
  9. xml 中聲明 init-method 方法,用于Bean實例初始化
  10. Configuration 配置類 通過@Bean注解 注冊Bean到Spring
  11. BeanPostProcessor 在Bean的初始化前后,植入擴展點!
  12. BeanFactoryPostProcessor 在BeanFactory創(chuàng)建后植入 擴展點!

通過打印日志學習Spring的執(zhí)行順序

首先我們先通過代碼實驗,驗證一下以上擴展點的執(zhí)行順序。

1.聲明 TestSpringOrder 分別繼承以下接口,并且在接口方法實現(xiàn)中,日志打印該接口的名稱。

public class TestSpringOrder implements
      ApplicationContextAware,
      BeanFactoryAware, 
      InitializingBean, 
      SmartLifecycle, 
      BeanNameAware, 
      ApplicationListener<ContextRefreshedEvent>, 
      CommandLineRunner,
      SmartInitializingSingleton {
@Override
public void afterPropertiesSet() throws Exception {
   log.error("啟動順序:afterPropertiesSet");
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
   log.error("啟動順序:setApplicationContext");
}

2.TestSpringOrder 使用 PostConstruct注解初始化,聲明 init-method方法初始化。

@PostConstruct
public void postConstruct() {
   log.error("啟動順序:post-construct");
}

public void initMethod() {
   log.error("啟動順序:init-method");
}

3.新建 TestSpringOrder2 

public class TestSpringOrder2 implements
         BeanPostProcessor, 
         BeanFactoryPostProcessor {
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      log.error("啟動順序:BeanPostProcessor postProcessBeforeInitialization beanName:{}", beanName);
      return bean;
   }

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      log.error("啟動順序:BeanPostProcessor postProcessAfterInitialization beanName:{}", beanName);
      return bean;
   }

   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      log.error("啟動順序:BeanFactoryPostProcessor postProcessBeanFactory ");
   }
}

執(zhí)行以上代碼后,可以在日志中看到啟動順序!

實際的執(zhí)行順序

2023-11-25 18:10:53,748 [main] ERROR (TestSpringOrder3:37) - 啟動順序:BeanFactoryPostProcessor postProcessBeanFactory 
2023-11-25 18:10:59,299 [main] ERROR (TestSpringOrder:53) - 啟動順序:構造函數(shù) TestSpringOrder
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:127) - 啟動順序: Autowired
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:129) - 啟動順序:setBeanName
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:111) - 啟動順序:setBeanFactory
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:121) - 啟動順序:setApplicationContext
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder3:25) - 啟動順序:BeanPostProcessor postProcessBeforeInitialization beanName:testSpringOrder
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:63) - 啟動順序:post-construct
2023-11-25 18:10:59,317 [main] ERROR (TestSpringOrder:116) - 啟動順序:afterPropertiesSet
2023-11-25 18:10:59,317 [main] ERROR (TestSpringOrder:46) - 啟動順序:init-method
2023-11-25 18:10:59,320 [main] ERROR (TestSpringOrder3:31) - 啟動順序:BeanPostProcessor postProcessAfterInitialization beanName:testSpringOrder
2023-11-25 18:17:21,563 [main] ERROR (SpringOrderConfiguartion:21) - 啟動順序: @Bean 注解方法執(zhí)行
2023-11-25 18:17:21,668 [main] ERROR (TestSpringOrder:58) - 啟動順序:SmartInitializingSingleton
2023-11-25 18:17:21,675 [main] ERROR (TestSpringOrder:74) - 啟動順序:start
2023-11-25 18:17:23,508 [main] ERROR (TestSpringOrder:68) - 啟動順序:ContextRefreshedEvent
2023-11-25 18:17:23,574 [main] ERROR (TestSpringOrder:79) - 啟動順序:CommandLineRunner

我通過在以上擴展點 添加 debug 斷點,調(diào)試代碼,整理出 Spring啟動原理的 長圖。過程省略…………

一張長圖透徹解釋 Spring啟動順序

圖片圖片

實例化和初始化的區(qū)別

new TestSpringOrder():new 創(chuàng)建對象實例,即為實例化一個對象;執(zhí)行該Bean的 init-method 等方法 為初始化一個Bean。注意初始化和實例化的區(qū)別。

Spring 重要擴展點的啟動順序

1.BeanFactoryPostProcessor

BeanFactory初始化之后,所有的Bean定義已經(jīng)被加載,但Bean實例還沒被創(chuàng)建(不包括BeanFactoryPostProcessor類型)。Spring IoC容器允許BeanFactoryPostProcessor讀取配置元數(shù)據(jù),修改bean的定義,Bean的屬性值等。

2.實例化Bean

Spring 調(diào)用java反射API 實例化 Bean。等同于 new TestSpringOrder();

3.Autowired 裝配依賴

Autowired是 借助于 AutowiredAnnotationBeanPostProcessor 解析 Bean 的依賴,裝配依賴。如果被依賴的Bean還未初始化,則先初始化 被依賴的Bean。在 Bean實例化完成后,Spring將首先裝配Bean依賴的屬性。

4.BeanNameAware

setBeanName。

5.BeanFactoryAware

setBeanFactory。

6.ApplicationContextAware setApplicationContext

在Bean實例化前,會率先設置Aware接口,例如 BeanNameAware BeanFactoryAware  ApplicationContextAware 等。

7.BeanPostProcessor postProcessBeforeInitialization

如果我想在 bean初始化方法前后要添加一些自己邏輯處理??梢蕴峁?nbsp;BeanPostProcessor接口實現(xiàn)類,然后注冊到Spring IoC容器中。在此接口中,可以創(chuàng)建Bean的代理,甚至替換這個Bean。

8.PostConstruct 執(zhí)行

接下來 Spring會依次調(diào)用 Bean實例初始化的 三大方法。

9.InitializingBean

afterPropertiesSet。

10.init-method

方法執(zhí)行。

11.BeanPostProcessor postProcessAfterInitialization

在 Spring 對Bean的初始化方法執(zhí)行完成后,執(zhí)行該方法。

12.其他Bean 實例化和初始化

Spring 會循環(huán)初始化Bean。直至所有的單例Bean都完成初始化。

13.所有單例Bean 初始化完成后

14.SmartInitializingSingleton Bean實例化后置處理

該接口的執(zhí)行時機在所有的單例Bean執(zhí)行完成后。例如Spring 事件訂閱機制的 EventListener注解,所有的訂閱者都是在這個位置被注冊進 Spring的。而在此之前,Spring Event訂閱機制還未初始化完成。所以如果有 MQ、Rpc 入口流量在此之前開啟,Spring Event就可能出問題!

所以Http、MQ、Rpc 入口流量必須在 SmartInitializingSingleton 之后開啟流量。

15.Spring 提供的擴展點,在所有單例Bean的 EventListener等組件全部啟動完成后,即Spring啟動完成,則執(zhí)行 start 方法。在這個位置適合開啟入口流量!

Http、MQ、Rpc 入口流量適合 在 SmartLifecyle 中開啟

16.發(fā)布 ContextRefreshedEvent 方法

該事件會執(zhí)行多次,在 Spring Refresh 執(zhí)行完成后,就會發(fā)布該事件!

17.注冊和初始化 Spring MVC

SpringBoot 應用,在父級 Spring啟動完成后,會嘗試啟動 內(nèi)嵌式 tomcat容器。在此之前,SpringBoot會初始化 SpringMVC 和注冊DispatcherServlet到Web容器。

18.Tomcat/Jetty 容器開啟端口

SpringBoot 調(diào)用內(nèi)嵌式容器,會開啟并監(jiān)聽端口,此時Http流量就開啟了。

19.應用啟動完成后,執(zhí)行 CommandLineRunner

SpringBoot 特有的機制,待所有的完全執(zhí)行完成后,會執(zhí)行該接口 run方法。值得一提的是,由于此時Http流量已經(jīng)開啟,如果此時進行本地緩存初始化、預熱緩存等,稍微有些晚了!在這個間隔期,可能緩存還未就緒!

所以預熱緩存的時機應該發(fā)生在 入口流量開啟之前,比較合適的機會是在 Bean初始化的階段。雖然 在Bean初始化時 Spring尚未完成啟動,但是調(diào)用 Bean預熱緩存也是可以的。但是注意:不要在 Bean初始化時 使用 Spring Event,因為它還未完成初始化 。

回答 關于 Spring 啟動原理的若干問題

1.init-method、PostConstruct、afterPropertiesSet 三個方法的執(zhí)行順序。

回答:PostConstruct,afterPropertiesSet,init-method

2.有兩個 Bean聲明了初始化方法。A使用 PostConstruct注解聲明,B使用 init-method 聲明。Spring一定先執(zhí)行 A 的PostConstruct 方法嗎?

回答:Spring 會循環(huán)初始化Bean實例,初始化完成1個Bean,再初始化下一個Bean。A、B兩個Bean的初始化順序不確定,誰先誰后不確定。無法保證 A 的PostConstruct 一定先執(zhí)行。除非使用 Order注解,聲明Bean的初始化順序!

3.Spring 何時裝配Autowire屬性,PostConstruct方法中引用 Autowired 字段是否會空指針?

Autowired裝配依賴發(fā)生在 PostConstruct之前,不會出現(xiàn)空指針!

4.PostConstruct 中方法依賴ApplicationContextAware拿到 ApplicationContext,兩者的順序誰先誰后?是否會出現(xiàn)空指針!

ApplicationContextAware 會先執(zhí)行,不會出現(xiàn)空指針!但是當Autowired沒有找到對應的依賴,并且聲明了非強制依賴時,該字段會為空,有潛在空指針風險。

5.項目應該如何監(jiān)聽 Spring 的啟動就緒事件。

通過SmartLifecyle start方法,監(jiān)聽Spring就緒 。適合在此開啟入口流量!

6.項目如何監(jiān)聽Spring 刷新事件。

監(jiān)聽 Spring Event ContextRefreshedEvent

7.Spring就緒事件和刷新事件的執(zhí)行順序和區(qū)別。

Spring就緒事件會先于 刷新事件。兩者都可能多次執(zhí)行,要確保方法的冪等處理,避免重復注冊問題

8.Http 流量入口何時啟動完成。

SpringBoot 最后階段,啟動完成Spring 上下文,才開啟Http入口流量,此時 SmartLifecycle#start 已執(zhí)行。所有單例Bean和SpringEvent等組件都已經(jīng)就緒!

9.項目中在 init-method 方法中注冊 Rpc是否合理?什么是合理的時機?

init 開啟Rpc流量非常不合理。因為Spring尚未啟動完成,包括 Spring Event尚未就緒!

10.項目中在 init-method 方法中注冊 MQ消費組是否合理?什么是合理的時機?

init 開啟 MQ 流量非常不合理。因為Spring尚未啟動完成,包括 Spring Event尚未就緒!

11.Spring還未完全啟動,在 PostConstruct 中調(diào)用 getBeanByAnnotation能否獲得準確的結果?

雖然未啟動完成,但是Spring執(zhí)行該getBeanByAnnotation方法時,會率先檢查 Bean定義,如果Bean定義對應的 Bean尚未初始化,則初始化這些Bean。所以即便是Spring初始化過程中調(diào)用,調(diào)用結果是準確的。

源碼級別介紹

SmartInitializingSingleton 接口的執(zhí)行位置

下圖代碼說明了,Spring在初始化全部 單例Bean以后,會執(zhí)行 SmartInitializingSingleton 接口。

圖片圖片

Autowired 何時裝配Bean的依賴

在Bean實例化之后,但初始化之前,AutowiredAnnotationBeanPostProcessor 會注入Autowired字段。

圖片圖片

SpringBoot 何時開啟Http端口

下圖代碼中可以看到,SpringBoot會首先啟動 Spring上下文,完成后才啟動 嵌入式Web容器,初始化SpringMVC,監(jiān)聽端口。

圖片圖片

Spring 初始化Bean的關鍵代碼

下圖我加了注釋,Spring初始化Bean的關鍵代碼,全在 這個方法里,感興趣的可以自行查閱代碼 。

AbstractAutowireCapableBeanFactory#initializeBean。

Spring CommandLineRunner 執(zhí)行位置

Spring Boot外部,當啟動完Spring上下文以后,最后才啟動 CommandLineRunner。

總結

SpringBoot 會在Spring完全啟動完成后,才開啟Http流量。這給了我們啟示:應該在Spring啟動完成后開啟入口流量。Rpc和 MQ流量 也應該如此,所以建議大家 在 SmartLifecype 或者 ContextRefreshedEvent 等位置 注冊服務,開啟流量。

例如 Spring Cloud  Eureka 服務發(fā)現(xiàn)組件,就是在 SmartLifecype中注冊服務的!

責任編輯:武曉燕 來源: 一安未來
相關推薦

2021-02-07 09:01:10

Java并發(fā)編程

2016-01-05 11:38:59

Linux內(nèi)核運行

2019-09-11 10:12:12

華為

2015-03-10 10:15:27

AppleWatch開發(fā)Swift

2020-11-27 06:28:55

Spring循環(huán)依賴

2015-09-14 09:07:15

Java多線程

2015-10-29 15:09:32

信息圖數(shù)據(jù)

2015-09-23 10:04:03

開放數(shù)據(jù)

2018-02-13 14:56:24

戴爾

2022-08-19 14:46:16

視覺框架

2015-06-24 10:51:10

iOS學習流程

2020-09-12 16:45:49

Git

2025-03-11 10:58:00

2021-09-29 11:30:01

大數(shù)據(jù)技術架構

2018-05-18 18:09:44

人工智能

2024-05-07 08:49:45

微服務架構模式

2013-12-16 10:59:52

WiFi上鎖WiFi被盜

2023-09-05 08:53:51

2019-03-18 15:00:48

SQLJoin用法數(shù)據(jù)庫
點贊
收藏

51CTO技術棧公眾號