1. 程式人生 > >頭禿了,二十三張圖帶你從原始碼瞭解Spring Boot 的啟動流程~

頭禿了,二十三張圖帶你從原始碼瞭解Spring Boot 的啟動流程~

持續原創輸出,點選上方藍字關注我

目錄

  • 前言
  • 原始碼版本
  • 從哪入手?
  • 原始碼如何切分?
  • 如何建立SpringApplication?
    • 設定應用型別
    • 設定初始化器(Initializer)
    • 設定監聽器(Listener)
    • 設定監聽器(Listener)
  • 執行run()方法
    • 獲取、啟動執行過程監聽器
    • 環境構建
    • 建立IOC容器
    • IOC容器的前置處理
    • 重新整理容器
    • IOC容器的後置處理
    • 發出結束執行的事件
    • 執行Runners
    • 總結
  • 總結

前言

Spring Boot 專欄已經寫了五十多天了,前面二十章從基礎應用到高階整合避重就輕介紹的都是工作、面試中常見的知識點。

今天開始底層原始碼介紹的階段,相對內容比較深一點,作者也儘可能介紹的通俗易懂,層次分明一點。相信讀過我寫的Mybatis

專欄的文章都知道,只要跟著作者的步驟,方法一步步研究,其實原始碼並不難。

這篇文章花了四天時間精雕細琢,力求介紹的通俗易懂,畢竟原始碼相對難度更高些,希望通過作者拆分講解能夠幫助到讀者。

如果沒讀過作者的前二十篇文章,點選前往

原始碼版本

作者Spring Boot是基於2.4.0。每個版本有些變化,讀者儘量和我保持一致,以防原始碼有些出入。

從哪入手?

相信很多人嘗試讀過Spring Boot的原始碼,但是始終沒有找到合適的方法。那是因為你對Spring Boot的各個元件、機制不是很瞭解,研究起來就像大海撈針。

至於從哪入手不是很簡單的問題嗎,當然主啟動類了,即是標註著@SpringBootApplication

註解並且有著main()方法的類,如下一段程式碼:

@SpringBootApplication
public class AnnotationDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AnnotationDemoApplication.class, args);
    }
}

話不多說,DEBUG伺候,別怕,搞它........

原始碼如何切分?

SpringApplication中的靜態run()方法並不是一步完成的,最終執行的原始碼如下:

//org.springframework.context.ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  return new SpringApplication(primarySources).run(args);
 }

很顯然分為兩個步驟,分別是建立SpringApplication和執行run()方法,下面將分為這兩個部分介紹。

如何建立SpringApplication?

建立即是new物件了,DEBUG跟進程式碼,最終執行的SpringApplication構造方法如下圖:

如上圖中標註的註釋,建立過程重用的其實分為這三個階段,下面將會一一介紹每個階段做了什麼事。

設定應用型別

這個過程非常重要,直接決定了專案的型別,應用型別分為三種,都在WebApplicationType這個列舉類中,如下:

  1. NONE:顧名思義,什麼都沒有,正常流程走,不額外的啟動web容器,比如Tomcat
  2. SERVLET:基於servlet的web程式,需要啟動內嵌的servletweb容器,比如Tomcat
  3. REACTIVE:基於reactive的web程式,需要啟動內嵌reactiveweb容器,作者不是很瞭解,不便多說。

判斷的依據很簡單,就是載入對應的類,比如載入了DispatcherServlet等則會判斷是Servlet的web程式。原始碼如下:

static WebApplicationType deduceFromClasspath() {
  if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
   return WebApplicationType.REACTIVE;
  }
  for (String className : SERVLET_INDICATOR_CLASSES) {
   if (!ClassUtils.isPresent(className, null)) {
    return WebApplicationType.NONE;
   }
  }
  return WebApplicationType.SERVLET;
 }

這裡我引入了spring-boot-starter-web,肯定是Servlet的web程式。

設定初始化器(Initializer)

初始化器ApplicationContextInitializer是個好東西,用於IOC容器重新整理之前初始化一些元件,比如ServletContextApplicationContextInitializer

那麼如何獲取初始化器呢?跟著上圖中的程式碼進入,在SpringApplication中的如下圖中的方法:

相對重要的就是第一步獲取初始化器的名稱了,這個肯定是全類名了,詳細原始碼肯定在loadFactoryNames()方法中了,跟著原始碼進入,最終呼叫的是#SpringFactoriesLoader.loadSpringFactories()方法。

loadSpringFactories()方法就不再詳細解釋了,其實就是從類路徑META-INF/spring.factories中載入ApplicationContextInitializer的值。

