1. 程式人生 > >面試-Spring的生命週期

面試-Spring的生命週期

Spring Bean的生命週期是Spring面試熱點問題。這個問題即考察對Spring的微觀瞭解,又考察對Spring的巨集觀認識,想要答好並不容易!本文希望能夠從原始碼角度入手,幫助面試者徹底搞定Spring Bean的生命週期。

只有四個!

是的,Spring Bean的生命週期只有這四個階段。把這四個階段和每個階段對應的擴充套件點糅合在一起雖然沒有問題,但是這樣非常凌亂,難以記憶。要徹底搞清楚Spring的生命週期,首先要把這四個階段牢牢記住。例項化和屬性賦值對應構造方法和setter方法的注入,初始化和銷燬是使用者能自定義擴充套件的兩個階段。在這四步之間穿插的各種擴充套件點,稍後會講。

  • 例項化 Instantiation
  • 屬性賦值 Populate
  • 初始化 Initialization
  • 銷燬 Destruction

例項化 -> 屬性賦值 -> 初始化 -> 銷燬

主要邏輯都在doCreate()方法中,邏輯很清晰,就是順序呼叫以下三個方法,這三個方法與三個生命週期階段一一對應,非常重要,在後續擴充套件介面分析中也會涉及。

  • createBeanInstance() -> 例項化
  • populateBean() -> 屬性賦值
  • initializeBean() -> 初始化

原始碼如下,能證明例項化,屬性賦值和初始化這三個生命週期的存在。關於本文的Spring原始碼都將忽略無關部分,便於理解:

請別再問Spring Bean的生命週期了

 

至於銷燬,是在容器關閉時呼叫的,詳見ConfigurableApplicationContext#close()

常用擴充套件點

Spring生命週期相關的常用擴充套件點非常多,所以問題不是不知道,而是記不住或者記不牢。其實記不住的根本原因還是不夠了解,這裡通過原始碼+分類的方式幫大家記憶。

第一大類:影響多個Bean的介面

實現了這些介面的Bean會切入到多個Bean的生命週期中。正因為如此,這些介面的功能非常強大,Spring內部擴充套件也經常使用這些介面,例如自動注入以及AOP的實現都和他們有關。

  • BeanPostProcessor
  • InstantiationAwareBeanPostProcessor

這兩兄弟可能是Spring擴充套件中最重要的兩個介面!InstantiationAwareBeanPostProcessor作用於例項化階段的前後,BeanPostProcessor作用於初始化階段的前後。正好和第一、第三個生命週期階段對應。通過圖能更好理解:

 

請別再問Spring Bean的生命週期了

 

 

InstantiationAwareBeanPostProcessor實際上繼承了BeanPostProcessor介面,嚴格意義上來看他們不是兩兄弟,而是兩父子。但是從生命週期角度我們重點關注其特有的對例項化階段的影響,圖中省略了從BeanPostProcessor繼承的方法。

InstantiationAwareBeanPostProcessor extends BeanPostProcessor

InstantiationAwareBeanPostProcessor原始碼分析:

postProcessBeforeInstantiation呼叫點,忽略無關程式碼:

請別再問Spring Bean的生命週期了

 

可以看到,postProcessBeforeInstantiation在doCreateBean之前呼叫,也就是在bean例項化之前呼叫的,英文原始碼註釋解釋道該方法的返回值會替換原本的Bean作為代理,這也是Aop等功能實現的關鍵點。

postProcessAfterInstantiation呼叫點,忽略無關程式碼:

請別再問Spring Bean的生命週期了

 

可以看到該方法在屬性賦值方法內,但是在真正執行賦值操作之前。其返回值為boolean,返回false時可以阻斷屬性賦值階段(continueWithPropertyPopulation = false;)。

關於BeanPostProcessor執行階段的原始碼穿插在下文Aware介面的呼叫時機分析中,因為部分Aware功能的就是通過他實現的!只需要先記住BeanPostProcessor在初始化前後呼叫就可以了。

第二大類:只調用一次的介面

這一大類介面的特點是功能豐富,常用於使用者自定義擴充套件。

第二大類中又可以分為兩類:

  • Aware型別的介面
  • 生命週期介面

無所不知的Aware

Aware型別的介面的作用就是讓我們能夠拿到Spring容器中的一些資源。基本都能夠見名知意,Aware之前的名字就是可以拿到什麼資源,例如BeanNameAware可以拿到BeanName,以此類推。呼叫時機需要注意:所有的Aware方法都是在初始化階段之前呼叫的!

Aware介面眾多,這裡同樣通過分類的方式幫助大家記憶。

Aware介面具體可以分為兩組,至於為什麼這麼分,詳見下面的原始碼分析。如下排列順序同樣也是Aware介面的執行順序,能夠見名知意的介面不再解釋。

Aware Group1

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware

Aware Group2

  • EnvironmentAware
  • EmbeddedValueResolverAware 這個知道的人可能不多,實現該介面能夠獲取Spring EL解析器,使用者的自定義註解需要支援spel表示式的時候可以使用,非常方便。
  • ApplicationContextAware(ResourceLoaderAwareApplicationEventPublisherAwareMessageSourceAware) 這幾個介面可能讓人有點懵,實際上這幾個介面可以一起記,其返回值實質上都是當前的ApplicationContext物件,因為ApplicationContext是一個複合介面,如下:

 

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
 MessageSource, ApplicationEventPublisher, ResourcePatternResolver {}

