天天看點

面試官問Spring 啟動流程,把這篇文章甩給他!

作者:程式猿凱撒

前言

Spring啟動時候整個入口是這麼一個方法

AbstractApplicationContext#refresh
面試官問Spring 啟動流程,把這篇文章甩給他!

總共有12個方法,也就是啟動時的核心步驟

AbstractApplicationContext有衆多實作,這裡我選擇SpringBoot Web應用預設的實作來講

AnnotationConfigServletWebServerApplicationContext
面試官問Spring 啟動流程,把這篇文章甩給他!

AnnotationConfigServletWebServerApplicationContext類圖

對應的SpringBoot版本為 2.2.5.RELEASE

高版本refresh方法會多一些日志相關的代碼,這裡為了友善講解,就使用這個版本

是以後面本文提到的所有子類的方法實作、重寫都是指AnnotationConfigServletWebServerApplicationContext及其父類

本文主要是講一些啟動的步驟,具體的很多技術實作細節、技術點這裡就不過多贅述

如果有疑問的,可以檢視三萬字盤點Spring 9大核心基礎功能這篇文章或者公衆号菜單欄中關于Spring的文章,基本上都能找到答案

prepareRefresh

prepareRefresh整個重新整理的一個步驟,這個步驟是做啟動的一個準備操作

面試官問Spring 啟動流程,把這篇文章甩給他!

ApplicationContext剛建立出來,什麼也沒有,是以需要做一些準備

首先是做一些狀态位的變更,表明開始啟動了或者重新整理了

後面一行

initPropertySources

initPropertySources是一個模闆方法,本身是一個空實作,是給子類用的

我們的這個子類就重寫了initPropertySources方法

面試官問Spring 啟動流程,把這篇文章甩給他!

會将Servlet相關的配置加入到Environment中,這樣我們就能從Environment中擷取到Servlet相關的配置了

再後面一行

getEnvironment().validateRequiredProperties()

這行代碼就是校驗一些必要的配置屬性,我們可以通過ConfigurableEnvironment來設定哪些屬性是必要的,預設是沒有必要的

是以prepareRefresh就是做了一些前置操作,準備好一些屬性配置相關的東西,後面的其它環節,比如說生成Bean時可能需要用到這些配置

obtainFreshBeanFactory

面試官問Spring 啟動流程,把這篇文章甩給他!

這一步驟是重新整理BeanFactory并且擷取BeanFactory

refreshBeanFactory() 和 getBeanFactory() 都是抽象方法,由子類來實作的

面試官問Spring 啟動流程,把這篇文章甩給他!

而子類的實作其實很簡單,就是給beanFactory設定一個id和傳回beanFactory

beanFactory就是下面這個玩意

面試官問Spring 啟動流程,把這篇文章甩給他!

并且建立對象的ApplicationContext對象的時候就建立了,類型為

DefaultListableBeanFactory

是以從這就可以看出來,雖然說BeanFactory是一個接口,有非常多的實作

但是實際情況下,真正使用的就是DefaultListableBeanFactory

并且DefaultListableBeanFactory其實算是BeanFactory唯一真正的實作

除此之外,還可以得出一個結論,ApplicationContext中有一個BeanFactory(DefaultListableBeanFactory)

prepareBeanFactory

上一步驟擷取到了BeanFactory,但是這個BeanFactory僅僅就是剛剛new出來的,什麼也沒有

是以目前步驟就是對BeanFactory做一些配置工作

前三行代碼

面試官問Spring 啟動流程,把這篇文章甩給他!

先給BeanFactory設定了一個ClassLoader,因為BeanFactory是用來建立Bean,需要加載Bean class對象

然後設定了一個BeanExpressionResolver,這個是用來解析SpEL表達式的

然後添加了一個PropertyEditorRegistrar,也就是

ResourceEditorRegistrar

這個的作用就是為BeanFactory添加一堆跟資源相關的PropertyEditor

面試官問Spring 啟動流程,把這篇文章甩給他!

ResourceEditorRegistrar核心實作方法

PropertyEditor之前說過,就是進行類型轉換的,将一個字元串轉成對應的類型

接下來這幾行

面試官問Spring 啟動流程,把這篇文章甩給他!

這裡主要是添加了一個BeanPostProcessor,也就是

ApplicationContextAwareProcessor

BeanPostProcessor我們都知道會在Bean的生命周期階段進行回調,是Bean的生命周期一個核心的環節

