在企業應用軟件開發中,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 講](http://time.geekbang.org/column/article/8471)提到過的 Marker Interface 嗎,Aware 接口就是個典型應用例子,Bean 可以實現各種不同 Aware 的子接口,為容器以 Callback 形式注入依賴對象提供了統一入口。
言歸正傳,還是回到 Spring 的學習和面試。關于 Spring,也許一整本書都無法完整涵蓋其內容,專欄里我會有限地補充:
* Spring 的基礎機制。
* Spring 框架的涵蓋范圍。
* Spring AOP 自身設計的一些細節,前面[第 24 講](http://time.geekbang.org/column/article/10076)偏重于底層實現原理,這樣還不夠全面,畢竟不管是動態代理還是字節碼操縱,都還只是基礎,更需要 Spring 層面對切面編程的支持。
## 知識擴展
首先,我們先來看看 Spring 的基礎機制,至少你需要理解下面兩個基本方面。
* 控制反轉(Inversion of Control),或者也叫依賴注入(Dependency Injection),廣泛應用于 Spring 框架之中,可以有效地改善了模塊之間的緊耦合問題。
從 Bean 創建過程可以看到,它的依賴關系都是由容器負責注入,具體實現方式包括帶參數的構造函數、setter 方法或者[AutoWired](https://docs.spring.io/spring-framework/docs/5.0.3.RELEASE/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html)方式實現。
* AOP,我們已經在前面接觸過這種切面編程機制,Spring 框架中的事務、安全、日志等功能都依賴于 AOP 技術,下面我會進一步介紹。
第二,Spring 到底是指什么?
我前面談到的 Spring,其實是狹義的[Spring Framework](https://github.com/spring-projects/spring-framework/blob/67ea4b3a050af3db5545f58ff85a0d132ee91c2a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java),其內部包含了依賴注入、事件機制等核心模塊,也包括事務、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](https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/Advisor.java)來指代它,其內部會包括切入的時機(Pointcut)和切入的動作(Advice)。
* Join Point,它是 Aspect 可以切入的特定點,在 Spring 里面只有方法可以作為 Join Point。
* [](https://github.com/spring-projects/spring-framework/blob/67ea4b3a050af3db5545f58ff85a0d132ee91c2a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java)[Advice](https://github.com/spring-projects/spring-framework/blob/67ea4b3a050af3db5545f58ff85a0d132ee91c2a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java),它定義了切面中能夠采取的動作。如果你去看 Spring 源碼,就會發現 Advice、Join Point 并沒有定義在 Spring 自己的命名空間里,這是因為他們是源自[AOP 聯盟](http://aopalliance.sourceforge.net/),可以看作是 Java 工程師在 AOP 層面溝通的通用規范。
Java 核心類庫中同樣存在類似代碼,例如 Java 9 中引入的 Flow API 就是 Reactive Stream 規范的最小子集,通過這種方式,可以保證不同產品直接的無縫溝通,促進了良好實踐的推廣。
具體的 Spring Advice 結構請參考下面的示意圖。

其中,BeforeAdvice 和 AfterAdvice 包括它們的子接口是最簡單的實現。而 Interceptor 則是所謂的攔截器,用于攔截住方法(也包括構造器)調用事件,進而采取相應動作,所以 Interceptor 是覆蓋住整個方法調用過程的 Advice。通常將攔截器類型的 Advice 叫作 Around,在代碼中可以使用“@Around”來標記,或者在配置中使用“”。
如果從時序上來看,則可以參考下圖,理解具體發生的時機。

* Pointcut,它負責具體定義 Aspect 被應用在哪些 Join Point,可以通過指定具體的類名和方法名來實現,或者也可以使用正則表達式來定義條件。
你可以參看下面的示意圖,來進一步理解上面這些抽象在邏輯上的意義。

* Join Point 僅僅是可利用的機會。
* Pointcut 是解決了切面編程中的 Where 問題,讓程序可以知道哪些機會點可以應用某個切面動作。
* 而 Advice 則是明確了切面編程中的 What,也就是做什么;同時通過指定 Before、After 或者 Around,定義了 When,也就是什么時候做。
在準備面試時,如果在實踐中使用過 AOP 是最好的,否則你可以選擇一個典型的 AOP 實例,理解具體的實現語法細節,因為在面試考察中也許會問到這些技術細節。
如果你有興趣深入內部,最好可以結合 Bean 生命周期,理解 Spring 如何解析 AOP 相關的注解或者配置項,何時何地使用到動態代理等機制。為了避免被龐雜的源碼弄暈,我建議你可以從比較精簡的測試用例作為一個切入點,如[CglibProxyTests](https://github.com/spring-projects/spring-framework/blob/da80502ea6ed4860f5bf7b668300644cdfe3bb5a/spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java)。
另外,Spring 框架本身功能點非常多,AOP 并不是它所支持的唯一切面技術,它只能利用動態代理進行運行時編織,而不能進行編譯期的靜態編織或者類加載期編織。例如,在 Java 平臺上,我們可以使用 Java Agent 技術,在類加載過程中對字節碼進行操縱,比如修改或者替換方法實現等。在 Spring 體系中,如何做到類似功能呢?你可以使用 AspectJ,它具有更加全面的能力,當然使用也更加復雜。
今天我從一個常見的 Spring 面試題開始,淺談了 Spring 的基礎機制,探討了 Spring 生態范圍,并且補充分析了部分 AOP 的設計細節,希望對你有所幫助。
## 一課一練
關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,請介紹一下 Spring 聲明式事務的實現機制,可以考慮將具體過程畫圖。
- 前言
- 開篇詞
- 開篇詞 -以面試題為切入點,有效提升你的Java內功
- 模塊一 Java基礎
- 第1講 談談你對Java平臺的理解?
- 第2講 Exception和Error有什么區別?
- 第3講 談談final、finally、 finalize有什么不同?
- 第4講 強引用、軟引用、弱引用、幻象引用有什么區別?
- 第5講 String、StringBuffer、StringBuilder有什么區別?
- 第6講 動態代理是基于什么原理?
- 第7講 int和Integer有什么區別?
- 第8講 對比Vector、ArrayList、LinkedList有何區別?
- 第9講 對比Hashtable、HashMap、TreeMap有什么不同?
- 第10講 如何保證集合是線程安全的? ConcurrentHashMap如何實現高效地線程安全?
- 第11講 Java提供了哪些IO方式? NIO如何實現多路復用?
- 第12講 Java有幾種文件拷貝方式?哪一種最高效?
- 第13講 談談接口和抽象類有什么區別?
- 第14講 談談你知道的設計模式?
- 模塊二 Java進階
- 第15講 synchronized和ReentrantLock有什么區別呢?
- 第16講 synchronized底層如何實現?什么是鎖的升級、降級?
- 第17講 一個線程兩次調用start()方法會出現什么情況?
- 第18講 什么情況下Java程序會產生死鎖?如何定位、修復?
- 第19講 Java并發包提供了哪些并發工具類?
- 第20講 并發包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么區別?
- 第21講 Java并發類庫提供的線程池有哪幾種? 分別有什么特點?
- 第22講 AtomicInteger底層實現原理是什么?如何在自己的產品代碼中應用CAS操作?
- 第23講 請介紹類加載過程,什么是雙親委派模型?
- 第24講 有哪些方法可以在運行時動態生成一個Java類?
- 第25講 談談JVM內存區域的劃分,哪些區域可能發生OutOfMemoryError?
- 第26講 如何監控和診斷JVM堆內和堆外內存使用?
- 第27講 Java常見的垃圾收集器有哪些?
- 第28講 談談你的GC調優思路?
- 第29講 Java內存模型中的happen-before是什么?
- 第30講 Java程序運行在Docker等容器環境有哪些新問題?
- 模塊三 Java安全基礎
- 第31講 你了解Java應用開發中的注入攻擊嗎?
- 第32講 如何寫出安全的Java代碼?
- 模塊四 Java性能基礎
- 第33講 后臺服務出現明顯“變慢”,談談你的診斷思路?
- 第34講 有人說“Lambda能讓Java程序慢30倍”,你怎么看?
- 第35講 JVM優化Java代碼時都做了什么?
- 模塊五 Java應用開發擴展
- 第36講 談談MySQL支持的事務隔離級別,以及悲觀鎖和樂觀鎖的原理和應用場景?
- 第37講 談談Spring Bean的生命周期和作用域?
- 第38講 對比Java標準NIO類庫,你知道Netty是如何實現更高性能的嗎?
- 第39講 談談常用的分布式ID的設計方案?Snowflake是否受冬令時切換影響?
- 周末福利
- 周末福利 談談我對Java學習和面試的看法
- 周末福利 一份Java工程師必讀書單
- 結束語
- 結束語 技術沒有終點
- 結課測試 Java核心技術的這些知識,你真的掌握了嗎?