這裡涉及到另一道面試題,ApplicationContext和BeanFactory的區別,可以從ApplicationContext繼承的這幾個介面入手,除去BeanFactory相關的兩個介面就是ApplicationContext獨有的功能,這裡不詳細說明。

Aware呼叫時機原始碼分析

詳情如下,忽略了部分無關程式碼。程式碼位置就是我們上文提到的initializeBean方法詳情,這也說明了Aware都是在初始化階段之前呼叫的!

請別再問Spring Bean的生命週期了

 

可以看到並不是所有的Aware介面都使用同樣的方式呼叫。Bean××Aware都是在程式碼中直接呼叫的,而ApplicationContext相關的Aware都是通過BeanPostProcessor#postProcessBeforeInitialization()實現的。

感興趣的可以自己看一下ApplicationContextAwareProcessor這個類的原始碼,就是判斷當前建立的Bean是否實現了相關的Aware方法,如果實現了會呼叫回撥方法將資源傳遞給Bean。

至於Spring為什麼這麼實現,應該沒什麼特殊的考量。也許和Spring的版本升級有關。基於對修改關閉,對擴充套件開放的原則,Spring對一些新的Aware採用了擴充套件的方式新增。

BeanPostProcessor的呼叫時機也能在這裡體現,包圍住invokeInitMethods方法,也就說明了在初始化階段的前後執行。

關於Aware介面的執行順序,其實只需要記住第一組在第二組執行之前就行了。每組中各個Aware方法的呼叫順序其實沒有必要記,有需要的時候點進原始碼一看便知。

簡單的兩個生命週期介面

至於剩下的兩個生命週期介面就很簡單了,例項化和屬性賦值都是Spring幫助我們做的,能夠自己實現的有初始化和銷燬兩個生命週期階段。

InitializingBean 對應生命週期的初始化階段,在上面原始碼的invokeInitMethods(beanName, wrappedBean, mbd);方法中呼叫。

有一點需要注意,因為Aware方法都是執行在初始化方法之前,所以可以在初始化方法中放心大膽的使用Aware介面獲取的資源,這也是我們自定義擴充套件Spring的常用方式。

除了實現InitializingBean介面之外還能通過註解或者xml配置的方式指定初始化方法,至於這幾種定義方式的呼叫順序其實沒有必要記。因為這幾個方法對應的都是同一個生命週期,只是實現方式不同,我們一般只採用其中一種方式。

DisposableBean 類似於InitializingBean,對應生命週期的銷燬階段,以ConfigurableApplicationContext#close()方法作為入口,實現是通過迴圈取所有實現了DisposableBean介面的Bean然後呼叫其destroy()方法 。感興趣的可以自行跟一下原始碼。

擴充套件閱讀: BeanPostProcessor 註冊時機與執行順序

註冊時機

我們知道BeanPostProcessor也會註冊為Bean,那麼Spring是如何保證BeanPostProcessor在我們的業務Bean之前初始化完成呢?

請看我們熟悉的refresh()方法的原始碼,省略部分無關程式碼:

請別再問Spring Bean的生命週期了

 

可以看出,Spring是先執行registerBeanPostProcessors()進行BeanPostProcessors的註冊,然後再執行finishBeanFactoryInitialization初始化我們的單例非懶載入的Bean。

執行順序

BeanPostProcessor有很多個,而且每個BeanPostProcessor都影響多個Bean,其執行順序至關重要,必須能夠控制其執行順序才行。關於執行順序這裡需要引入兩個排序相關的介面:PriorityOrdered、Ordered

PriorityOrdered是一等公民,首先被執行,PriorityOrdered公民之間通過介面返回值排序,Ordered是二等公民,然後執行,Ordered公民之間通過介面返回值排序

都沒有實現是三等公民,最後執行。

在以下原始碼中,可以很清晰的看到Spring註冊各種型別BeanPostProcessor的邏輯,根據實現不同排序介面進行分組。優先順序高的先加入,優先順序低的後加入。

請別再問Spring Bean的生命週期了

 

根據排序介面返回值排序,預設升序排序,返回值越低優先順序越高。

/**
 * Useful constant for the highest precedence value.
 * @see java.lang.Integer#MIN_VALUE
 */
 int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
 /**
 * Useful constant for the lowest precedence value.
 * @see java.lang.Integer#MAX_VALUE
 */
 int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

PriorityOrdered、Ordered介面作為Spring整個框架通用的排序介面,在Spring中應用廣泛,也是非常重要的介面。

總結

Spring Bean的生命週期分為四個階段和多個擴充套件點。擴充套件點又可以分為影響多個Bean和影響單個Bean。整理如下:

四個階段

  • 例項化 Instantiation
  • 屬性賦值 Populate
  • 初始化 Initialization
  • 銷燬 Destruction

多個擴充套件點

影響多個Bean

  • BeanPostProcessor
  • InstantiationAwareBeanPostProcessor

影響單個Bean

Aware

  • Aware Group1
  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware
  • Aware Group2
  • EnvironmentAware
  • EmbeddedValueResolverAware
  • ApplicationContextAware(ResourceLoaderAwareApplicationEventPublisherAwareMessageSourceAware)

生命週期

  • InitializingBean
  • D