ApplicationContextAwareProcessor這個是用來處理Bean生命周期中的Aware回調有關

面試官問Spring 啟動流程,把這篇文章甩給他!

當你的Bean實作這些接口的時候,在建立的時候Spring會回調這些接口,傳入對應的對象

而後面的這行代碼

beanFactory.ignoreDependencyInterface(EnvironmentAware.class);

意思是說,如果你的Bean想注入一個EnvironmentAware對象

java           

複制代碼

@Resource private EnvironmentAware environmentAware;

這是不允許的

因為很簡單,注入一個EnvironmentAware對象,沒有實際的意義

後面的其它幾行代碼也都是這個意思

再接下來這幾行

面試官問Spring 啟動流程,把這篇文章甩給他!

這跟上面的ignoreDependencyInterface作用相反

他是來設定依賴注入時Bean的類型所對應的對象

比如說這行代碼

beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);

這行代碼的意思就是當你需要注入一個Bean類型為BeanFactory.class類型的時候

java           

複制代碼

@Resource private BeanFactory beanFactory;

那麼實際注入的就是方法第二個參數beanFactory,也就是上面擷取的DefaultListableBeanFactory對象

同理,注入ResourceLoader、ApplicationEventPublisher、ApplicationContext時,其實注入的對象都是this,也就是目前的ApplicationContext對象

再再接下來這幾行

面試官問Spring 啟動流程,把這篇文章甩給他!

最開始又添加了一個BeanPostProcessor

ApplicationListenerDetector
面試官問Spring 啟動流程,把這篇文章甩給他!

這個BeanPostProcessor是跟ApplicationListener有關

他是将單例的ApplicationListener給添加到ApplicationContext中

再後面就是往BeanFactory裡面添加一些跟配置屬性相關的單例對象,如果有哪裡用到,就可以從BeanFactory中擷取到了

prepareBeanFactory就完了

正如方法名字的含義一樣,就是對BeanFactory做一些配置相關的東西

比如添加一些BeanPostProcessor,注冊一些PropertyEditor

為Bean的生成做準備操作

最後畫張圖來總結一下這個方法的作用

面試官問Spring 啟動流程,把這篇文章甩給他!

此時BeanFactory狀态就是這樣的

面試官問Spring 啟動流程,把這篇文章甩給他!

postProcessBeanFactory

這個方法是一個模闆方法

面試官問Spring 啟動流程,把這篇文章甩給他!

本身是空實作,是交給子類來擴充,子類可以根據不同的特性再對BeanFactory進行一些準備工作

比如我們用的這個Web實作就重寫了這個方法,對BeanFactory設定一些Web相關的配置

面試官問Spring 啟動流程,把這篇文章甩給他!

AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory

首先調用父類ServletWebServerApplicationContext的postProcessBeanFactory

面試官問Spring 啟動流程,把這篇文章甩給他!

ServletWebServerApplicationContext#postProcessBeanFactory

前兩行代碼跟之前說的一樣,也是添加Aware接口的回調對應的BeanPostProcessor,隻不過這個Aware是跟Servlet相關的東西

接下來調用registerWebApplicationScopes方法,最終會調到下面這個方法

WebApplicationContextUtils#registerWebApplicationScopes
面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法幹了兩件事

第一件事就是注冊一下Bean在Web環境下的作用域request、session,八股文中的東西

第二個就是注冊一些依賴注入時Bean類型和對應的對象,這在日常開發中還是有用的

比如可以直接注入一個ServletRequest

java           

複制代碼

@Resource private ServletRequest servletRequest;

是以,父類的實作主要還是對BeanFactory進行一些配置,隻不過配置的主要是跟Web環境相關的東西

現在來看看AnnotationConfigServletWebServerApplicationContext自身的實作

面試官問Spring 啟動流程,把這篇文章甩給他!

核心代碼就是這兩行

this.scanner.scan(this.basePackages);

this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));

scanner和reader就是下面這兩個玩意

面試官問Spring 啟動流程,把這篇文章甩給他!

也就是說,如果這些配置都不是空的話,那麼此時就會掃描對應的包的下Bean,生成對應的BeanDenifition,再注冊到DefaultListableBeanFactory

至于為什麼會存到DefaultListableBeanFactory中,可以看看之前的文章

此時BeanFactory大概是這麼一個狀态

面試官問Spring 啟動流程,把這篇文章甩給他!

除此之外,還有一個賊重要的事

AnnotationConfigServletWebServerApplicationContext這個ApplicationContext建立時會去建立AnnotatedBeanDefinitionReader

