不懂SpringApplication生命週期事件?那就等於不會Spring Boot嘛

學習方法之少廢話:吹牛、裝逼、叫大哥。
做者:A哥(YourBatman)
公衆號:BAT的烏托邦(ID:BAT-utopia)
文末是否有彩蛋:有java

前言

各位小夥伴你們好,我是A哥。本文屬總結性文章,對總覽Spring Boot生命週期非常重要,建議點在看、轉發「造福」更多小夥伴。react

我最近不是在寫Spring Cloud深度剖析的相關專欄麼,最近有收到小夥伴發過來一些問題,經過這段時間收集到的反饋,總結了一下有一個問題很是集中:那即是對Spring Boot應用SpringApplication的生命週期、事件的理解。有句話我不是常常掛嘴邊說的麼,你對Spring Framework有多瞭解決定了你對Spring Boot有多瞭解,你對Spring Boot的瞭解深度又會制約你去了解Spring Cloud,一環扣一環。所以此問題反饋比較集中是在清理之中的~web

爲什麼在Spring Boot中生命週期事件機制如此重要?原因很簡單:Spring Cloud父容器是由該生命週期事件機制來驅動的,而它僅僅是一個典型表明。Spring Cloud構建在Spring Boot之上,它在此基礎上構建並添加了一些「Cloud」功能。應用程序事件ApplicationEvent以及監聽ApplicationListener是Spring Framework提供的擴展點,Spring Boot對此擴展點利用得很是充分和深刻,而且還衍生出了很是多「子」事件類型,甚至自成體系。從ApplicationEvent衍生出來的子事件類型很是多,例如JobExecutionEvent、RSocketServerInitializedEvent、AuditApplicationEvent...spring

本文並不會對每一個子事件分別介紹(也並沒有必要),而是集中火力主攻Spring Boot最爲重要的一套事件機制:SpringApplication生命週期的事件體系
學習去...json


正文

本文將以SpringApplication的啓動流程/生命週期各時期發出的Event事件爲主線,結合每一個生命週期內完成的大事記介紹,真正實現一文讓你總覽Spring Boot的全貌,這對你深刻理解Spring Boot,以及整合進Spring Cloud都將很是重要bootstrap

爲表誠意,本文一開始便把SpringApplication生命週期事件流程圖附上,而後再精細化講解各個事件的詳情。緩存

話外音:趕時間的小夥伴能夠拿圖走人😁,但不建議白嫖喲tomcat


生命週期事件流程圖


版本說明:

因爲不一樣版本、類路徑下存在不一樣包時結果會存在差別,不指明版本的文章都是不夠負責任的。所以對導包/版本狀況做出以下說明:app

  • Spring Boot:2.2.2.RELEASE。有且僅導入spring-boot-starter-webspring-boot-starter-actuator
  • Spring Cloud:Hoxton.SR1。有且僅導入spring-cloud-context(注意:並不是spring-cloud-starter,並不含有spring-cloud-commons哦)

總的來講:本例導包是很是很是「乾淨」的,這樣在流程上才更有說服力嘛~spring-boot


SpringApplicationEvent

它是和SpringApplication生命週期有關的全部事件的父類,@since 1.0.0。

public abstract class SpringApplicationEvent extends ApplicationEvent {
	private final String[] args;
	
	public SpringApplicationEvent(SpringApplication application, String[] args) {
		super(application);
		this.args = args;
	}
	public SpringApplication getSpringApplication() {
		return (SpringApplication) getSource();
	}
	public final String[] getArgs() {
		return this.args;
	}
}

它是抽象類,擴展自Spring Framwork的ApplicationEvent,確保了事件和應用實體SpringApplication產生關聯(固然還有String[] args)。它有以下實現子類(7個):


每一個事件都表明着SpringApplication不一樣生命週期所處的位置,下面分別進行講解。




ApplicationStartingEvent:開始啓動中

@since 1.5.0,並不是1.0.0就有的哦。不過如今幾乎沒有人用1.5如下的版本了,因此可當它是標準事件。


