37、談談Spring Bean的生命週期和作用域?

在企業應用軟件開發中,Java 是毫無爭議的主流語言,開放的 Java EE 規範和強大的開源框架功不可沒,其中 Spring 
毫無疑問已經成爲企業軟件開發的事實標準之一。今天這一講,我將補充 Spring 相關的典型面試問題,並談談其部分設計細節。

今天我要問你的問題是,談談 Spring Bean 的生命週期和作用域?

典型回答

Spring Bean 生命週期比較複雜,可以分爲創建和銷燬兩個過程。

首先,創建 Bean 會經過一系列的步驟,主要包括:

  •   實例化 Bean 對象。
  •   設置 Bean 屬性。
  •   如果我們通過各種 Aware 接口聲明瞭依賴關係,則會注入 Bean 對容器基礎設施層面的依賴。具體包括 BeanNameAware、BeanFactoryAware 和 ApplicationContextAware,分別會注入 Bean ID、Bean Factory 或者 ApplicationContext。
  •   調用 BeanPostProcessor 的前置初始化方法 postProcessBeforeInitialization。
  •   如果實現了 InitializingBean 接口,則會調用 afterPropertiesSet 方法。
  •   調用 Bean 自身定義的 init 方法。
  •   調用 BeanPostProcessor 的後置初始化方法 postProcessAfterInitialization。
  •   創建過程完畢。

 你可以參考下面示意圖理解這個具體過程和先後順序。

第二,Spring Bean 的銷燬過程會依次調用 DisposableBean 的 destroy 方法和 Bean 自身定製的 destroy 方法。

Spring Bean 有五個作用域,其中最基礎的有下面兩種:

  •   Singleton,這是 Spring 的默認作用域,也就是爲每個 IOC 容器創建唯一的一個 Bean 實例。
  •   Prototype,針對每個 getBean 請求,容器都會單獨創建一個 Bean 實例。

從 Bean 的特點來看,Prototype 適合有狀態的 Bean,而 Singleton 則更適合無狀態的情況。另外,使用 Prototype 
作用域需要經過仔細思考,畢竟頻繁創建和銷燬 Bean 是有明顯開銷的。

如果是 Web 容器,則支持另外三種作用域:

  •   Request,爲每個 HTTP 請求創建單獨的 Bean 實例。
  •   Session,很顯然 Bean 實例的作用域是 Session 範圍。
  •   GlobalSession,用於 Portlet 容器,因爲每個 Portlet 有單獨的 Session,GlobalSession 提供一個全局性的 HTTP Session。


考點分析

今天我選取的是一個入門性質的高頻 Spring 面試題目,我認爲相比於記憶題目典型回答裏的細節步驟,理解和思考 Bean 生命週期所體現出來的 Spring 設計和機制更有意義。

你能看到,Bean 的生命週期是完全被容器所管理的,從屬性設置到各種依賴關係,都是容器負責注入,並進行各個階段其他事宜的處理,Spring 容器爲應用開發者定義了清晰的生命週期溝通界面。

如果從具體 API 設計和使用技巧來看,還記得我在專欄第 13 講提到過的 Marker Interface 嗎,Aware 接口就是個典型應用例子,Bean 可以實現各種不同 Aware 的子接口,爲容器以 Callback 形式注入依賴對象提供了統一入口。

言歸正傳,還是回到 Spring 的學習和麪試。關於 Spring,也許一整本書都無法完整涵蓋其內容,專欄裏我會有限地補充:

  •   Spring 的基礎機制。
  •   Spring 框架的涵蓋範圍。
  •   Spring AOP 自身設計的一些細節,前面第 24 講偏重於底層實現原理,這樣還不夠全面,畢竟不管是動態代理還是字節碼操縱,都還只是基礎,更需要 Spring 層面對切面編程的支持。

 

知識擴展

首先,我們先來看看 Spring 的基礎機制,至少你需要理解下面兩個基本方面。

  •   控制反轉(Inversion of Control),或者也叫依賴注入(Dependency Injection),廣泛應用於 Spring 框架之中,可以有效地改善了模塊之間的緊耦合問題。

從 Bean 創建過程可以看到,它的依賴關係都是由容器負責注入,具體實現方式包括帶參數的構造函數、setter 方法或者AutoWired方式實現。

  •   AOP,我們已經在前面接觸過這種切面編程機制,Spring 框架中的事務、安全、日誌等功能都依賴於 AOP 技術,下面我會進一步介紹。

 

第二,Spring 到底是指什麼?

我前面談到的 Spring,其實是狹義的Spring Framework,其內部包含了依賴注入、事件機制等核心模塊,也包括事務、O/R Mapping 等功能組成的數據訪問模塊,以及 Spring MVC 等 Web 框架和其他基礎組件。

廣義上的 Spring 已經成爲了一個龐大的生態系統,例如:

  •   Spring Boot,通過整合通用實踐,更加自動、智能的依賴管理等,Spring Boot 提供了各種典型應用領域的快速開發基礎,所以它是以應用爲中心的一個框架集合。
  •   Spring Cloud,可以看作是在 Spring Boot 基礎上發展出的更加高層次的框架,它提供了構建分佈式系統的通用模式,包含服務發現和服務註冊、分佈式配置管理、負載均衡、分佈式診斷等各種子系統,可以簡化微服務系統的構建。
  •   當然,還有針對特定領域的 Spring Security、Spring Data 等。


