在企業應用軟件開發中,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 聲明式事務的實現機制,可以考慮將具體過程畫圖。