完成的大事記

  • SpringApplication實例已實例化:new SpringApplication(primarySources)
    • 它在實例化階段完成了以下幾件「大」事:

      • 推斷出應用類型webApplicationType、main方法所在類
      • 給字段initializers賦值:拿到SPI方式配置的ApplicationContextInitializer上下文初始化器
      • 給字段listeners賦值:拿到SPI方式配置的ApplicationListener應用監聽器
    • 注意:在此階段(早期階段)不要過多地使用它的內部狀態,由於它可能在生命週期的後期被修改(話外音:使用時需謹慎)

  • 此時,SpringApplicationRunListener已實例化:它經過SPI方式指定org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
    • 若你有本身的運行時應用監聽器,使用相同方式配置上便可,均會生效
  • 因爲EventPublishingRunListener已經實例化了,所以在後續的事件發送中,均可以觸發對應的監聽器的執行
  • 發送ApplicationStartingEvent事件,觸發對應的監聽器的執行

監聽此事件的監聽器們

默認狀況下,有4個監聽器監聽ApplicationStartingEvent事件:

  1. LoggingApplicationListener:@since 2.0.0。對日誌系統抽象LoggingSystem執行實例化以及初始化以前的操做,默認使用的是基於Logback的LogbackLoggingSystem
  2. BackgroundPreinitializer:啓動一個後臺進行對一些類進行預熱。如ValidationInitializer、JacksonInitializer...,由於這些組件有第一次懲罰的特色(而且首次初始化均還比較耗時),因此使用後臺線程先預熱效果更佳
  3. DelegatingApplicationListener:它監聽的是ApplicationEvent,而實際上只會ApplicationEnvironmentPreparedEvent到達時生效,因此此處忽略
  4. LiquibaseServiceLocatorApplicationListener:略

總結:此事件節點結束時,SpringApplication完成了一些實例化相關的動做:本實例實例化、本實例屬性賦值、日誌系統實例化等。


ApplicationEnvironmentPreparedEvent:環境已準備好

@since 1.0.0。該事件節點是最爲重要的一個節點之一,由於對於Spring應用來講,環境抽象Enviroment簡直過重要了,它是最爲基礎的元數據,決定着程序的構建和走向,因此構建的時機是比較早的。


完成的大事記

  • 封裝命令行參數(main方法的args)到ApplicationArguments裏面
  • 建立出一個環境抽象實例ConfigurableEnvironment的實現類,而且填入值:Profiles配置和Properties屬性,默認內容以下(注意,這只是初始狀態,後面還會改變、添加屬性源,實際見最後的截圖):
  • 發送ApplicationEnvironmentPreparedEvent事件,觸發對應的監聽器的執行
    • 對環境抽象Enviroment的填值,均是由監聽此事件的監聽器去完成,見下面的監聽器詳解
  • bindToSpringApplication(environment):把環境屬性中spring.main.xxx = xxx綁定到當前的SpringApplication實例屬性上,如經常使用的spring.main.allow-bean-definition-overriding=true會被綁定到當前SpringApplication實例的對應屬性上

監聽此事件的監聽器們