而AnnotatedBeanDefinitionReader的構造方法最終會調用這麼一行代碼

AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)
面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法非常重要,他會去注冊一些BeanDefinition到BeanFactory中,這裡我稱為Spring内部的Bean

這裡我說幾個常見和重要的

  • ConfigurationClassPostProcessor:這個是用來處理配置類的,非常重要,記住這個類,後面有大用
  • AutowiredAnnotationBeanPostProcessor:處理@Autowired、@Value注解
  • CommonAnnotationBeanPostProcessor:處理@Resource、@PostConstruct等注解

是以除了掃描出來的一些Bean對應的BeanDefinition,還有一些Spring内部的Bean會注冊到BeanFactory中

此時BeanFactory的狀态就如下圖所示

面試官問Spring 啟動流程,把這篇文章甩給他!

不過,在SpringBoot預設情況下,不會指定包和配置類,也就不會掃描檔案,生成BeanDefinition

但是内部建立的BeanDefinition依然存在,并且在ApplicationContext建立的時候就注冊到BeanFactory中了

是以總結來說,postProcessBeanFactory這個方法是交給子類對BeanFactory做一些準備操作,并且可能會掃描Bean

invokeBeanFactoryPostProcessors

從這個方法的名字可以看出,是調用BeanFactoryPostProcessor,這個步驟非常重要,而且過程有點繞

前置知識:BeanFactoryPostProcessor及其子接口

BeanFactoryPostProcessor是一個接口,有一個方法,方法參數就是BeanFactory

面試官問Spring 啟動流程,把這篇文章甩給他!

通過這個方法就可以拿到BeanFactory,然後對BeanFactory做一些自己的調整

比如說,你想關閉循環依賴,你就可以實作這個接口,然後進行調整

他還有一個子接口BeanDefinitionRegistryPostProcessor

面試官問Spring 啟動流程,把這篇文章甩給他!

這個接口是對BeanDefinitionRegistry進行調整,BeanDefinitionRegistry就是存BeanDefinition的地方,真實的實作就是DefaultListableBeanFactory

是以BeanDefinitionRegistryPostProcessor的作用就是往BeanDefinitionRegistry(DefaultListableBeanFactory)中添加BeanDefinition的

再看invokeBeanFactoryPostProcessors

有了這兩個前置知識之後,我們來看看invokeBeanFactoryPostProcessors方法的實作

面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法最終會調用下面方法來真正的處理

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors;
面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法比較長,大緻分為兩件事

  • 調用所有的BeanDefinitionRegistryPostProcessor,解析配置類,注冊BeanDefinition到DefaultListableBeanFactory中
  • 從BeanFactory中擷取所有的BeanFactoryPostProcessor進行調用,完成對BeanFactory一些其它的擴充

調用BeanDefinitionRegistryPostProcessor

首先第一步,先從BeanFactory中擷取到所有的BeanDefinitionRegistryPostProcessor對象,調用它的postProcessBeanDefinitionRegistry方法

還記得上一節在說注冊Spring内部的Bean時特地強調的一個類ConfigurationClassPostProcessor不?

他就實作了BeanDefinitionRegistryPostProcessor接口

面試官問Spring 啟動流程,把這篇文章甩給他!

是以此時擷取到的就是ConfigurationClassPostProcessor

面試官問Spring 啟動流程,把這篇文章甩給他!
擷取ConfigurationClassPostProcessor的時候會走Bean的生命周期,也就是會回調前面添加的BeansPostProcessor,但是也沒幾個

之後會調用他的postProcessBeanDefinitionRegistry方法,來處理此時BeanFactory中的配置類

配置類從哪來,前面一直沒提到過

但是看一下ApplicationContext是如何使用的就知道了

比如說,下面這個demo

面試官問Spring 啟動流程,把這篇文章甩給他!

在建立一個ApplicationContext之後,在注冊一個Bean之後再refresh

此時這個注冊的Bean就是配置類。

如果你不注冊,那是真沒有配置類,此時也就沒什麼意義了。

是以,ApplicationContext一定會有一個配置類,不然沒有意義。

在SpringBoot條件下,SpringBoot在啟動時就會将啟動引導類當做配置類給扔到BeanFactory中。

是以ConfigurationClassPostProcessor最開始處理的時候,就是處理啟動引導類

我們可以在ConfigurationClassPostProcessor方法實作上打個斷點驗證一下

面試官問Spring 啟動流程,把這篇文章甩給他!

