該系列文章是本人在學習 Spring 的過程中總結下來的,里面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring 源碼分析 GitHub 地址 進行閱讀
Spring 版本:5.1.14.RELEASE
該系列其他文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》
1. 什么是 Spring Framework ?
官方文檔:
Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs.
這個問題很難回答,在 Spring 官方文檔中的描述也很抽象,答案在于你對 Spring 是如何理解的,想必每個人都有自己的回答方式,以下是我個人對于 Spring 的理解:
整個 Spring 生態(tài)在涉及到 Java 的項目中被廣泛應用,它提供了非常多的組件,能夠讓你在開發(fā) Java 應用的過程變得更加容易,彈性地支持其他軟件框架,可以比作一個“排插座”,其他軟件框架簡單地“插上”即可結合 Spring 一起使用,給開發(fā)人員帶來了非常多的便利。Spring 底層 IoC 容器的設計實現(xiàn)也是非常完美的,在整個 Spring 應用上下文的生命周期和 Spring Bean 的生命周期的許多階段提供了相應的擴展點,供開發(fā)者自行擴展,使得框架非常的靈活。
2. Spring Framework 的優(yōu)勢和不足?
優(yōu)勢:Spring 面向模塊進行開發(fā),根據(jù)不同的功能進行劃分,根據(jù)需求引入對應的模塊即可,對于開發(fā)人員非常友好。例如 Spring IoC 容器,將我們的 Java 對象作為 Spring Bean 進行管理,管理著 Bean 的整個生命周期;Spring MVC 提供“模型-視圖-控制器”(Model-View-Controller)架構和隨時可用的組件,用于開發(fā)靈活且松散耦合的 Web 應用程序;Spring AOP 提供面向切面編程的接口,可以很方便的使用;還有許多其他的功能模塊,就不一一講述了。
不足:整個 Spring 體系比較復雜,對于開發(fā)人員需要一定的學習成本,遇到相關問題時需要對底層實現(xiàn)有充分的了解,這也就需要開發(fā)人員投入更多的時間和精力去學習。當然,如今 Spring 體系整合了 Java 生態(tài)非常多的東西,為開發(fā)人員帶來的便利遠大于這些不足,我覺得是有必要對 Spring 進行充分的學習,去了解 Spring 的貢獻者們的設計思路,對自身也會有很大的提升,從中可以學習到許多的東西。
3. 你對 IoC 的理解?
Inversion of Control(IoC)是面向對象中的一種編程思想或原則??梢韵然氐絺鹘y(tǒng)方式,當我依賴一個對象,我需要主動去創(chuàng)建它并進行屬性賦值,然后我才能去使用這個對象。對于 IoC 這種方式來說,它使得對象或者組件的創(chuàng)建更為透明,你不需要過多地關注細節(jié),如創(chuàng)建對象、屬性賦值,這些工作交都由 IoC 容器來完成,已達到解耦的目的。
IoC 控制反轉,簡單來理解其實就是把獲取依賴對象的方式,交由 IoC 容器來實現(xiàn),由“主動拉取”變?yōu)椤氨粍荧@取”。
4. 為什么需要 IoC ?
實際上,IoC 是為了屏蔽構造細節(jié)。例如 new 出來的對象的生命周期中的所有細節(jié)對于使用端都是知道的,如果在沒有 IoC 容器的前提下,IoC 是沒有存在的必要,不過在復雜的系統(tǒng)中,我們的應用更應該關注的是對象的運用,而非它的構造和初始化等細節(jié)。
5. IoC 和 DI 的區(qū)別?
DI 依賴注入不完全等同于 IoC,更應該說 DI 依賴注入是 IoC 的一種實現(xiàn)方式或策略。
依賴查找和依賴注入都是 IoC 的實現(xiàn)策略。依賴查找就是在應用程序里面主動調用 IoC 容器提供的接口去獲取對應的 Bean 對象,而依賴注入是在 IoC 容器啟動或者初始化的時候,通過構造器、字段、setter 方法或者接口等方式注入依賴。依賴查找相比于依賴注入對于開發(fā)者而言更加繁瑣,具有一定的代碼入侵性,需要借助 IoC 容器提供的接口,所以我們總是強調后者。依賴注入在 IoC 容器中的實現(xiàn)也是調用相關的接口獲取 Bean 對象,只不過這些工作都是在 IoC 容器啟動時由容器幫你實現(xiàn)了,在應用程序中我們通常很少主動去調用接口獲取 Bean 對象。
6. IoC 容器的職責?
主要有以下職責:
IoC 容器有非常多,例如 JDK 的 Java Beans,Java EE 的 EJB,Apache Avalon,Google guice,Spring,其中 Spring 是最成功的的一個,目前被廣泛應用。
其中 Spring 借鑒了 JDK 的 Java Beans 設計思想,也使用到其中相關類(例如 java.beans.PropertyEditor 屬性編輯器),開發(fā)過 IDE 的 GUI 界面的伙伴應該對 Java Beans 比較熟悉。
7. 什么是 Spring IoC 容器?
Spring 框架是一個 IoC 容器的實現(xiàn),DI 依賴注入是它的實現(xiàn)的一個原則,提供依賴查找和依賴注入兩種依賴處理,管理著 Bean 的生命周期。Spring 還提供了 AOP 抽象、事件抽象、事件監(jiān)聽機制、SPI 機制、強大的第三方整合、易測試性等其他特性。
8. 構造器注入和 Setter 注入
構造器注入:通過構造器的參數(shù)注入相關依賴對象
Setter 注入:通過 Setter 方法注入依賴對象,也可以理解為字段注入
對于兩種注入方式的看法:
-
構造器注入可以避免一些尷尬的問題,比如說狀態(tài)不確定性地被修改,在初始化該對象時才會注入依賴對象,一定程度上保證了 Bean 初始化后就是不變的對象,這樣對于我們的程序和維護性都會帶來更多的便利;
-
構造器注入不允許出現(xiàn)循環(huán)依賴,因為它要求被注入的對象都是成熟態(tài),保證能夠實例化,而 Setter 注入或字段注入沒有這樣的要求;
-
構造器注入可以保證依賴的對象能夠有序的被注入,而 Setter 注入或字段注入底層是通過反射機制進行注入,無法完全保證注入的順序;
-
如果構造器注入出現(xiàn)比較多的依賴導致代碼不夠優(yōu)雅,我們應該考慮自身代碼的設計是否存在問題,是否需要重構代碼結構。
除了上面的注入方式外,Spring 還提供了接口回調注入,通過實現(xiàn) Aware 接口(例如 BeanNameAware、ApplicationContextAware)可以注入相關對象,Spring 在初始化這類 Bean 時會調用其 setXxx 方法注入對象,例如注入 beanName、ApplicationContext
9. BeanFactory 和 ApplicationContext 誰才是 Spring IoC 容器?
BeanFactory 是 Spring 底層 IoC 容器,ApplicationContext 是 BeanFactory 的子接口,是 BeanFactory 的一個超集,提供 IoC 容器以外更多的功能。ApplicationContext 除了扮演 IoC 容器角色,還提供了這些企業(yè)特性:面向切面(AOP)、配置元信息、資源管理、事件機制、國際化、注解、Environment 抽象等。我們一般稱 ApplicationContext 是 Spring 應用上下文,BeanFactory 為 Spring 底層 IoC 容器。
10. Spring Bean 的生命周期?
生命周期:
-
Spring Bean 元信息配置階段,可以通過面向資源(XML 或 Properties)、面向注解、面向 API 進行配置
-
Spring Bean 元信息解析階段,對上一步的配置元信息進行解析,解析成 BeanDefinition 對象,該對象包含定義 Bean 的所有信息,用于實例化一個 Spring Bean
-
Spring Bean 元信息注冊階段,將 BeanDefinition 配置元信息 保存至 BeanDefinitionRegistry 的 ConcurrentHashMap 集合中
-
Spring BeanDefinition 合并階段,定義的 Bean 可能存在層次性關系,則需要將它們進行合并,存在相同配置則覆蓋父屬性,最終生成一個 RootBeanDefinition 對象
-
Spring Bean 的實例化階段,首先的通過類加載器加載出一個 Class 對象,通過這個 Class 對象的構造器創(chuàng)建一個實例對象,構造器注入在此處會完成。在實例化階段 Spring 提供了實例化前后兩個擴展點(InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation、postProcessAfterInstantiation 方法)
-
Spring Bean 屬性賦值階段,在 Spring 實例化后,需要對其相關屬性進行賦值,注入依賴的對象。首先獲取該對象所有屬性與屬性值的映射,可能已定義,也可能需要注入,在這里都會進行賦值(反射機制)。提示一下,依賴注入的實現(xiàn)通過 CommonAnnotationBeanPostProcessor(@Resource、@PostConstruct、@PreDestroy)和 AutowiredAnnotationBeanPostProcessor(@Autowired、@Value)兩個處理器實現(xiàn)的。
-
Aware 接口回調階段,如果 Spring Bean 是 Spring 提供的 Aware 接口類型(例如 BeanNameAware、ApplicationContextAware),這里會進行接口的回調,注入相關對象(例如 beanName、ApplicationContext)
-
Spring Bean 初始化階段,這里會調用 Spring Bean 配置的初始化方法,執(zhí)行順序:@PostConstruct 標注方法、實現(xiàn) InitializingBean 接口的 afterPropertiesSet() 方法、自定義初始化方法。在初始化階段 Spring 提供了初始化前后兩個擴展點(BeanPostProcessor 的 postProcessBeforeInitialization、postProcessAfterInitialization 方法)
-
Spring Bean 初始化完成階段,在所有的 Bean(不是抽象、單例模式、不是懶加載方式)初始化后,Spring 會再次遍歷所有初始化好的單例 Bean 對象,如果是 SmartInitializingSingleton 類型則調用其 afterSingletonsInstantiated() 方法,這里也屬于 Spring 提供的一個擴展點
-
Spring Bean 銷毀階段,當 Spring 應用上下文關閉或者你主動銷毀某個 Bean 時則進入 Spring Bean 的銷毀階段,執(zhí)行順序:@PreDestroy 注解的銷毀動作、實現(xiàn)了 DisposableBean 接口的 Bean 的回調、destroy-method 自定義的銷毀方法。這里也有一個銷毀前階段,也屬于 Spring 提供的一個擴展點,@PreDestroy 就是基于這個實現(xiàn)的
-
Spring 垃圾收集(GC)
總結:
-
上面 1、2、3 屬于 BeanDefinition 配置元信息階段,算是 Spring Bean 的前身,想要生成一個 Bean 對象,需要將這個 Bean 的所有信息都定義好;
-
其中 4、5 屬于實例化階段,想要生成一個 Java Bean 對象,那么肯定需要根據(jù) Bean 的元信息先實例化一個對象;
-
接下來的 6 屬于屬性賦值階段,實例化后的對象還是一個空對象,我們需要根據(jù) Bean 的元信息對該對象的所有屬性進行賦值;
-
后面的 7、8 、9 屬于初始化階段,在 Java Bean 對象生成后,可能需要對這個對象進行相關初始化工作才予以使用;
-
最后面的 10、11 屬于銷毀階段,當 Spring 應用上下文關閉或者主動銷毀某個 Bean 時,可能需要對這個對象進行相關銷毀工作,最后等待 JVM 進行回收。
11. BeanDefinition 是什么?
BeanDefinition 是 Spring Bean 的“前身”,其內部包含了初始化一個 Bean 的所有元信息,在 Spring 初始化一個 Bean 的過程中需要根據(jù)該對象生成一個 Bean 對象并進行一系列的初始化工作。
12. Spring 內建的 Bean 作用域有哪些?
| 來源 |
說明 |
| singleton |
默認 Spring Bean 作用域,一個 BeanFactory 有且僅有一個實例 |
| prototype |
原型作用域,每次依賴查找和依賴注入生成新 Bean 對象 |
| request |
將 Spring Bean 存儲在 ServletRequest 上下文中 |
| session |
將 Spring Bean 存儲在 HttpSession 中 |
| application |
將 Spring Bean 存儲在 ServletContext 中 |
13. BeanPostProcessor 與 BeanFactoryPostProcessor 的區(qū)別?
BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回調,允許對關心的 Bean 進行擴展,甚至是替換,其相關子類也提供 Spring Bean 生命周期中其他階段的回調。
BeanFactoryPostProcessor 提供 Spring BeanFactory(底層 IoC 容器)的生命周期的回調,用于擴展 BeanFactory(實際為 ConfigurableListableBeanFactory),BeanFactoryPostProcessor 必須由 Spring ApplicationContext 執(zhí)行,BeanFactory 無法與其直接交互。
14. 依賴注入和依賴查找的來源是否相同?
否,依賴查找的來源僅限于 Spring BeanDefinition 以及單例對象,而依賴注入的來源還包括 Resolvable Dependency(Spring 應用上下文定義的可已處理的注入對象,例如注入 BeanFactory 注入的是 ApplicationContext 對象)以及 @Value 所標注的外部化配置
15. 如何基于 Extensible XML authoring 擴展 Spring XML 元素?
Spring XML 擴展
-
編寫 XML Schema 文件(XSD 文件):定義 XML 結構
-
自定義 NamespaceHandler 實現(xiàn):定義命名空間的處理器
-
自定義 BeanDefinitionParser 實現(xiàn):綁定命名空間下不同的 XML 元素與其對應的解析器
-
注冊 XML 擴展(META-INF/spring.handlers 文件):命名空間與命名空間處理器的映射
-
編寫 Spring Schema 資源映射文件(META-INF/spring.schemas 文件):XML Schema 文件通常定義為網(wǎng)絡的形式,在無網(wǎng)的情況下無法訪問,所以一般在本地的也有一個 XSD 文件,可通過編寫 spring.schemas 文件,將網(wǎng)絡形式的 XSD 文件與本地的 XSD 文件進行映射,這樣會優(yōu)先從本地獲取對應的 XSD 文件
Mybatis 對 Spring 的集成項目中的 <mybatis:scan /> 標簽就是這樣實現(xiàn)的,可以參考:NamespaceHandler、MapperScannerBeanDefinitionParser、XSD 等文件
具體實現(xiàn)邏輯參考后續(xù)《解析自定義標簽(XML 文件)》一文
16. Java 泛型擦寫發(fā)生在編譯時還是運行時?
運行時。編譯時,泛型參數(shù)類型還是存在的,運行時會忽略。
17. 簡述 Spring 事件機制原理?
主要有以下幾個角色:
-
Spring 事件 - org.springframework.context.ApplicationEvent,實現(xiàn)了 java.util.EventListener 接口
-
Spring 事件監(jiān)聽器 - org.springframework.context.ApplicationListener,實現(xiàn)了 java.util.EventObject 類
-
Spring 事件發(fā)布器 - org.springframework.context.ApplicationEventPublisher
-
Spring 事件廣播器 - org.springframework.context.event.ApplicationEventMulticaster
Spring 內建的事件:
- ContextRefreshedEvent:Spring 應用上下文就緒事件
- ContextStartedEvent:Spring 應用上下文啟動事件
- ContextStoppedEvent:Spring 應用上下文停止事件
- ContextClosedEvent:Spring 應用上下文關閉事件
Spring 應用上下文就是一個 ApplicationEventPublisher 事件發(fā)布器,其內部有一個 ApplicationEventMulticaster 事件廣播器(被觀察者),里面保存了所有的 ApplicationListener 事件監(jiān)聽器(觀察者)。Spring 應用上下文發(fā)布一個事件后會通過 ApplicationEventMulticaster 事件廣播器進行廣播,能夠處理該事件類型的 ApplicationListener 事件監(jiān)聽器則進行處理。
18. @EventListener 的工作原理?
@EventListener 用于標注在方法上面,該方法則可以用來處理 Spring 的相關事件。
Spring 內部有一個處理器 EventListenerMethodProcessor,它實現(xiàn)了 SmartInitializingSingleton 接口,在所有的 Bean(不是抽象、單例模式、不是懶加載方式)初始化后,Spring 會再次遍歷所有初始化好的單例 Bean 對象時會執(zhí)行該處理器對該 Bean 進行處理。在 EventListenerMethodProcessor 中會對標注了 @EventListener 注解的方法進行解析,如果符合條件則生成一個 ApplicationListener 事件監(jiān)聽器并注冊。
19. Spring 提供的注解有哪些?
核心注解有以下:
| Spring 注解 |
場景說明 |
起始版本 |
| @Repository |
數(shù)據(jù)倉儲模式注解 |
2.0 |
| @Component |
通用組件模式注解 |
2.5 |
| @Service |
服務模式注解 |
2.5 |
| @Controller |
Web 控制器模式注解 |
2.5 |
| @Configuration |
配置類模式注解 |
3.0 |
Spring 模式注解都是 @Component 的派生注解,Spring 為什么會提供這么多派生注解?
@Component 注解是一個通用組件注解,標注這個注解后表明你需要將其作為一個 Spring Bean 進行使用,而其他注解都有各自的作用,例如 @Controller 及其派生注解用于 Web 場景下處理 HTTP 請求,@Configuration 注解通常會將這個 Spring Bean 作為一個配置類,也會被 CGLIB 提供,幫助實現(xiàn) AOP 特性。這也是領域驅動設計中的一種思想。
領域驅動設計:Domain-Driven Design,簡稱 DDD。過去系統(tǒng)分析和系統(tǒng)設計都是分離的,這樣割裂的結果導致需求分析的結果無法直接進行設計編程,而能夠進行編程運行的代碼卻扭曲需求,導致客戶運行軟件后才發(fā)現(xiàn)很多功能不是自己想要的,而且軟件不能快速跟隨需求變化。DDD 則打破了這種隔閡,提出了領域模型概念,統(tǒng)一了分析和設計編程,使得軟件能夠更靈活快速跟隨需求變化。
| Spring 注解 |
場景說明 |
起始版本 |
| @ImportResource |
替換 XML 元素 <import> |
2.5 |
| @Import |
導入 Configuration 類 |
2.5 |
| @ComponentScan |
掃描指定 package 下標注 Spring 模式注解的類 |
3.1 |
| Spring 注解 |
場景說明 |
起始版本 |
| @Autowired |
Bean 依賴注入,支持多中依賴查找方式 |
2.5 |
| @Qualifier |
細粒度的 @Autowired 依賴查找 |
2.5 |
| Spring 注解 |
場景說明 |
起始版本 |
| @EnableWebMvc |
啟動整個 Web MVC 模塊 |
3.1 |
| @EnableTransactionManagement |
啟動整個事務管理模塊 |
3.1 |
| @EnableCaching |
啟動整個緩存模塊 |
3.1 |
| @EnableAsync |
啟動整個異步處理模塊 |
3.1 |
@Enable 模塊驅動是以 @Enable 為前綴的注解驅動編程模型。所謂“模塊”是指具備相同領域的功能組件集合,組合所形成一個獨立的單元。比如 Web MVC 模塊、AspectJ 代理模塊、Caching(緩存)模塊、JMX(Java 管理擴展)模塊、Async(異步處理)模塊等。
這類注解底層原理就是通過 @Import 注解導入相關類(Configuration Class、 ImportSelector 接口實現(xiàn)、ImportBeanDefinitionRegistrar 接口實現(xiàn)),來實現(xiàn)引入某個模塊或功能。
| Spring 注解 |
場景說明 |
起始版本 |
| @Conditional |
條件限定,引入某個 Bean |
4.0 |
| @Profile |
從 Spring 4.0 開始,@Profile 基于 @Conditional 實現(xiàn),限定 Bean 的 Spring 應用環(huán)境 |
4.0 |
20. 簡述 Spring Environment ?
統(tǒng)一 Spring 配置屬性的存儲,用于占位符處理和類型轉換,還支持更豐富的配置屬性源(PropertySource);
通過 Environment Profiles 信息,幫助 Spring 容器提供條件化地裝配 Bean。
21. Environment 完整的生命周期是怎樣的?
在 Spring 應用上下文進入刷新階段之前,可以通過 setEnvironment(Environment) 方法提前設置 Environment 對象,在刷新階段如果沒有 Environment 對象則會創(chuàng)建一個新的 Environment 對象
22. Spring 應用上下文的生命周期?
Spring 應用上下文就是 ApplicationContext,生命周期主要體現(xiàn)在 org.springframework.context.support.AbstractApplicationContext#refresh() 方法中,大致如下:
-
Spring 應用上下文啟動準備階段,設置相關屬性,例如啟動時間、狀態(tài)標識、Environment 對象
-
BeanFactory 初始化階段,初始化一個 BeanFactory 對象,加載出 BeanDefinition 們;設置相關組件,例如 ClassLoader 類加載器、表達式語言處理器、屬性編輯器,并添加幾個 BeanPostProcessor 處理器
-
BeanFactory 后置處理階段,主要是執(zhí)行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的處理,對 BeanFactory 和 BeanDefinitionRegistry 進行后置處理,這里屬于 Spring 應用上下文的一個擴展點
-
BeanFactory 注冊 BeanPostProcessor 階段,主要初始化 BeanPostProcessor 類型的 Bean(依賴查找),在 Spring Bean 生命周期的許多節(jié)點都能見到該類型的處理器
-
初始化內建 Bean,初始化當前 Spring 應用上下文的 MessageSource 對象(國際化文案相關)、ApplicationEventMulticaster 事件廣播器對象、ThemeSource 對象
-
Spring 事件監(jiān)聽器注冊階段,主要獲取到所有的 ApplicationListener 事件監(jiān)聽器進行注冊,并廣播早期事件
-
BeanFactory 初始化完成階段,主要是初始化所有還未初始化的 Bean(不是抽象、單例模式、不是懶加載方式)
-
Spring 應用上下文刷新完成階段,清除當前 Spring 應用上下文中的緩存,例如通過 ASM(Java 字節(jié)碼操作和分析框架)掃描出來的元數(shù)據(jù),并發(fā)布上下文刷新事件
-
Spring 應用上下文啟動階段,需要主動調用 AbstractApplicationContext#start() 方法,會調用所有 Lifecycle 的 start() 方法,最后會發(fā)布上下文啟動事件
-
Spring 應用上下文停止階段,需要主動調用 AbstractApplicationContext#stop() 方法,會調用所有 Lifecycle 的 stop() 方法,最后會發(fā)布上下文停止事件
-
Spring 應用上下文關閉階段,發(fā)布當前 Spring 應用上下文關閉事件,銷毀所有的單例 Bean,關閉底層 BeanFactory 容器;注意這里會有一個鉤子函數(shù)(Spring 向 JVM 注冊的一個關閉當前 Spring 應用上下文的線程),當 JVM “關閉” 時,會觸發(fā)這個線程的運行
總結:
-
上面的 1、2、3、4、5、6、7、8 都屬于 Sping 應用上下文的刷新階段,完成了 Spring 應用上下文一系列的初始化工作;
-
9 屬于 Spring 應用上下文啟動階段,和 Lifecycle 生命周期對象相關,會調用這些對象的 start() 方法,最后發(fā)布上下文啟動事件;
-
10 屬于 Spring 應用上下文停止階段,和 Lifecycle 生命周期對象相關,會調用這些對象的 stop() 方法,最后發(fā)布上下文停止事件;
-
11 屬于 Spring 應用上下文關閉階段,發(fā)布上下文關閉事件,銷毀所有的單例 Bean,關閉底層 BeanFactory 容器。
23. Spring 應用上下文生命周期有哪些階段?
參考Spring 應用上下文的生命周期:
- 刷新階段 - ConfigurableApplicationContext#refresh()
- 啟動階段 - ConfigurableApplicationContext#start()
- 停止階段 - ConfigurableApplicationContext#stop()
- 關閉階段 - ConfigurableApplicationContext#close()
24. 簡述 ObjectFactory?
ObjectFactory(或 ObjectProvider) 可關聯(lián)某一類型的 Bean,僅提供一個 getObject() 方法用于返回目標 Bean 對象,ObjectFactory 對象被依賴注入或依賴查找時并未實時查找到關聯(lián)類型的目標 Bean 對象,在調用 getObject() 方法才會依賴查找到目標 Bean 對象。
根據(jù) ObjectFactory 的特性,可以說它提供的是延遲依賴查找。通過這一特性在 Spring 處理循環(huán)依賴(字段注入)的過程中就使用到了 ObjectFactory,在某個 Bean 還沒有完全初始化好的時候,會先緩存一個 ObjectFactory 對象(調用其 getObject() 方法可返回當前正在初始化的 Bean 對象),如果初始化的過程中依賴的對象又依賴于當前 Bean,會先通過緩存的 ObjectFactory 對象獲取到當前正在初始化的 Bean,這樣一來就解決了循環(huán)依賴的問題。
注意這里是延遲依賴查找而不是延遲初始化,ObjectFactory 無法決定是否延遲初始化,而需要通過配置 Bean 的 lazy 屬性來決定這個 Bean 對象是否需要延遲初始化,非延遲初始化的 Bean 在 Spring 應用上下文刷新過程中就會初始化。
提示:如果是 ObjectFactory(或 ObjectProvider)類型的 Bean,在被依賴注入或依賴查找時返回的是 DefaultListableBeanFactory#DependencyObjectProvider 私有內部類,實現(xiàn)了 ObjectProvider<T> 接口,關聯(lián)的類型為 Object。
25. 簡述 FactoryBean?
FactoryBean 關聯(lián)一個 Bean 對象,提供了一個 getObject() 方法用于返回這個目標 Bean 對象,F(xiàn)actoryBean 對象在被依賴注入或依賴查找時,實際得到的 Bean 就是通過 getObject() 方法獲取到的目標類型的 Bean 對象。如果想要獲取 FactoryBean 本身這個對象,在 beanName 前面添加 & 即可獲取。
我們可以通過 FactoryBean 幫助實現(xiàn)復雜的初始化邏輯,例如在 Spring 繼集成 MyBatis 的項目中,Mapper 接口沒有實現(xiàn)類是如何被注入的?其實 Mapper 接口就是一個 FactoryBean 對象,當你注入該接口時,實際的到的就是其 getObject() 方法返回的一個代理對象,關于數(shù)據(jù)庫的操作都是通過該代理對象來完成。
26. ObjectFactory、FactoryBean 和 BeanFactory 的區(qū)別?
根據(jù)其名稱可以知道其字面意思分別是:對象工廠,工廠 Bean
ObjectFactory、FactoryBean 和 BeanFactory 均提供依賴查找的能力。
-
ObjectFactory 提供的是延遲依賴查找,想要獲取某一類型的 Bean,需要調用其 getObject() 方法才能依賴查找到目標 Bean 對象。ObjectFactory 就是一個對象工廠,想要獲取該類型的對象,需要調用其 getObject() 方法生產一個對象。
-
FactoryBean 不提供延遲性,在被依賴注入或依賴查找時,得到的就是通過 getObject() 方法拿到的實際對象。FactoryBean 關聯(lián)著某個 Bean,可以說在 Spring 中它就是某個 Bean 對象,無需我們主動去調用 getObject() 方法,如果想要獲取 FactoryBean 本身這個對象,在 beanName 前面添加 & 即可獲取。
-
BeanFactory 則是 Spring 底層 IoC 容器,里面保存了所有的單例 Bean,ObjectFactory 和 FactoryBean 自身不具備依賴查找的能力,能力由 BeanFactory 輸出。
27. @Bean 的處理流程是怎樣的?
Spring 應用上下文生命周期,在 BeanDefinition(@Component 注解、XML 配置)的加載完后,會執(zhí)行所有 BeanDefinitionRegistryPostProcessor 類型的處理器,Spring 內部有一個 ConfigurationClassPostProcessor 處理器,它會對所有的配置類進行處理,解析其內部的注解(@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean),其中 @Bean 注解標注的方法會生成對應的 BeanDefinition 對象并注冊。詳細步驟可查看后續(xù)文章。
28. BeanFactory 是如何處理循環(huán)依賴?
前言,下面的“循環(huán)依賴”換成“循環(huán)依賴注入”比較合適,在 Spring 中通過 depends-on 配置的依賴對象如果出現(xiàn)循環(huán)依賴會拋出異常
說明:這里的循環(huán)依賴指的是單例模式下的 Bean 字段注入時出現(xiàn)的循環(huán)依賴。構造器注入對于 Spring 無法自動解決(應該考慮代碼設計是否有問題),可通過延遲初始化來處理。Spring 只解決單例模式下的循環(huán)依賴。
在 Spring 底層 IoC 容器 BeanFactory 中處理循環(huán)依賴的方法主要借助于以下 3 個 Map 集合:
singletonObjects(一級 Map),里面保存了所有已經(jīng)初始化好的單例 Bean,也就是會保存 Spring IoC 容器中所有單例的 Spring Bean;
earlySingletonObjects(二級 Map),里面會保存從 三級 Map 獲取到的正在初始化的 Bean
singletonFactories(三級 Map),里面保存了正在初始化的 Bean 對應的 ObjectFactory 實現(xiàn)類,調用其 getObject() 方法返回正在初始化的 Bean 對象(僅實例化還沒完全初始化好),如果存在則將獲取到的 Bean 對象并保存至 二級 Map,同時從當前 三級 Map 移除該 ObjectFactory 實現(xiàn)類。
當通過 getBean 依賴查找時會首先依次從上面三個 Map 獲取,存在則返回,不存在則進行初始化,這三個 Map 是處理循環(huán)依賴的關鍵。
例如兩個 Bean 出現(xiàn)循環(huán)依賴,A 依賴 B,B 依賴 A;當我們去依賴查找 A,在實例化后初始化前會先生成一個 ObjectFactory 對象(可獲取當前正在初始化 A)保存在上面的 singletonFactories 中,初始化的過程需注入 B;接下來去查找 B,初始 B 的時候又要去注入 A,又去查找 A ,由于可以通過 singletonFactories 直接拿到正在初始化的 A,那么就可以完成 B 的初始化,最后也完成 A 的初始化,這樣就避免出現(xiàn)循環(huán)依賴。
問題一:為什么需要上面的 二級 Map ?
因為通過 三級 Map獲取 Bean 會有相關 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 的處理,避免重復處理,處理后返回的可能是一個代理對象
例如在循環(huán)依賴中一個 Bean 可能被多個 Bean 依賴, A -> B(也依賴 A) -> C -> A,當你獲取 A 這個 Bean 時,后續(xù) B 和 C 都要注入 A,沒有上面的 二級 Map的話,三級 Map 保存的 ObjectFactory 實現(xiàn)類會被調用兩次,會重復處理,可能出現(xiàn)問題,這樣做在性能上也有所提升
問題二:為什么不直接調用這個 ObjectFactory#getObject() 方法放入 二級Map 中,而需要上面的 三級 Map?
對于不涉及到 AOP 的 Bean 確實可以不需要 singletonFactories(三級 Map),但是 Spring AOP 就是 Spring 體系中的一員,如果沒有singletonFactories(三級 Map),意味著 Bean 在實例化后就要完成 AOP 代理,這樣違背了 Spring 的設計原則。Spring 是通過 AnnotationAwareAspectJAutoProxyCreator 這個后置處理器在完全創(chuàng)建好 Bean 后來完成 AOP 代理,而不是在實例化后就立馬進行 AOP 代理。如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給 Bean 先創(chuàng)建代理對象,但是在沒有出現(xiàn)循環(huán)依賴的情況下,設計之初就是讓 Bean 在完全創(chuàng)建好后才完成 AOP 代理。
29. Spring 中幾種初始化方法的執(zhí)行順序?
有以下初始化方式:
-
Aware 接口:實現(xiàn)了 Spring 提供的相關 XxxAware 接口,例如 BeanNameAware、ApplicationContextAware,其 setXxx 方法會被回調,可以注入相關對象
-
@PostConstruct 注解:該注解是 JSR-250 的標準注解,Spring 會調用該注解標注的方法
-
InitializingBean 接口:實現(xiàn)了該接口,Spring 會調用其 afterPropertiesSet() 方法
-
自定義初始化方法:通過 init-method 指定的方法會被調用
在 Spring 初始 Bean 的過程中上面的初始化方式的執(zhí)行順序如下:
-
Aware 接口的回調
-
JSR-250 @PostConstruct 標注的方法的調用
-
InitializingBean#afterPropertiesSet 方法的回調
-
init-method 初始化方法的調用
|