默認狀況下,有9個監聽器監聽ApplicationEnvironmentPreparedEvent事件:

  1. BootstrapApplicationListener:來自SC。優先級最高,用於啓動/建立Spring Cloud的應用上下文。須要注意的是:到此時SB的上下文ApplicationContext還並無建立哦。這個流程「嵌套」特別像Bean初始化流程:初始化Bean A時,遇到了Bean B,就須要先去完成Bean B的初始化,再回頭來繼續完成Bean A的步驟。
    • 說明:在建立SC的應用的時候,使用的也是SpringApplication#run()完成的(非web),所以也會走下一整套SpringApplication的生命週期邏輯,因此請你務必區分。
      • 特別是這種case會讓「絕大多數」初始化器、監聽器等執行屢次,若你有那種只須要執行一次的需求(好比只想讓SB容器生命週期內執行,SC生命週期不執行),請務必自行處理,不然會被執行屢次而帶來不可預知的結果
    • SC應用上下文讀取的外部化配置文件名默認是:bootstrap,使用的也是ConfigFileApplicationListener完成的加載/解析
  2. LoggingSystemShutdownListener:來自SC。對LogbackLoggingSystem先清理,再從新初始化一次,效果同上個事件,至關於從新來了一次,畢竟如今有Enviroment環境裏嘛
  3. ConfigFileApplicationListener:@since 1.0.0。它也許是最重要的一個監聽器。作了以下事情:
    • 加載SPI配置的全部的EnvironmentPostProcessor實例,而且排好序。須要注意的是:ConfigFileApplicationListener也是個EnvironmentPostProcessor,會參與排序哦
    • 排好序後,分別一個個的執行EnvironmentPostProcessor(@since 1.3.0,並不是一開始就有),介紹以下:
      • SystemEnvironmentPropertySourceEnvironmentPostProcessor:@since 2.0.0。把SystemEnvironmentPropertySource替換爲其子類OriginAwareSystemEnvironmentPropertySource(屬性值帶有Origin來源),僅此而已
      • SpringApplicationJsonEnvironmentPostProcessor:@since 1.3.0。把環境中spring.application.json=xxx值解析成爲一個MapPropertySource屬性源,而後放進環境裏面去(屬性源的位置是作了處理的,通常不用太關心)
        • 能夠看到,SB是直接支持JSON串配置的哦。Json解析參見:JsonParser
      • CloudFoundryVcapEnvironmentPostProcessor:@since 1.3.0。略
      • ConfigFileApplicationListener:@since 1.0.0(它比EnvironmentPostProcessor先出現的哦)。加載application.properties/yaml等外部化配置,解析好後放進環境裏(這應該是最爲重要的)。
        • 外部化配置默認的優先級爲:"classpath:/,classpath:/config/,file:./,file:./config/"。當前工程下的config目錄裏的application.properties優先級最高,當前工程類路徑下的application.properties優先級最低
        • 值得強調的是:bootstrap.xxx也是由它負責加載的,處理規則同樣
      • DebugAgentEnvironmentPostProcessor:@since 2.2.0。處理和reactor測試相關,略
  4. AnsiOutputApplicationListener:@since 1.2.0。讓你的終端(能夠是控制檯、能夠是日誌文件)支持Ansi彩色輸出,使其更具可讀性。固然前提是你的終端支持ANSI顯示。參考類:AnsiOutput。你可經過spring.output.ansi.enabled = xxx配置,可選值是:DETECT/ALWAYS/NEVER,通常不動便可。另外,針對控制檯能夠單獨配置:spring.output.ansi.console-available = true/false
  5. LoggingApplicationListener:@since 2.0.0。根據Enviroment環境完成initialize()初始化動做:日誌等級、日誌格式模版等
    • 值得注意的是:它這步至關於在ApplicationStartingEvent事件基礎上進一步完成了初始化(上一步只是實例化)
  6. ClasspathLoggingApplicationListener:@since 2.0.0。用於把classpath路徑以log.debug()輸出,略
    1. 值得注意的是:classpath類路徑是有N多個的Arrays.toString(((URLClassLoader) classLoader).getURLs()),也就是說每一個.jar裏都屬於classpath的範疇
    2. 固然嘍,你須要注意Spring在處理類路徑時:classpath和classpath*的區別~,這屬於基礎知識
  7. BackgroundPreinitializer:本事件達到時無動做
  8. DelegatingApplicationListener:執行經過外部化配置context.listener.classes = xxx,xxx的監聽器們,而後把該事件廣播給他們,關心此事件的監聽器執行
    • 這麼作的好處:能夠經過屬性文件外部化配置監聽器,而不必定必須寫在spring.factories裏,更具彈性
    • 外部化配置的執行優先級,仍是相對較低的,到這裏纔給與執行嘛
  9. FileEncodingApplicationListener:檢測當前系統環境的file.encoding和spring.mandatory-file-encoding設置的值是否同樣,若是不同則拋出異常若是不配置spring.mandatory-file-encoding則不檢查

總結:此事件節點結束時,Spring Boot的環境抽象Enviroment已經準備完畢,但此時其上下文ApplicationContext沒有建立,可是Spring Cloud的應用上下文(引導上下文)已經所有初始化完畢哦,因此SC管理的外部化配置也應該都進入到了SB裏面。以下圖所示(這是基本上算是Enviroment的最終態了):


小提示:SC配置的優先級是高於SB管理的外部化配置的。例如針對spring.application.name這個屬性,若bootstrap裏已配置了值,再在application.yaml裏配置其實就無效了,所以生產上建議不要寫兩處。


ApplicationContextInitializedEvent:上下文已實例化