上面的介紹比較籠統,針對這麼多內容,如果將目標定得太過寬泛,可能就迷失在 Spring 生態之中,我建議還是深入你當前使用的模塊,如 Spring MVC。並且,從整體上把握主要前沿框架(如 Spring Cloud)的應用範圍和內部設計,至少要了解主要組件和具體用途,畢竟如何構建微服務等,已經逐漸成爲 Java 應用開發面試的熱點之一。

 

第三,我們來探討一下更多有關 Spring AOP 自身設計和實現的細節。

先問一下自己,我們爲什麼需要切面編程呢?

切面編程落實到軟件工程其實是爲了更好地模塊化,而不僅僅是爲了減少重複代碼。通過 AOP 等機制,我們可以把橫跨多個不同模塊的代碼抽離出來,讓模塊本身變得更加內聚,進而業務開發者可以更加專注於業務邏輯本身。從迭代能力上來看,我們可以通過切面的方式進行修改或者新增功能,這種能力不管是在問題診斷還是產品能力擴展中,都非常有用。

在之前的分析中,我們已經分析了 AOP Proxy 的實現原理,簡單回顧一下,它底層是基於 JDK 動態代理或者 cglib 字節碼操縱等技術,運行時動態生成被調用類型的子類等,並實例化代理對象,實際的方法調用會被代理給相應的代理對象。但是,這並沒有解釋具體在 AOP 設計層面,什麼是切面,如何定義切入點和切面行爲呢?

 

Spring AOP 引入了其他幾個關鍵概念:

  •   Aspect,通常叫作方面,它是跨不同 Java 類層面的橫切性邏輯。在實現形式上,既可以是 XML 文件中配置的普通類,也可以在類代碼中用“@Aspect”註解去聲明。在運行時,Spring 架會創建類似Advisor來指代它,其內部會包括切入的時機(Pointcut)和切入的動作(Advice)。
  •   Join Point,它是 Aspect 可以切入的特定點,在 Spring 裏面只有方法可以作爲 Join Point。
  •   Advice,它定義了切面中能夠採取的動作。如果你去看 Spring 源碼,就會發現 Advice、Join Point 並沒有定義在 Spring 自己的命名空間裏,這是因爲他們是源自AOP 聯盟,可以看作是 Java 工程師在 AOP 層面溝通的通用規範。


Java 核心類庫中同樣存在類似代碼,例如 Java 9 中引入的 Flow API 就是 Reactive Stream 規範的最小子集,通過這種方式,可以保證不同產品直接的無縫溝通,促進了良好實踐的推廣。

具體的 Spring Advice 結構請參考下面的示意圖。


其中,BeforeAdvice 和 AfterAdvice 包括它們的子接口是最簡單的實現。而 Interceptor 則是所謂的攔截器,用於攔截住方法(也包括構造器)調用事件,進而採取相應動作,所以 Interceptor 是覆蓋住整個方法調用過程的 Advice。通常將攔截器類型的 Advice 叫作 Around,在代碼中可以使用“@Around”來標記,或者在配置中使用“<aop:around>”。

如果從時序上來看,則可以參考下圖,理解具體發生的時機。

  •   Pointcut,它負責具體定義 Aspect 被應用在哪些 Join Point,可以通過指定具體的類名和方法名來實現,或者也可以使用正則表達式來定義條件。

 

你可以參看下面的示意圖,來進一步理解上面這些抽象在邏輯上的意義。

  •   Join Point 僅僅是可利用的機會。
  •   Pointcut 是解決了切面編程中的 Where 問題,讓程序可以知道哪些機會點可以應用某個切面動作。
  •   而 Advice 則是明確了切面編程中的 What,也就是做什麼;同時通過指定 Before、After 或者 Around,定義了 When,也就是什麼時候做。


在準備面試時,如果在實踐中使用過 AOP 是最好的,否則你可以選擇一個典型的 AOP 實例,理解具體的實現語法細節,因爲在面試考察中也許會問到這些技術細節。

如果你有興趣深入內部,最好可以結合 Bean 生命週期,理解 Spring 如何解析 AOP 相關的註解或者配置項,何時何地使用到動態代理等機制。爲了避免被龐雜的源碼弄暈,我建議你可以從比較精簡的測試用例作爲一個切入點,如CglibProxyTests。

另外,Spring 框架本身功能點非常多,AOP 並不是它所支持的唯一切面技術,它只能利用動態代理進行運行時編織,而不能進行編譯期的靜態編織或者類加載期編織。例如,在 Java 平臺上,我們可以使用 Java Agent 技術,在類加載過程中對字節碼進行操縱,比如修改或者替換方法實現等。在 Spring 體系中,如何做到類似功能呢?你可以使用 AspectJ,它具有更加全面的能力,當然使用也更加複雜。

今天我從一個常見的 Spring 面試題開始,淺談了 Spring 的基礎機制,探討了 Spring 生態範圍,並且補充分析了部分 AOP 
的設計細節,希望對你有所幫助。

 

一課一練

關於今天我們討論的題目你做到心中有數了嗎?今天的思考題是,請介紹一下 Spring 聲明式事務的實現機制,可以考慮將具體過程畫圖。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章