SpringBoot 啟動流程分析
用了Spring Boot有一段時間了,相比於Spring要理解它更難一些,在Spring boot中提出以下幾個疑問
- Spring Boot的架構是如何設計的,能很輕鬆的整合很多開源元件。
- Spring Boot中資源如何載入,事件如何管理,什麼時候可以對Spring Boot進行擴充套件[什麼時候可以訪問資源,哪個階段可以干預BeanFactory的行為,哪個階段可以干預Bean的行為]等等
- 其實問題還有很多
就從下面這張圖說起,由於整個Spring Boot的繼承結構比較多,這裡以一個Spring Boot的標準Web專案中涉及的核心類的關係展開說明:
1:BeanFactory和ApplicationContext是什麼關係?
從上述整理出的類圖大概能看出來,BeanFactory和ApplicationContext都有自己的繼承結構,兩者本身都是介面,且都有自己的實現.兩者的關係分別拿兩個的實現GenericApplicationContext(ApplicationContext的實現) DefaultListableBeanFactory(BeanFactory的實現),GenericApplicationContext的構造器使用了以下程式碼
public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); }
可見兩者是組合關係而非繼承關係,進一步說,在整個Spring的架構中BeanFactory作為Bean的工廠和容器負責Bean定義的載入、Bean的建立、以及提供核心的訪問Bean的方法。ApplicationContext是BeanFactory的擴充套件,基於BeanFactory進行了很大程度的擴充套件和資源整合,BeanFactory是IOC容器的核心.ApplicationContext 是Spring應用程式的核心.
2:配置檔案的載入,資源的國際化,事件等是如何被設計的?
Spring Boot中層次明確,提供了頂級介面MessageSource載入國際化資源的標註,ApplicationEventPublisher介面定義事件多播的標準(一個監聽模式),ResourcePatternResolver介面定義載入各種資源的標準,ResourceLoader定義資源載入器的標註(不不同來源,不同協議等),WebServer介面定義在Web環境中獲取Web伺服器的標準等等,各種ApplicationContext的實現需要按需來實現這些頂級介面並實現對應資源的裝載工作.看起來這是一個複雜的繼承結構,有幾個比較重要的組合點
- ApplicationContext介面繼承了MessageSource,ApplicationEventPublisher,esourcePatternResolver,EnvironmentCapabl介面,意味著需要有一個子類在恰當的時候來實現這些工作。
- AbstractApplicationContext是ApplicationContext的第一個抽象的實現,實現了事件多播、國際化、資原始檔載入等等核心功能,使用模版方法定義了子類中整個ApplicationContext的初始化流程,設計時為子類提供了不少的可擴充套件的方法.AbstractApplicationContext可以說是整個ApplicationContext的大腦(汽車的發動機).
- 其它繼承AbstractApplicationContext的子類分別提供了不同的實現,比如GenericWebApplicationContext,提供了對ServletContext的實現以及主題資源的實現,AnnotationConfigServletWebServerApplicationContext提供了基於類路徑掃描Bean和基於註解的Bean定義。
3:如此複雜的繼承關係,在時間上時以什麼順序發生的,各個模組是如何來互動保證Spring Boot程式良好執行?
畫一張圖,這裡臨時整理了一份可以參考,因理解有限難免有不當之處.下圖中以入口程式SpringApplication.run為起點,上下文型別為AnnotationConfigServletWebServerApplicationContext實現,以及其它比較重要操作按時間順序進行組織.[這裡把所有ApplicationContext本身及子類的所有擴充套件和實現都集中在AnnotationConfigServletWebServerApplicationContext中不在單獨註明]。
- 入口程式, SpringApplication.run接收一個註解的配置類,最終例項化一個SpringApplication的例項來呼叫例項方法 new SpringApplication(primarySources).run(args)作為整個應用程式的起點.在其構造器內,計算了當前的上下文的型別(判斷的依據比較簡單,特定的類是否存在),載入一批初始化器,初始化器是ApplicationContextInitializer的實現,用來對整個核心容器執行初始化操作,同時載入一批監聽器,監聽器是ApplicationListener的實現,用於在初始階段響應特定的容器事件以進行程式設計擴充套件.接下來執行run方法開始啟動SpringBoot應用程式.
- run方法首先執行相關的準備工作,啟動監聽程式,封裝引數資訊,封裝環境變數資訊,列印Banner,建立合適的ApplicationContext例項,在建立AnnotationConfigServletWebServerApplicationContext期間,需要定義從類路徑中掃描Bean定義的能力,以及從註解的配置檔案中解析Bean定義的能力,該過程通過協調AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner來完成.
- 上下文建立成功後,執行上下文的準備工作,完成環境變數設定,設定resourceLoader,設定resourceLoader 載入器,應用初始化器,通知容器就緒事件,載入配置資源,執行load,將所有通過註解,掃描,以及配置檔案方式定義的Bean都註冊到上下文中去,各種型別的Bean的解析載入過程使用BeanDefinitionLoader來完成,依賴AnnotatedBeanDefinitionReader,ClassPathBeanDefinitionScanner完成Bean掃描和註解的Bean讀取.load完成後所有配置的Bean被註冊到上下文中.接下來通知load完成事件.
- 執行重新整理操作,這個階段呼叫了父類的refresh[AbstractApplicationContext定義,工廠方法]執行容器的重新整理操作,是整個上下文啟動的核心階段,執行prepareRefresh執行重新整理前的準備工作,更新上下文狀態,初始化所有屬性資源,驗證必須的配置檔案,執行重新整理父容器操作[如果有],準備BeanFactory對BeanFactory進行各種配置(類載入器,表示式解析器,註冊依賴,新增BeanFactory後處理器等),呼叫所有已新增的BeanFactoryPostProcessors[一個容器擴充套件點],註冊BeanPostProcessors以攔截Bean的建立,初始化訊息資源,初始化上下文事件多播機制,呼叫onRefresh給子類擴充套件,檢查Bean中的監聽器並註冊到多播器中,例項化所有非懶載入的Bean到BeanFactory中.
- 執行重新整理後的操作,清除快取的資源,初始化生命週期處理器並觸發容器重新整理操作,多播容器重新整理事件,至此容器重新整理完成.
- 呼叫afterRefresh執行重新整理後的操作,多播容器啟動事件
- 回撥CommandLineRunner,ApplicationRunner的Bean實現
- 多播上下文執行事件
4:Spring Boot程式啟動過車中有哪些中間狀態?
Spring Boot程式從一開始啟動就被一個“SpringApplicationRunListeners”的監聽器監聽著,按照定義的事件動作Sring Boot程式在啟動過車中有下面的幾種狀態,環境變數就緒後傳送一個environmentPrepared事件,此時可以訪問環境變數資訊,容器重新整理前傳送一個contextPrepared事件,允許對BeanFactory提前做一些操作,Bean都註冊到上下文後傳送一個contextLoaded事件,還可對BeanFactory做一些操作,重新整理操作完成後傳送一個started事件這時容器已經啟動完畢,ApplicationRunner,CommandLineRunner實現沒有被回撥,ApplicationRunner,CommandLineRunner回撥完成後傳送一個running事件,啟動過車正常結束,返回上下文物件.期間所有未被處理的異常都將導致Spring Boot程式啟動失敗.