spring-boot-autoconfigurespring.factories檔案中的值如下圖:

上圖中的只是一部分初始化器,因為spring.factories檔案不止一個。

下圖中是我的demo中注入的初始化器,現實專案中並不止這些。

這也告訴我們自定義一個ApplicationContextInitializer只需要實現介面,在spring.factories檔案中設定即可。

設定監聽器(Listener)

監聽器(ApplicationListener)這個概念在Spring中就已經存在,主要用於監聽特定的事件(ApplicationEvent),比如IOC容器重新整理、容器關閉等。

Spring Boot擴充套件了ApplicationEvent構建了SpringApplicationEvent這個抽象類,主要用於Spring Boot啟動過程中觸發的事件,比如程式啟動中、程式啟動完成等。如下圖:

監聽器如何獲取?從原始碼中知道其實和初始化器(ApplicationContextInitializer)執行的是同一個方法,同樣是從META-INF/spring.factories檔案中獲取。

spring-boot-autoconfigurespring.factories檔案中的值如下圖:

spring.factories檔案不止一個,同樣監聽器也不止以上這些。

作者demo中注入的一些監聽器如下圖:

總結

SpringApplication的構建都是為了run()方法啟動做鋪墊,構造方法中總共就有幾行程式碼,最重要的部分就是設定應用型別、設定初始化器、設定監聽器。

「注意」:初始化器和這裡的監聽器都要放置在spring.factories檔案中才能在這一步驟載入,否則不會生效,因此此時IOC容器還未建立,即使將其注入到IOC容器中也是不會生效的。

作者簡單的畫了張執行流程圖,僅供參考,如下:

執行run()方法

上面分析了SpringApplication的構建過程,一切都做好了鋪墊,現在到了啟動的過程了。

作者根據原始碼將啟動過程分為了「8步」,下面將會一一介紹。

1. 獲取、啟動執行過程監聽器

SpringApplicationRunListener這個監聽器和ApplicationListener不同,它是用來監聽應用程式啟動過程的,介面的各個方法含義如下:

public interface SpringApplicationRunListener {

