<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          二十三張圖帶你從源碼分析Spring Boot 啟動流程

          共 17839字,需瀏覽 36分鐘

           ·

          2022-03-02 12:50

          持續(xù)原創(chuàng)輸出,點擊上方藍(lán)字關(guān)注我

          目錄

          • 前言
          • 源碼版本
          • 從哪入手?
          • 源碼如何切分?
          • 如何創(chuàng)建SpringApplication?
            • 設(shè)置應(yīng)用類型
            • 設(shè)置初始化器(Initializer)
            • 設(shè)置監(jiān)聽器(Listener)
            • 設(shè)置監(jiān)聽器(Listener)
          • 執(zhí)行run()方法
            • 獲取、啟動運行過程監(jiān)聽器
            • 環(huán)境構(gòu)建
            • 創(chuàng)建IOC容器
            • IOC容器的前置處理
            • 刷新容器
            • IOC容器的后置處理
            • 發(fā)出結(jié)束執(zhí)行的事件
            • 執(zhí)行Runners
            • 總結(jié)
          • 總結(jié)

          前言

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

          今天開始底層源碼介紹的階段,相對內(nèi)容比較深一點,作者也盡可能介紹的通俗易懂,層次分明一點。相信讀過我寫的Mybatis專欄的文章都知道,只要跟著作者的步驟,方法一步步研究,其實源碼并不難。

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

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

          源碼版本

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

          從哪入手?

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

          至于從哪入手不是很簡單的問題嗎,當(dāng)然主啟動類了,即是標(biāo)注著@SpringBootApplication注解并且有著main()方法的類,如下一段代碼:

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

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

          源碼如何切分?

          SpringApplication中的靜態(tài)run()方法并不是一步完成的,最終執(zhí)行的源碼如下:

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

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

          如何創(chuàng)建SpringApplication?

          創(chuàng)建即是new對象了,DEBUG跟進(jìn)代碼,最終執(zhí)行的SpringApplication構(gòu)造方法如下圖:

          如上圖中標(biāo)注的注釋,創(chuàng)建過程重用的其實分為這三個階段,下面將會一一介紹每個階段做了什么事。

          設(shè)置應(yīng)用類型

          這個過程非常重要,直接決定了項目的類型,應(yīng)用類型分為三種,都在WebApplicationType這個枚舉類中,如下:

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

          判斷的依據(jù)很簡單,就是加載對應(yīng)的類,比如加載了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程序。

          設(shè)置初始化器(Initializer)

          初始化器ApplicationContextInitializer是個好東西,用于IOC容器刷新之前初始化一些組件,比如ServletContextApplicationContextInitializer

          那么如何獲取初始化器呢?跟著上圖中的代碼進(jìn)入,在SpringApplication中的如下圖中的方法:

          相對重要的就是第一步獲取初始化器的名稱了,這個肯定是全類名了,詳細(xì)源碼肯定在loadFactoryNames()方法中了,跟著源碼進(jìn)入,最終調(diào)用的是#SpringFactoriesLoader.loadSpringFactories()方法。

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

          spring-boot-autoconfigurespring.factories文件中的值如下圖:

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

          下圖中是我的demo中注入的初始化器,現(xiàn)實項目中并不止這些。

          這也告訴我們自定義一個ApplicationContextInitializer只需要實現(xiàn)接口,在spring.factories文件中設(shè)置即可。

          設(shè)置監(jiān)聽器(Listener)

          監(jiān)聽器(ApplicationListener)這個概念在Spring中就已經(jīng)存在,主要用于監(jiān)聽特定的事件(ApplicationEvent),比如IOC容器刷新、容器關(guān)閉等。

          Spring Boot擴展了ApplicationEvent構(gòu)建了SpringApplicationEvent這個抽象類,主要用于Spring Boot啟動過程中觸發(fā)的事件,比如程序啟動中、程序啟動完成等。如下圖:

          監(jiān)聽器如何獲取?從源碼中知道其實和初始化器(ApplicationContextInitializer)執(zhí)行的是同一個方法,同樣是從META-INF/spring.factories文件中獲取。

          spring-boot-autoconfigurespring.factories文件中的值如下圖:

          spring.factories文件不止一個,同樣監(jiān)聽器也不止以上這些。

          作者demo中注入的一些監(jiān)聽器如下圖:

          總結(jié)

          SpringApplication的構(gòu)建都是為了run()方法啟動做鋪墊,構(gòu)造方法中總共就有幾行代碼,最重要的部分就是設(shè)置應(yīng)用類型、設(shè)置初始化器、設(shè)置監(jiān)聽器。

          「注意」:初始化器和這里的監(jiān)聽器都要放置在spring.factories文件中才能在這一步驟加載,否則不會生效,因此此時IOC容器還未創(chuàng)建,即使將其注入到IOC容器中也是不會生效的。

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

          執(zhí)行run()方法

          上面分析了SpringApplication的構(gòu)建過程,一切都做好了鋪墊,現(xiàn)在到了啟動的過程了。

          作者根據(jù)源碼將啟動過程分為了「8步」,下面將會一一介紹。

          1. 獲取、啟動運行過程監(jiān)聽器

          SpringApplicationRunListener這個監(jiān)聽器和ApplicationListener不同,它是用來監(jiān)聽?wèi)?yīng)用程序啟動過程的,接口的各個方法含義如下:

          public interface SpringApplicationRunListener {

              // 在run()方法開始執(zhí)行時,該方法就立即被調(diào)用,可用于在初始化最早期時做一些工作
              void starting();
              // 當(dāng)environment構(gòu)建完成,ApplicationContext創(chuàng)建之前,該方法被調(diào)用
              void environmentPrepared(ConfigurableEnvironment environment);
              // 當(dāng)ApplicationContext構(gòu)建完成時,該方法被調(diào)用
              void contextPrepared(ConfigurableApplicationContext context);
              // 在ApplicationContext完成加載,但沒有被刷新前,該方法被調(diào)用
              void contextLoaded(ConfigurableApplicationContext context);
              // 在ApplicationContext刷新并啟動后,CommandLineRunners和ApplicationRunner未被調(diào)用前,該方法被調(diào)用
              void started(ConfigurableApplicationContext context);
              // 在run()方法執(zhí)行完成前該方法被調(diào)用
              void running(ConfigurableApplicationContext context);
              // 當(dāng)應(yīng)用運行出錯時該方法被調(diào)用
              void failed(ConfigurableApplicationContext context, Throwable exception);
          }

          如何獲取運行監(jiān)聽器?

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

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

          跟進(jìn)getRunListeners()方法,其實還是調(diào)用了loadFactoryNames()方法從spring.factories文件中獲取值,如下:

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

          最終注入的是EventPublishingRunListener這個實現(xiàn)類,創(chuàng)建實例過程肯定是通過反射了,因此我們看看它的構(gòu)造方法,如下圖:

          這個運行監(jiān)聽器內(nèi)部有一個事件廣播器(SimpleApplicationEventMulticaster),主要用來廣播特定的事件(SpringApplicationEvent)來觸發(fā)特定的監(jiān)聽器ApplicationListener

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

          如何啟動運行監(jiān)聽器?

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

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

          執(zhí)行SpringApplicationRunListenersstarting()方法,跟進(jìn)去其實很簡單,遍歷執(zhí)行上面獲取的運行監(jiān)聽器,這里只有一個EventPublishingRunListener。因此執(zhí)行的是它的starting()方法,源碼如下圖:

          上述源碼中邏輯很簡單,其實只是執(zhí)行了multicastEvent()方法,廣播了ApplicationStartingEvent事件。至于multicastEvent()內(nèi)部方法感興趣的可以看看,其實就是遍歷ApplicationListener的實現(xiàn)類,找到監(jiān)聽ApplicationStartingEvent這個事件的監(jiān)聽器,執(zhí)行onApplicationEvent()方法。

          總結(jié)

          這一步其實就是廣播了ApplicationStartingEvent事件來觸發(fā)監(jiān)聽這個事件的ApplicationListener

          因此如果自定義了ApplicationListener并且監(jiān)聽了ApplicationStartingEvent(應(yīng)用程序開始啟動)事件,則這個監(jiān)聽器將會被觸發(fā)。

          2. 環(huán)境構(gòu)建

          這一步主要用于加載系統(tǒng)配置以及用戶的自定義配置(application.properties),源碼如下,在run()方法中:

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

          prepareEnvironment方法內(nèi)部廣播了ApplicationEnvironmentPreparedEvent事件,源碼如下圖:

          環(huán)境構(gòu)建這一步加載了系統(tǒng)環(huán)境配置、用戶自定義配置并且廣播了ApplicationEnvironmentPreparedEvent事件,觸發(fā)監(jiān)聽器。

          3. 創(chuàng)建IOC容器

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

          context = createApplicationContext();

          跟進(jìn)代碼,真正執(zhí)行的是ApplicationContextFactory方法,如下圖:

          根據(jù)webApplicationType決定創(chuàng)建的類型,很顯然,我這里的是servlet,因此創(chuàng)建的是AnnotationConfigServletWebServerApplicationContext

          這一步僅僅是創(chuàng)建了IOC容器,未有其他操作。

          4. IOC容器的前置處理

          這一步真是精華了,在刷新容器之前做準(zhǔn)備,其中有一個非常關(guān)鍵的操作:將啟動類注入容器,為后續(xù)的自動化配置奠定基礎(chǔ)。源碼如下:

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

          prepareContext()源碼解析如下圖,內(nèi)容還是挺多的:

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

          調(diào)用初始化器

          SpringApplication構(gòu)建過程中設(shè)置的初始化器,從spring.factories取值的。執(zhí)行的流程很簡單,遍歷執(zhí)行,源碼如下圖:

          將自定義的ApplicationContextInitializer放在META-INF/spring.factories中,在此時也是會被調(diào)用。

          加載啟動類,注入容器

          這一步是將主啟動類加載到IOC容器中,作為后續(xù)自動配置的入口。

          SpringApplication構(gòu)建過程中將主啟動類放置在primarySources這個集合中,此時的getAllSources()即是從其中取值,如下圖:

          這里取出的就是主啟動類,當(dāng)然你的項目中可能不止一個,接下來就是將其加載到IOC容器中了,源碼如下:

          load(context, sources.toArray(new Object[0]));

          跟著代碼進(jìn)去,其實主要邏輯都在BeanDefinitionLoader.load()方法,如下圖:

          將主啟動類加載到beanDefinitionMap,后續(xù)該啟動類將作為開啟自動配置化的入口,后續(xù)章節(jié)詳細(xì)介紹。

          兩次廣播事件

          這一步涉及到了兩次事件廣播,分別是ApplicationContextInitializedEventApplicationPreparedEvent,對應(yīng)的源碼如下:

          listeners.contextPrepared(context);
          load(context, sources.toArray(new Object[0]));

          5. 刷新容器

          刷新容器完全是Spring的功能了,比如初始化資源,初始化上下文廣播器等,這個就不再詳細(xì)介紹,有興趣可以看看Spring的源碼。

          protected void refresh(ApplicationContext applicationContext) {
              Assert.isInstanceOf(AbstractApplicationContext.classapplicationContext);
              //調(diào)用創(chuàng)建的容器applicationContext中的refresh()方法
              ((AbstractApplicationContext)applicationContext).refresh();
          }
          public void refresh() throws BeansException, IllegalStateException {
              synchronized (this.startupShutdownMonitor) {
                  /**
                   * 刷新上下文環(huán)境
                   */

                  prepareRefresh();

                  /**
                   * 初始化BeanFactory,解析XML,相當(dāng)于之前的XmlBeanFactory的操作,
                   */

                  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

                  /**
                   * 為上下文準(zhǔn)備BeanFactory,即對BeanFactory的各種功能進(jìn)行填充,如常用的注解@Autowired @Qualifier
                   * 添加ApplicationContextAwareProcessor處理器
                   * 在依賴注入忽略實現(xiàn)*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
                   * 注冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進(jìn)去
                   */

                  prepareBeanFactory(beanFactory);

                  try {
                      /**
                       * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
                       */

                      postProcessBeanFactory(beanFactory);

                      /**
                       * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
                       * 執(zhí)行對應(yīng)的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
                       */

                      invokeBeanFactoryPostProcessors(beanFactory);

                      /**
                       * 注冊攔截Bean創(chuàng)建的Bean處理器,即注冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區(qū)別
                       * 注意,這里僅僅是注冊,并不會執(zhí)行對應(yīng)的方法,將在bean的實例化時執(zhí)行對應(yīng)的方法
                       */

                      registerBeanPostProcessors(beanFactory);

                      /**
                       * 初始化上下文中的資源文件,如國際化文件的處理等
                       */

                      initMessageSource();

                      /**
                       * 初始化上下文事件廣播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher
                       */

                      initApplicationEventMulticaster();

                      /**
                       * 給子類擴展初始化其他Bean
                       */

                      onRefresh();

                      /**
                       * 在所有bean中查找listener bean,然后注冊到廣播器中
                       */

                      registerListeners();

                      /**
                       * 設(shè)置轉(zhuǎn)換器
                       * 注冊一個默認(rèn)的屬性值解析器
                       * 凍結(jié)所有的bean定義,說明注冊的bean定義將不能被修改或進(jìn)一步的處理
                       * 初始化剩余的非惰性的bean,即初始化非延遲加載的bean
                       */

                      finishBeanFactoryInitialization(beanFactory);

                      /**
                       * 通過spring的事件發(fā)布機制發(fā)布ContextRefreshedEvent事件,以保證對應(yīng)的監(jiān)聽器做進(jìn)一步的處理
                       * 即對那種在spring啟動后需要處理的一些類,這些類實現(xiàn)了ApplicationListener<ContextRefreshedEvent>,
                       * 這里就是要觸發(fā)這些類的執(zhí)行(執(zhí)行onApplicationEvent方法)
                       * 另外,spring的內(nèi)置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
                       * 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過程,同時發(fā)出ContextRefreshEvent通知其他人
                       */

                      finishRefresh();
                  }

                  finally {
              
                      resetCommonCaches();
                  }
              }
          }

          6. IOC容器的后置處理

          一個擴展方法,源碼如下:

          afterRefresh(context, applicationArguments);

          默認(rèn)為空,如果有自定義需求可以重寫,比如打印一些啟動結(jié)束日志等。

          7. 發(fā)出結(jié)束執(zhí)行的事件

          同樣是EventPublishingRunListener這個監(jiān)聽器,廣播ApplicationStartedEvent事件。

          但是這里廣播事件和前幾次不同,并不是廣播給SpringApplication中的監(jiān)聽器(在構(gòu)建過程中從spring.factories文件獲取的監(jiān)聽器)。因此在IOC容器中注入的監(jiān)聽器(使用@Component等方式注入的)也能夠生效。前面幾個事件只有在spring.factories文件中設(shè)置的監(jiān)聽器才會生效。

          跟著代碼進(jìn)入,可以看到started()方法源碼如下:

          這里并沒有用事件廣播器SimpleApplicationEventMulticaster廣播事件,而是使用ConfigurableApplicationContext直接在IOC容器中發(fā)布事件。

          8. 執(zhí)行Runners

          Spring Boot 提供了兩種Runner讓我們定制一些額外的操作,分別是CommandLineRunnerApplicationRunner,關(guān)于這兩個的區(qū)別,后面文章詳細(xì)介紹。

          調(diào)用的源碼如下:

          callRunners(context, applicationArguments);

          跟進(jìn)代碼,其實真正執(zhí)行的是如下方法:

          邏輯很簡單,從IOC容器中獲取,遍歷調(diào)用。

          總結(jié)

          Spring Boot 啟動流程相對簡單些,作者將其細(xì)分了以上八個步驟,希望能夠幫助讀者理解,流程圖如下:

          總結(jié)

          Spring Boot啟動流程就介紹到這里了,需要重點理解run()方法執(zhí)行的八個步驟以及事件、初始化器、監(jiān)聽器等組件的執(zhí)行時間點。

          作者每一篇文章都很用心,這篇源碼解析花了三天時間精雕細(xì)琢,力求講解的通俗易懂,希望能夠幫助到你。

          另外作者的第一本PDF書籍已經(jīng)整理好了,由淺入深的詳細(xì)介紹了Mybatis基礎(chǔ)以及底層源碼,有需要的朋友公眾號回復(fù)關(guān)鍵詞「Mybatis進(jìn)階」即可獲取,目錄如下:


          往期推薦



          Spring Boot 與 注解那些事兒~

          嗯,挺全乎兒的,Spring Boot 多環(huán)境配置都在這兒了,你喜歡哪一種呢?

          三十二張圖告訴你,Jenkins構(gòu)建SpringBoot有多簡單~

          一次打包引發(fā)的思考,原來maven還可以這么玩

          分享幾個壓箱底兒的實用工具~

          Swagger3.0 天天刷屏,真的香嗎?

          推薦三款插件,讓你玩轉(zhuǎn)Github

          熱部署還在用devtools嗎?JRebel不香嗎?


          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷午夜精品久久久久久性色AV | 韩国无码一区二区三区精品 | 波多野结衣大香蕉 | 国产熟女一区二区视频网站 | 91人妻人人人 |