@since 2.1.0,很是新的一個事件。當SpringApplication的上下文ApplicationContext準備好,對單例Bean們實例化之,發送此事件。因此此事件又可稱爲:contextPrepared事件。


完成的大事記

  • printBanner(environment):打印Banner圖,默認打印的是Spring Boot字樣

    • spring.main.banner-mode = xxx來控制Banner的輸出,可選值爲CONSOLE/LOG/OFF,通常默認就好
    • 默認在類路徑下放置一個banner.txt文件,可實現自定義Banner。關於更多自定義方式,如使用圖片、gif等,本處不作過多介紹
      • 小建議:別花裏胡哨搞個佛祖在那。讓它能自動打印輸出當前應用名,這樣纔是最爲實用,最高級的(但須要你定製化開發,而且支持可配置,最好對使用者無感,屬於一個common組件)
  • 根據是不是web環境、是不是REACTIVE等,用空構造器建立出一個ConfigurableApplicationContext上下文實例(由於使用的是空構造器,因此不會立馬「啓動」上下文)

    • SERVLET -> AnnotationConfigServletWebServerApplicationContext
    • REACTIVE -> AnnotationConfigReactiveWebServerApplicationContext
    • 非web環境 -> AnnotationConfigApplicationContext(SC應用的容器就是使用的它)
  • 既然上下文實例已經有了,那麼就開始對它進行一些參數的設置:

    • 首先最重要的即是把已經準備好的環境Enviroment環境設置給它
    • 設置些beanNameGenerator、resourceLoader、ConversionService等組件
    • 實例化全部的ApplicationContextInitializer上下文初始化器,而且排序好後挨個執行它(這個很重要),默認有以下截圖這些初始化器此時要執行:

      下面對這些初始化器分別作出簡單介紹:
      1. BootstrapApplicationListener.AncestorInitializer:來自SC。用於把SC容器設置爲SB容器的父容器。固然實際操做委託給了此方法:new ParentContextApplicationContextInitializer(this.parent).initialize(context)去完成
      2. BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer:來自SC。代理了下面會提到的EnvironmentDecryptApplicationInitializer,也就是說在此處就會先執行,用於提早解密Enviroment環境裏面的屬性,如相關URL等
      3. PropertySourceBootstrapConfiguration:來自SC。重要,和配置中心相關,若想自定義配置中心必須瞭解它。主要做用是PropertySourceLocator屬性源定位器,我會把它放在配置中心章節詳解
      4. EnvironmentDecryptApplicationInitializer:來自SC。屬性源頭經過上面加載回來了,經過它來實現解密
        • 值得注意的是:它被執行了兩次哦~
      5. DelegatingApplicationContextInitializer:和上面的DelegatingApplicationListener功能相似,支持外部化配置context.initializer.classes = xxx,xxx
      6. SharedMetadataReaderFactoryContextInitializer:略
      7. ContextIdApplicationContextInitializer:@since 1.0.0。設置應用ID -> applicationContext.setId()。默認取值爲spring.application.name,再爲application,再爲自動生成
      8. ConfigurationWarningsApplicationContextInitializer:@since 1.2.0。對錯誤的配置進行警告(不會終止程序),以warn()日誌輸出在控制檯。默認內置的只有對包名的檢查:若你掃包含有"org.springframework"/"org"這種包名就警告
        • 若你想自定義檢查規則,請實現Check接口,而後...
      9. RSocketPortInfoApplicationContextInitializer:@since 2.2.0。暫略
      10. ServerPortInfoApplicationContextInitializer:@since 2.0.0。將本身做爲一個監聽器註冊到上下文ConfigurableApplicationContext裏,專門用於監聽WebServerInitializedEvent事件(非SpringApplication的生命週期事件)
        • 該事件有兩個實現類:ServletWebServerInitializedEventReactiveWebServerInitializedEvent。發送此事件的時機是WebServer已啓動完成,因此已經有了監聽的端口號
        • 該監聽器作的事有兩個:
          • "local." + getName(context.getServerNamespace()) + ".port"做爲key(默認值是local.server.port),value是端口值。這樣能夠經過@Value來獲取到本機端口了(但貌似端口寫0的時候,SB在顯示上有個小bug)
          • 做爲一個屬性源MapPropertySource放進環境裏,屬性源名稱爲:server.ports(由於一個server是能夠監聽多個端口的,因此這裏用複數)
      • ConditionEvaluationReportLoggingListener:將ConditionEvaluationReport報告(自動配置中哪些匹配了,哪些沒匹配上)寫入日誌,固然只有LogLevel#DEBUG時纔會輸出(注意:這不是日誌級別哦,應該叫報告級別)。如你配置debug=true就開啓了此自動配置類報告
        • 槽點:它明明是個初始化器,爲毛命名爲Listener?
  • 發送ApplicationContextInitializedEvent事件,觸發對應的監聽器的執行