在處理之前可以看見,除了幾個spring内部的BeanDefinition之外,還有一個myApplication,就是我的啟動引導類

面試官問Spring 啟動流程,把這篇文章甩給他!

處理的時候它會解析啟動引導類的注解,進行自動裝配,掃描你寫的代碼的操作,之後生成BeanDefinition

當處理完成之後我們再看看,DefaultListableBeanFactory有了非常多的BeanDefinition了

面試官問Spring 啟動流程,把這篇文章甩給他!

是以到第一步就完成了,此時BeanFactory就加載了很多Bean

接下來,由于又新注冊了很多BeanDefinition,而這些裡面就有可能有BeanDefinitionRegistryPostProcessor接口的實作

是以之後會重複從BeanFactory中擷取BeanDefinitionRegistryPostProcessor,調用postProcessBeanDefinitionRegistry

一直會循環下去,直到所有的BeanDefinitionRegistryPostProcessor都被調用為止

面試官問Spring 啟動流程,把這篇文章甩給他!

由于BeanDefinitionRegistryPostProcessor繼承BeanFactoryPostProcessor

是以之後也會調用BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法

調用BeanFactoryPostProcessor

當調完所有的BeanDefinitionRegistryPostProcessor實作方法

之後就會從BeanFactory擷取所有的BeanFactoryPostProcessor(除了BeanDefinitionRegistryPostProcessor實作之外),調用postProcessBeanFactory方法

此時就可以通過BeanFactoryPostProcessor再次對BeanFactory進制擴充

總的來說,這一步驟的核心作用就是完成對BeanFactory自定義擴充,但是由于BeanFactoryPostProcessor都是Bean,是以要第一步先加載Bean,之後才能通過BeanFactoryPostProcessor來擴充

一張圖來總結上面主要幹的事

面試官問Spring 啟動流程,把這篇文章甩給他!

這裡簡化了一些前面提到東西

registerBeanPostProcessors

上面一個步驟已經完成了Bean的掃描和對BeanFactory的擴充

這一節通過方法名就可以看出,是跟BeanPostProcessor相關

面試官問Spring 啟動流程,把這篇文章甩給他!

不過在這個方法執行之前,我們先來看看此時BeanFactory中已經有了哪些BeanPostProcessor

面試官問Spring 啟動流程,把這篇文章甩給他!

此時隻有4個,前3個前面都提到過,但是像我們熟知的處理@Autowired、@Resource注解的BeanPostProcessor都不在裡面

是以這裡就有一個非常重要的小細節

在目前這個步驟執行之前如果從BeanFactory中擷取Bean的話,雖然會走Bean生命周期的整個過程,但是@Autowired、@Resource注解都不會生效,因為此時BeanFactory中還沒有處理這些注解的BeanPostProcessor(CommonAnnotationBeanPostProcessor等)

什麼意思呢,舉個例子

比如上面一節,在目前步驟執行之前會從BeanFactory中擷取BeanFactoryPostProcessor

假設現在你實作了BeanFactoryPostProcessor,想注入一個ApplicationContext對象

面試官問Spring 啟動流程,把這篇文章甩給他!

此時是注入不成功的,@Resource注解不會生效,就是這個意思。

這時隻能通過ApplicationContextAware方式擷取,因為有對應的BeanPostProcessor(ApplicationContextAwareProcessor)

接下來我們再來看看registerBeanPostProcessors實作

最終也是調用下面的方法

PostProcessorRegistrationDelegate#registerBeanPostProcessors
面試官問Spring 啟動流程,把這篇文章甩給他!

這個過程就沒上面那個步驟複雜了

其實就是從BeanFactory中擷取到所有的BeanPostProcessor,然後添加到BeanFactory中

不過值得注意的是,BeanPostProcessor建立會有優先級,優先級高的會先被建立和添加到BeanFactory中

到這一步其實BeanFactory就算是準備完成了,基本上跟建立Bean相關的前置操作幾乎都完成了

最後再來張圖總結一下這個方法幹的事

面試官問Spring 啟動流程,把這篇文章甩給他!

initMessageSource

這個方法是處理國際化相關的操作

面試官問Spring 啟動流程,把這篇文章甩給他!

這個操作比較簡單,就是從BeanFactory中看看有沒有Bean名稱為messageSource的Bean

面試官問Spring 啟動流程,把這篇文章甩給他!

有的話就使用這個MessageSource,沒有的話就用預設的