    // 在run()方法開始執行時,該方法就立即被呼叫,可用於在初始化最早期時做一些工作
    void starting();
    // 當environment構建完成,ApplicationContext建立之前,該方法被呼叫
    void environmentPrepared(ConfigurableEnvironment environment);
    // 當ApplicationContext構建完成時,該方法被呼叫
    void contextPrepared(ConfigurableApplicationContext context);
    // 在ApplicationContext完成載入,但沒有被重新整理前,該方法被呼叫
    void contextLoaded(ConfigurableApplicationContext context);
    // 在ApplicationContext重新整理並啟動後,CommandLineRunners和ApplicationRunner未被呼叫前,該方法被呼叫
    void started(ConfigurableApplicationContext context);
    // 在run()方法執行完成前該方法被呼叫
    void running(ConfigurableApplicationContext context);
    // 當應用執行出錯時該方法被呼叫
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

如何獲取執行監聽器?

SpringApplication#run()方法中,原始碼如下:

//從spring.factories中獲取監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);

跟進getRunListeners()方法,其實還是呼叫了loadFactoryNames()方法從spring.factories檔案中獲取值,如下:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

最終注入的是EventPublishingRunListener這個實現類,建立例項過程肯定是通過反射了,因此我們看看它的構造方法,如下圖:

這個執行監聽器內部有一個事件廣播器(SimpleApplicationEventMulticaster),主要用來廣播特定的事件(SpringApplicationEvent)來觸發特定的監聽器ApplicationListener

EventPublishingRunListener中的每個方法用來觸發SpringApplicationEvent中的不同子類。

如何啟動執行監聽器?

SpringApplication#run()方法中,原始碼如下:

//執行starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);

執行SpringApplicationRunListenersstarting()方法,跟進去其實很簡單,遍歷執行上面獲取的執行監聽器,這裡只有一個EventPublishingRunListener。因此執行的是它的starting()方法,原始碼如下圖:

上述原始碼中邏輯很簡單,其實只是執行了multicastEvent()方法,廣播了ApplicationStartingEvent事件。至於multicastEvent()內部方法感興趣的可以看看,其實就是遍歷ApplicationListener的實現類,找到監聽ApplicationStartingEvent這個事件的監聽器,執行onApplicationEvent()方法。

總結

這一步其實就是廣播了ApplicationStartingEvent事件來觸發監聽這個事件的ApplicationListener

因此如果自定義了ApplicationListener並且監聽了ApplicationStartingEvent(應用程式開始啟動)事件,則這個監聽器將會被觸發。

2. 環境構建

這一步主要用於載入系統配置以及使用者的自定義配置(application.properties),原始碼如下,在run()方法中:

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

prepareEnvironment方法內部廣播了ApplicationEnvironmentPreparedEvent事件,原始碼如下圖:

環境構建這一步載入了系統環境配置、使用者自定義配置並且廣播了ApplicationEnvironmentPreparedEvent事件,觸發監聽器。

3. 建立IOC容器

原始碼在run()方法中,如下:

context = createApplicationContext();

跟進程式碼,真正執行的是ApplicationContextFactory方法,如下圖:

根據webApplicationType決定建立的型別,很顯然,我這裡的是servlet,因此建立的是AnnotationConfigServletWebServerApplicationContext

這一步僅僅是建立了IOC容器,未有其他操作。

4. IOC容器的前置處理

這一步真是精華了,在重新整理容器之前做準備,其中有一個非常關鍵的操作:將啟動類注入容器,為後續的自動化配置奠定基礎。原始碼如下:

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

prepareContext()原始碼解析如下圖,內容還是挺多的:

從上圖可以看出步驟很多,下面將會詳細介紹幾個重點的內容。

呼叫初始化器

相關推薦

禿十三圖帶原始碼瞭解Spring Boot啟動流程~

持續原創輸出,點選上方藍字關注我 目錄 前言原始碼版本從哪入手?原始碼如何切分?如何建立SpringApplication? 設定應用型別設定初始化器(Initializer)設定監聽器(Listener)設定監聽器(Listener) 執行run()方法 獲取、啟動執行過程監聽器環境構建建立IOC

禿Spring Boot 自動配置原始碼解析瞭解一波~

持續原創輸出,點選上方藍字關注我 目錄 前言原始碼版本@SpringBootApplication幹了什麼?@EnableAutoConfiguration幹了什麼?總結 前言 為什麼Spring Boot這麼火?因為便捷,開箱即用,但是你思考過為什麼會這麼便捷嗎?傳統的SSM架構配置檔案至少要寫半天,

禿使用@AutoConfigureBefore指定配置類順序竟沒生效?

持續原創輸出,點選上方藍字關注我 前言 日常工作中對於Spring Boot 提供的一些啟動器可能已經足夠使用了,但是不可避免的需要自定義啟動器,比如整合一個陌生的元件,也想要達到開箱即用的效果。 在上一章節從底層原始碼介紹了Spring Boot 的自動配置的原理,未讀過的朋友建議看一下:Spring

Android應用程式啟動詳解(原始碼瞭解App的啟動過程

本文承接《Android應用程式啟動詳解(一)》繼續來學習應用程式的啟動的那些事。上文提到startActivity()方法啟動一個app後經過一翻過程就到了app的入口方法ActivityThread.main()。其實我們在之前的文章中《Android的訊息機制(二)之L

麝香鼻炎靈再也不怕得鼻炎十年老鼻炎都能治好!

detail png 特點 通過 健康 進行 左右 就是 分享圖片 ??如果你有十年,20年的各類老鼻炎,鼻竇炎,中耳炎,就用麝香鼻炎靈滴鼻液。淘寶購買地址:點擊購買麝香鼻炎靈打開淘寶搜索:宜爽中醫鼻炎選用了三十多味專門治療鼻炎方面的一些中藥材,選用了珍稀名貴的野生中藥材麝

7圖帶解阿裏雲服務器ECS

motion blog 是不是 ofo ref tms 鏈接 https 世界 拿起手機,打開電腦,指尖輕輕點那麽幾下,大千世界盡收眼前,看到這裏,各位看官大人是不是也想有一個自己的網站呢?那麽當然了,想要有一個屬於自己的網站肯定少不了存放網站文件的服務器了,接下來小編就用

10圖帶深入理解Docker容器和鏡像-轉

轉換 AS pos run IT tree 很難 的區別 write 轉載:http://dockone.io/article/783 這篇文章希望能夠幫助讀者深入理解Docker的命令,還有容器(container)和鏡像(image)之間的區別,並深入探討容器和運行

圖帶看完 Apple 2018 秋季釋出會

2018年的最後一場蘋果釋出會,已經結束了。不知道看完了釋出會的小夥伴們有何感想?手都洗乾淨準備開剁了嗎? 在本次的秋季新品釋出會上,蘋果釋出了新款的 MacBook Air、iPad Pro、Apple Pencil、Mac mini。每款新品都做了較大的改變,Macbook Air 用上了

再也不用貼上複製Ctrl+E輕鬆幫把文字拆分到多個單元格!

表格是由一個個單元格組成的,每一個單元格我們都可以設定,姓名和手機號碼目前是最常見的一種表格形式,有的時候我們一個個貼上複製確實非常麻煩。費時又費力。今天小編教給大家一個簡單的方法,讓大家少費功夫。 1. 開啟需要拆分的表格 2. 在B1中輸入"葛梅",選取它和下面的空行,選中空

國慶快到還在創業的確定不點進來解下?

在移動網際網路流量紅利逐漸消的今天,移動端使用者的爭奪,已從增量市場轉向存量市場,從搶佔使用者轉向搶佔使用者時間。在眾多平臺的鬥爭都進入紅海期的時候,目前尚有未完全開發的巨大流量藍海—微信小程式,隨勢而動,一發將不可收拾。 與移動網際網路市場上的其他產品進行對比,微信的存在無疑滿足了人們一項

【記坑】Iterator遍歷時多次呼叫next()次遍歷需要Collection重新獲取迭代器

【記坑】Iterator遍歷時,多次呼叫next(),二次遍歷需要從Collection重新獲取迭代器 2018年02月10日 11:02:46 閱讀數:681 業務需求,從一份excel表中取到X軸(專案)和Y軸(平臺)的資料,和資料庫中的資料進行比較,如果匹配不上,則把所有匹配不上的

雙十一要來我去廟裡給一道護身符

  雙十一要來了,   小哥沒來,卻來了一個陌生電話: “王蘑菇先生,請問你是不是最近在X家網購了一套性感睡衣?我是這家網店的客服人員。您買的這套睡衣質量有問題,公司現在需要召回並退款給您。麻煩您提供一下退款賬戶……” 應該是騙子

Spring Cloud系列(十三) API閘道器服務Spring Cloud Zuul(Finchley.RC2版本)

為什麼使用Spring Cloud Zuul? 通過前幾章的介紹,我們對於Spring Cloud Netflix 下的核心元件已經瞭解了大半,利用這些元件我們已經可以構建一個簡單的微服務架構系統,比如通過使用Spring Cloud Eureka實現高可用的服務註冊中

圖帶瞭解Tomcat系統架構--讓面試官顫抖的Tomcat回答系列!

<div class="markdown_views prism-atom-one-dark">                             <!-- flowchart 箭頭圖示 勿刪 -->                       

Tomcat日記——四圖帶瞭解Tomcat系統架構

本文轉自https://blog.csdn.net/xlgen157387/article/details/79006434 俗話說,站在巨人的肩膀上看世界,一般學習的時候也是先總覽一下整體,然後逐個部分個個擊破,最後形成思路,瞭解具體細節,Tomcat的結構很複雜,

圖帶瞭解Tomcat系統架構

一、Tomcat頂層架構 先上一張Tomcat的頂層結構圖(圖A),如下: Tomcat中最頂層的容器是Server,代表著整個伺服器,從上圖中可以看出,一個Server可以包含至少一個Service,用於具體提供服務。 Service主要包含兩個部分:Connec

學習Docker之10圖帶深入理解Docker容器和映象

剛開始接觸Docker之後,就對容器和映象的概念有所迷惑,上一篇也簡單的說了一下之前的見解,其實並不準確,在之後的學習中再加上網上找的資料,發現了下面的這一片博文,一定要多看幾遍,看完思考再看,就會對容器和映象有更深刻的認識。 【編者的話】本文用圖文

圖帶瞭解“持續交付”和“DevOps”的前世今生

這是一個新概念風起雲勇的時代。 就讓我們從雲端抓它幾個名詞下來,一起玩耍吧!!! “敏捷軟體開發”,“增長黑客”,“持續整合”,“DevOps”,“精益創業”,“持續交付”,“大資料”… … OK,就這四個啦: “敏捷軟體開發”,“持續整合”,“DevOps”,“持續交付”, 先讓我們在Wikipe

10圖帶看懂大型網站架構演變

一個普通的網站發展成大型網站過程中的一種較為典型的架構演變歷程。這一步涉及的知識體系非常的多,要求對通訊、遠端呼叫、訊息機制等有深入的理解和掌握,要求的都是從理論、硬體級、作業系統級以及所採用的語言的實現都有清楚的理解。   架構演變第一步:物理分離webserver和資料庫

年底程式設計師來說說今年寫過的最牛逼的bug是什麼?

很久之前就有一個關於程式設計師的梗:“喲!又在寫 Bug 呢?” 據說這是對程式設計師殺傷力最大的一句話,沒有之一!之所以如此,那是因為這是句大實話啊!是的,程式設計師的人生就是Bug和Debug交織在一起的悲歌。 那!年底了,程式設計師們,來說說你今年寫過的最牛逼的bug是什麼?