監聽此事件的監聽器們

默認狀況下,有2個監聽器監聽ApplicationContextInitializedEvent事件:

  1. BackgroundPreinitializer:本事件達到時無動做
  2. DelegatingApplicationListener:本事件達到時無動做

總結:此事件節點結束時,完成了應用上下文ApplicationContext的準備工做,而且執行全部註冊的上下文初始化器ApplicationContextInitializer。可是此時,單例Bean是仍舊尚未初始化,而且WebServer也尚未啓動


ApplicationPreparedEvent:上下文已準備好

@since 1.0.0。截止到上個事件ApplicationContextInitializedEvent,應用上下文ApplicationContext充其量叫實例化好了,可是還剩下很重要的事沒作,這即是本週期的內容。


完成的大事記

  • 把applicationArguments、printedBanner等都做爲一個Bean放進Bean工廠裏(所以你就能夠@Autowired注入的哦)
    • 好比:有了Banner這個Bean,你能夠在你任何想要輸出的地方輸出一個Banner,而不只僅是啓動時只會輸出一次了
  • lazyInitialization = true延遲初始化,那就向Bean工廠放一個:new LazyInitializationBeanFactoryPostProcessor()
    • 該處理器@since 2.2.0。該處理器的做用是:對全部的Bean(經過LazyInitializationExcludeFilter接口指定的排除在外)所有.setLazyInit(true);延遲初始化
  • 根據primarySources和allSources,交給BeanDefinitionLoader(SB提供的實現)實現加載Bean的定義信息,它支持4種加載方式(4種源):
    • AnnotatedBeanDefinitionReader -> 基於註解
    • XmlBeanDefinitionReader -> 基於xml配置
    • GroovyBeanDefinitionReader -> Groovy文件
    • ClassPathBeanDefinitionScanner -> classpath中加載
    • (不一樣的源使用了不一樣的load加載方式)
  • 發送ApplicationPreparedEvent事件,觸發對應的監聽器的執行

監聽此事件的監聽器們

默認狀況下,有6個監聽器監聽ApplicationContextInitializedEvent事件:

  1. CloudFoundryVcapEnvironmentPostProcessor:略
  2. ConfigFileApplicationListener:向上下文註冊一個new PropertySourceOrderingPostProcessor(context)。它的做用是:Bean工廠結束後對環境裏的屬性源進行重排序 -> 把名字叫defaultProperties的屬性源放在最末位
    • 該屬性源是經過SpringApplication#setDefaultProperties API方式放進來的,通常不會使用到,留個印象便可
  3. LoggingApplicationListener:由於這時已經有Bean工廠了嘛,因此它作的事是:向工廠內放入Bean
    • "springBootLoggingSystem" -> loggingSystem
    • "springBootLogFile" -> logFile
    • "springBootLoggerGroups" -> loggerGroups
  4. BackgroundPreinitializer:本事件達到時無動做
  5. RestartListener:SC提供。把當前最新的上下文緩存起來而已,目前並未發現有實質性做用,可忽略
  6. DelegatingApplicationListener:本事件達到時無動做

總結:此事件節點結束時,應用上下文ApplicationContext初始化完成,該賦值的賦值了,Bean定義信息也已所有加載完成。可是,單例Bean尚未被實例化,web容器依舊還沒啓動。


ApplicationStartedEvent:應用成功啓動

@since 2.0.0。截止到此,應用已經準備就緒,而且經過監聽器、初始化器等完成了很是多的工做了,但仍舊剩下被認爲最爲重要的初始化單例Bean動做還沒作、web容器(如Tomcat)還沒啓動,這即是這個週期所要作的事。