不過SpringBoot項目下會自動裝配一個MessageSource,是以此時容器中是有的

initApplicationEventMulticaster

面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法跟上面的差不多,也是從BeanFactory找有沒有ApplicationEventMulticaster

有就用容器中的,沒有就自己建立一個

ApplicationEventMulticaster是真正用來釋出事件的,ApplicationEventPublisher最終也是調用他來釋出事件

ApplicationEventMulticaster内部會緩存所有的監聽器

當通過ApplicationEventMulticaster釋出事件的時候,會去找到所有的監聽器,然後調用

onRefresh

onRefresh也是一個模闆方法,本身也是空實作

面試官問Spring 啟動流程,把這篇文章甩給他!

子類重寫這個方法,會去建立一個Web伺服器

面試官問Spring 啟動流程,把這篇文章甩給他!

registerListeners

面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法其實也比較簡單,就是将監聽器給添加到ApplicationEventMulticaster中

finishBeanFactoryInitialization

面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法首先又是老套路,就是判斷容器中有沒有ConversionService

ConversionService也是用來做類型轉換的,跟前面提到的PropertyEditor作用差不多

如果有,就把ConversionService設定到BeanFactory中

到這一步,BeanFactory才算真的準備完成。。。

之後其實幹的事就不太重要了

但是最後一行比較重要

beanFactory.preInstantiateSingletons();

從方法的命名就可以看出,執行個體化所有的單例對象

因為對于BeanFactory的一些配置在前面都完成了,是以這裡就可以來執行個體化所有的單例對象了

這個方法會做兩件事

第一件事就是執行個體化所有的非懶加載的單例Bean

面試官問Spring 啟動流程,把這篇文章甩給他!

實際上就是通過getBean方法來的,因為擷取Bean,不存在的時候就會創,會走Bean的生命周期

第二件事就是一旦單例Bean實作了SmartInitializingSingleton接口,就會調用SmartInitializingSingleton的afterSingletonsInstantiated方法

面試官問Spring 啟動流程,把這篇文章甩給他!

這個其實也算是Bean生命周期的一部分。

finishRefresh

這個方法是整個Spring容器重新整理的最後一個方法

面試官問Spring 啟動流程,把這篇文章甩給他!

這個方法就是收尾的操作

清理一下緩存操作

之後就是初始化LifecycleProcessor

面試官問Spring 啟動流程,把這篇文章甩給他!

都是一樣的套路,優先用BeanFactory中的

後面就會調用LifecycleProcessor#onRefresh方法

這個方法的作用就是,如果你的Bean實作了SmartLifecycle的接口,會調start的方法

随後就釋出一個ContextRefreshedEvent事件,表明容器已經重新整理完成了

在Web環境底下,這個finishRefresh方法被重寫了

面試官問Spring 啟動流程,把這篇文章甩給他!

主要是多幹了一件事,那就是啟動Web伺服器

并且會釋出了一個ServletWebServerInitializedEvent事件

這個事件在SpringBoot中用的不多

但是在SpringCloud中卻非常重要

在SpringCloud環境底下會有一個類監聽這個事件

一旦監聽到這個事件,SpringCloud就會将目前的服務的資訊自動注冊到注冊中心上

這就是服務自動注冊的原理

總結

這裡再來簡單回顧一下Spring啟動大緻的幾個過程

最開始的準備操作,這部分就是準備一些配置屬性相關的

之後連續好幾個方法都是準備BeanFactory的,我把上面那張圖拿過來

面試官問Spring 啟動流程,把這篇文章甩給他!

整個準備BeanFactory過程大緻如下:

  • 先配置BeanFactory
  • 通過ConfigurationClassPostProcessor加載Bean到BeanFactory中
  • 從上一步加載的Bean中擷取BeanFactoryPostProcessor,完成對BeanFactory做自定義處理
  • 從上一步加載的Bean中擷取BeanPostProcessor,添加到BeanFactory中

當這些步驟完成之後,BeanFactory跟Bean建立相關的配置幾乎算是配置完成了

之後其實就是一些ApplicationContext内部的一些組價的初始化,比如MessageSource、ApplicationEventMulticaster等等

優先從BeanFactory中擷取,沒有再用預設的

到這ApplicationContext也算配置完成了,之後就可以執行個體化單例非懶加載的Bean了

再後面就是一些掃尾的操作,釋出一個ContextRefreshedEvent事件,表明容器已經重新整理完成了

這時Spring就就算是真正啟動完成了。

繼續閱讀