完成的大事記

  • 啓動Spring容器:AbstractApplicationContext#refresh(),這個步驟會作不少事,好比會實例化單例Bean
    • 該步驟屬於Spring Framework的核心內容範疇,作了不少事,請參考Spring核心技術內容章節
    • 在Spring容器refresh()啓動完成後,WebServer也隨之完成啓動,成功監聽到對應端口(們)
  • 輸出啓動成功的日誌:Started Application in xxx seconds (JVM running for xxx)
  • 發送ApplicationStartedEvent事件,觸發對應的監聽器的執行
  • callRunners():依次執行容器內配置的ApplicationRunner/CommandLineRunner的Bean實現類,支持sort排序
    • ApplicationRunner:@since 1.3.0,入參是ApplicationArguments,先執行(推薦使用)
    • CommandLineRunner:@since 1.0.0,入參是String... args,後執行(不推薦使用)

監聽此事件的監聽器們

默認狀況下,有3個監聽器監聽ApplicationStartedEvent事件:

  1. 前兩個不用再解釋了吧:本事件達到時無動做
  2. TomcatMetricsBinder:@since 2.1.0。和監控相關:將你的tomcat指標信息TomcatMetrics綁定到MeterRegistry,從而就能收集到相關指標了

總結:此事件節點結束時,SpringApplication的生命週期到這一步,正常的啓動流程就所有完成了。也就說Spring Boot應用能夠正常對對外提供服務了。


ApplicationReadyEvent:應用已準備好

@since 1.3.0。該事件所處的生命週期可認爲基本同ApplicationStartedEvent,僅是在其後執行而已,二者中間並沒有其它特別的動做,可是監聽此事件的監聽器們仍是蠻重要的


完成的大事記

同上。


監聽此事件的監聽器們

默認狀況下,有4個監聽器監聽ApplicationStartedEvent事件:

  1. SpringApplicationAdminMXBeanRegistrar:當此事件到達時,告訴Admin Spring應用已經ready,可使用啦。
  2. 中間這兩個不用再解釋了吧:本事件達到時無動做
  3. RefreshEventListener:當此事件到達時,告訴Spring應用已經ready了,接下來即可以執行ContextRefresher.refresh()

總結:此事件節點結束時,應用已經完徹底全的準備好了,而且也已經完成了相關組件的周知工做。



異常狀況

SpringApplication是有可能在啓動的時候失敗(如端口號已被佔用),固然任何一步驟遇到異常時交給SpringApplication#handleRunFailure()方法來處理,這時候也會有對應的事件發出。


ApplicationFailedEvent:應用啓動失敗

SpringApplication在啓動時拋出異常:多是端口綁定、也多是你自定義的監聽器你寫了個bug等,就會「可能」發送此事件。


完成的大事記
  • 獲得異常的退出碼ExitCode,而後發送ExitCodeEvent事件(非生命週期事件)
  • 發送ApplicationFailedEvent事件,觸發對應的監聽器的執行

監聽此事件的監聽器們

默認狀況下,有6個監聽器監聽ApplicationStartedEvent事件:

  1. LoggingApplicationListener:執行loggingSystem.cleanUp()清理資源
  2. ClasspathLoggingApplicationListener:輸出一句debug日誌:Application failed to start with classpath: ...
  3. 中間這兩個不用再解釋了吧:本事件達到時無動做
  4. ConditionEvaluationReportLoggingListener:自動配置輸出報告,輸出錯誤日誌唄:特別方便你查看和錯誤定位
    • 不得不誇:SB對錯誤定位這塊才真叫智能,比Spring Framework好用太多了
  5. BootstrapApplicationListener.CloseContextOnFailureApplicationListener:執行context.close()

總結:此事件節點結束時,會作一些釋放資源的操做。通常狀況下:咱們並不須要監聽到此事件


總結

關於SpringApplication的生命週期體系的介紹就到這了,相信經過此「萬字長文」你能體會到A哥的用心。翻了翻市面上的相關文章,本文Almost能夠保證是總結得最到位的,讓你經過一文即可從大的方面基本掌握Spring Boot,這無論是你使用SB,仍是後續自行擴展、精雕細琢SB,以及去深刻了解Spring Cloud均由很是重要的意義,但願對你有幫助,謝謝你的三連。

相關文章
相關標籤/搜索