死磕Spring之IoC篇 - 深入瞭解Spring IoC(面試題)

月圓吖發表於2021-02-20

該系列文章是本人在學習 Spring 的過程中總結下來的,裡面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼註釋 Spring 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.1.14.RELEASE

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 生態在涉及到 Java 的專案中被廣泛應用,它提供了非常多的元件,能夠讓你在開發 Java 應用的過程變得更加容易,彈性地支援其他軟體框架,可以比作一個“排插座”,其他軟體框架簡單地“插上”即可結合 Spring 一起使用,給開發人員帶來了非常多的便利。Spring 底層 IoC 容器的設計實現也是非常完美的,在整個 Spring 應用上下文的生命週期和 Spring Bean 的生命週期的許多階段提供了相應的擴充套件點,供開發者自行擴充套件,使得框架非常的靈活。

2. Spring Framework 的優勢和不足?

優勢:Spring 面向模組進行開發,根據不同的功能進行劃分,根據需求引入對應的模組即可,對於開發人員非常友好。例如 Spring IoC 容器,將我們的 Java 物件作為 Spring Bean 進行管理,管理著 Bean 的整個生命週期;Spring MVC 提供“模型-檢視-控制器”(Model-View-Controller)架構和隨時可用的元件,用於開發靈活且鬆散耦合的 Web 應用程式;Spring AOP 提供面向切面程式設計的介面,可以很方便的使用;還有許多其他的功能模組,就不一一講述了。

不足:整個 Spring 體系比較複雜,對於開發人員需要一定的學習成本,遇到相關問題時需要對底層實現有充分的瞭解,這也就需要開發人員投入更多的時間和精力去學習。當然,如今 Spring 體系整合了 Java 生態非常多的東西,為開發人員帶來的便利遠大於這些不足,我覺得是有必要對 Spring 進行充分的學習,去了解 Spring 的貢獻者們的設計思路,對自身也會有很大的提升,從中可以學習到許多的東西。

3. 你對 IoC 的理解?

Inversion of Control(IoC)是物件導向中的一種程式設計思想或原則。可以先回到傳統方式,當我依賴一個物件,我需要主動去建立它並進行屬性賦值,然後我才能去使用這個物件。對於 IoC 這種方式來說,它使得物件或者元件的建立更為透明,你不需要過多地關注細節,如建立物件、屬性賦值,這些工作交都由 IoC 容器來完成,已達到解耦的目的。

IoC 控制反轉,簡單來理解其實就是把獲取依賴物件的方式,交由 IoC 容器來實現,由“主動拉取”變為“被動獲取”。

4. 為什麼需要 IoC ?

實際上,IoC 是為了遮蔽構造細節。例如 new 出來的物件的生命週期中的所有細節對於使用端都是知道的,如果在沒有 IoC 容器的前提下,IoC 是沒有存在的必要,不過在複雜的系統中,我們的應用更應該關注的是物件的運用,而非它的構造和初始化等細節。

5. IoC 和 DI 的區別?

DI 依賴注入不完全等同於 IoC,更應該說 DI 依賴注入是 IoC 的一種實現方式或策略。

依賴查詢依賴注入都是 IoC 的實現策略。依賴查詢就是在應用程式裡面主動呼叫 IoC 容器提供的介面去獲取對應的 Bean 物件,而依賴注入是在 IoC 容器啟動或者初始化的時候,通過構造器、欄位、setter 方法或者介面等方式注入依賴。依賴查詢相比於依賴注入對於開發者而言更加繁瑣,具有一定的程式碼入侵性,需要藉助 IoC 容器提供的介面,所以我們總是強調後者。依賴注入在 IoC 容器中的實現也是呼叫相關的介面獲取 Bean 物件,只不過這些工作都是在 IoC 容器啟動時由容器幫你實現了,在應用程式中我們通常很少主動去呼叫介面獲取 Bean 物件。

6. IoC 容器的職責?

主要有以下職責:

  • 依賴處理,通過依賴查詢或者依賴注入

  • 管理託管的資源(Java Bean 或其他資源)的生命週期

  • 管理配置(容器配置、外部化配置、託管的資源的配置)

IoC 容器有非常多,例如 JDK 的 Java Beans,Java EE 的 EJB,Apache Avalon,Google guice,Spring,其中 Spring 是最成功的的一個,目前被廣泛應用。

其中 Spring 借鑑了 JDK 的 Java Beans 設計思想,也使用到其中相關類(例如 java.beans.PropertyEditor 屬性編輯器),開發過 IDE 的 GUI 介面的夥伴應該對 Java Beans 比較熟悉。

7. 什麼是 Spring IoC 容器?

Spring 框架是一個 IoC 容器的實現,DI 依賴注入是它的實現的一個原則,提供依賴查詢和依賴注入兩種依賴處理,管理著 Bean 的生命週期。Spring 還提供了 AOP 抽象、事件抽象、事件監聽機制、SPI 機制、強大的第三方整合、易測試性等其他特性。

8. 構造器注入和 Setter 注入

構造器注入:通過構造器的引數注入相關依賴物件

Setter 注入:通過 Setter 方法注入依賴物件,也可以理解為欄位注入

對於兩種注入方式的看法:

  • 構造器注入可以避免一些尷尬的問題,比如說狀態不確定性地被修改,在初始化該物件時才會注入依賴物件,一定程度上保證了 Bean 初始化後就是不變的物件,這樣對於我們的程式和維護性都會帶來更多的便利;

  • 構造器注入不允許出現迴圈依賴,因為它要求被注入的物件都是成熟態,保證能夠例項化,而 Setter 注入或欄位注入沒有這樣的要求;

  • 構造器注入可以保證依賴的物件能夠有序的被注入,而 Setter 注入或欄位注入底層是通過反射機制進行注入,無法完全保證注入的順序;

  • 如果構造器注入出現比較多的依賴導致程式碼不夠優雅,我們應該考慮自身程式碼的設計是否存在問題,是否需要重構程式碼結構。

除了上面的注入方式外,Spring 還提供了介面回撥注入,通過實現 Aware 介面(例如 BeanNameAware、ApplicationContextAware)可以注入相關物件,Spring 在初始化這類 Bean 時會呼叫其 setXxx 方法注入物件,例如注入 beanName、ApplicationContext

9. BeanFactory 和 ApplicationContext 誰才是 Spring IoC 容器?

BeanFactory 是 Spring 底層 IoC 容器,ApplicationContext 是 BeanFactory 的子介面,是 BeanFactory 的一個超集,提供 IoC 容器以外更多的功能。ApplicationContext 除了扮演 IoC 容器角色,還提供了這些企業特性:面向切面(AOP)、配置元資訊、資源管理、事件機制、國際化、註解、Environment 抽象等。我們一般稱 ApplicationContext 是 Spring 應用上下文,BeanFactory 為 Spring 底層 IoC 容器。

10. Spring Bean 的生命週期?

生命週期:

  1. Spring Bean 元資訊配置階段,可以通過面向資源(XML 或 Properties)、面向註解、面向 API 進行配置

  2. Spring Bean 元資訊解析階段,對上一步的配置元資訊進行解析,解析成 BeanDefinition 物件,該物件包含定義 Bean 的所有資訊,用於例項化一個 Spring Bean

  3. Spring Bean 元資訊註冊階段,將 BeanDefinition 配置元資訊 儲存至 BeanDefinitionRegistry 的 ConcurrentHashMap 集合中

  4. Spring BeanDefinition 合併階段,定義的 Bean 可能存在層次性關係,則需要將它們進行合併,存在相同配置則覆蓋父屬性,最終生成一個 RootBeanDefinition 物件

  5. Spring Bean 的例項化階段,首先的通過類載入器載入出一個 Class 物件,通過這個 Class 物件的構造器建立一個例項物件,構造器注入在此處會完成。在例項化階段 Spring 提供了例項化前後兩個擴充套件點(InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation、postProcessAfterInstantiation 方法)

  6. Spring Bean 屬性賦值階段,在 Spring 例項化後,需要對其相關屬性進行賦值,注入依賴的物件。首先獲取該物件所有屬性與屬性值的對映,可能已定義,也可能需要注入,在這裡都會進行賦值(反射機制)。提示一下,依賴注入的實現通過 CommonAnnotationBeanPostProcessor(@Resource、@PostConstruct、@PreDestroy)和 AutowiredAnnotationBeanPostProcessor(@Autowired、@Value)兩個處理器實現的。

  7. Aware 介面回撥階段,如果 Spring Bean 是 Spring 提供的 Aware 介面型別(例如 BeanNameAware、ApplicationContextAware),這裡會進行介面的回撥,注入相關物件(例如 beanName、ApplicationContext)

  8. Spring Bean 初始化階段,這裡會呼叫 Spring Bean 配置的初始化方法,執行順序:@PostConstruct 標註方法、實現 InitializingBean 介面的 afterPropertiesSet() 方法、自定義初始化方法。在初始化階段 Spring 提供了初始化前後兩個擴充套件點(BeanPostProcessor 的 postProcessBeforeInitialization、postProcessAfterInitialization 方法)

  9. Spring Bean 初始化完成階段,在所有的 Bean(不是抽象、單例模式、不是懶載入方式)初始化後,Spring 會再次遍歷所有初始化好的單例 Bean 物件,如果是 SmartInitializingSingleton 型別則呼叫其 afterSingletonsInstantiated() 方法,這裡也屬於 Spring 提供的一個擴充套件點

  10. Spring Bean 銷燬階段,當 Spring 應用上下文關閉或者你主動銷燬某個 Bean 時則進入 Spring Bean 的銷燬階段,執行順序:@PreDestroy 註解的銷燬動作、實現了 DisposableBean 介面的 Bean 的回撥、destroy-method 自定義的銷燬方法。這裡也有一個銷燬前階段,也屬於 Spring 提供的一個擴充套件點,@PreDestroy 就是基於這個實現的

  11. Spring 垃圾收集(GC)

總結:

  1. 上面 123 屬於 BeanDefinition 配置元資訊階段,算是 Spring Bean 的前身,想要生成一個 Bean 物件,需要將這個 Bean 的所有資訊都定義好;

  2. 其中 45 屬於例項化階段,想要生成一個 Java Bean 物件,那麼肯定需要根據 Bean 的元資訊先例項化一個物件;

  3. 接下來的 6 屬於屬性賦值階段,例項化後的物件還是一個空物件,我們需要根據 Bean 的元資訊對該物件的所有屬性進行賦值;

  4. 後面的 789 屬於初始化階段,在 Java Bean 物件生成後,可能需要對這個物件進行相關初始化工作才予以使用;

  5. 最後面的 1011 屬於銷燬階段,當 Spring 應用上下文關閉或者主動銷燬某個 Bean 時,可能需要對這個物件進行相關銷燬工作,最後等待 JVM 進行回收。

11. BeanDefinition 是什麼?

BeanDefinition 是 Spring Bean 的“前身”,其內部包含了初始化一個 Bean 的所有元資訊,在 Spring 初始化一個 Bean 的過程中需要根據該物件生成一個 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 的區別?

BeanPostProcessor 提供 Spring Bean 初始化前和初始化後的生命週期回撥,允許對關心的 Bean 進行擴充套件,甚至是替換,其相關子類也提供 Spring Bean 生命週期中其他階段的回撥。

BeanFactoryPostProcessor 提供 Spring BeanFactory(底層 IoC 容器)的生命週期的回撥,用於擴充套件 BeanFactory(實際為 ConfigurableListableBeanFactory),BeanFactoryPostProcessor 必須由 Spring ApplicationContext 執行,BeanFactory 無法與其直接互動。

14. 依賴注入和依賴查詢的來源是否相同?

否,依賴查詢的來源僅限於 Spring BeanDefinition 以及單例物件,而依賴注入的來源還包括 Resolvable Dependency(Spring 應用上下文定義的可已處理的注入物件,例如注入 BeanFactory 注入的是 ApplicationContext 物件)以及 @Value 所標註的外部化配置

15. 如何基於 Extensible XML authoring 擴充套件 Spring XML 元素?

Spring XML 擴充套件

  1. 編寫 XML Schema 檔案(XSD 檔案):定義 XML 結構

  2. 自定義 NamespaceHandler 實現:定義名稱空間的處理器

  3. 自定義 BeanDefinitionParser 實現:繫結名稱空間下不同的 XML 元素與其對應的解析器

  4. 註冊 XML 擴充套件(META-INF/spring.handlers 檔案):名稱空間與名稱空間處理器的對映

  5. 編寫 Spring Schema 資源對映檔案(META-INF/spring.schemas 檔案):XML Schema 檔案通常定義為網路的形式,在無網的情況下無法訪問,所以一般在本地的也有一個 XSD 檔案,可通過編寫 spring.schemas 檔案,將網路形式的 XSD 檔案與本地的 XSD 檔案進行對映,這樣會優先從本地獲取對應的 XSD 檔案

Mybatis 對 Spring 的整合專案中的 <mybatis:scan /> 標籤就是這樣實現的,可以參考:NamespaceHandlerMapperScannerBeanDefinitionParserXSD 等檔案

具體實現邏輯參考後續《解析自定義標籤(XML 檔案)》一文

16. Java 泛型擦寫發生在編譯時還是執行時?

執行時。編譯時,泛型引數型別還是存在的,執行時會忽略。

17. 簡述 Spring 事件機制原理?

主要有以下幾個角色:

  • Spring 事件 - org.springframework.context.ApplicationEvent,實現了 java.util.EventListener 介面

  • Spring 事件監聽器 - org.springframework.context.ApplicationListener,實現了 java.util.EventObject 類

  • Spring 事件釋出器 - org.springframework.context.ApplicationEventPublisher

  • Spring 事件廣播器 - org.springframework.context.event.ApplicationEventMulticaster

Spring 內建的事件:

  • ContextRefreshedEvent:Spring 應用上下文就緒事件
  • ContextStartedEvent:Spring 應用上下文啟動事件
  • ContextStoppedEvent:Spring 應用上下文停止事件
  • ContextClosedEvent:Spring 應用上下文關閉事件

Spring 應用上下文就是一個 ApplicationEventPublisher 事件釋出器,其內部有一個 ApplicationEventMulticaster 事件廣播器(被觀察者),裡面儲存了所有的 ApplicationListener 事件監聽器(觀察者)。Spring 應用上下文釋出一個事件後會通過 ApplicationEventMulticaster 事件廣播器進行廣播,能夠處理該事件型別的 ApplicationListener 事件監聽器則進行處理。

18. @EventListener 的工作原理?

@EventListener 用於標註在方法上面,該方法則可以用來處理 Spring 的相關事件。

Spring 內部有一個處理器 EventListenerMethodProcessor,它實現了 SmartInitializingSingleton 介面,在所有的 Bean(不是抽象、單例模式、不是懶載入方式)初始化後,Spring 會再次遍歷所有初始化好的單例 Bean 物件時會執行該處理器對該 Bean 進行處理。在 EventListenerMethodProcessor 中會對標註了 @EventListener 註解的方法進行解析,如果符合條件則生成一個 ApplicationListener 事件監聽器並註冊。

19. Spring 提供的註解有哪些?

核心註解有以下:

  • Spring 模式註解
Spring 註解 場景說明 起始版本
@Repository 資料倉儲模式註解 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 提供,幫助實現 AOP 特性。這也是領域驅動設計中的一種思想。

領域驅動設計:Domain-Driven Design,簡稱 DDD。過去系統分析和系統設計都是分離的,這樣割裂的結果導致需求分析的結果無法直接進行設計程式設計,而能夠進行程式設計執行的程式碼卻扭曲需求,導致客戶執行軟體後才發現很多功能不是自己想要的,而且軟體不能快速跟隨需求變化。DDD 則打破了這種隔閡,提出了領域模型概念,統一了分析和設計程式設計,使得軟體能夠更靈活快速跟隨需求變化。

  • 裝配註解
Spring 註解 場景說明 起始版本
@ImportResource 替換 XML 元素 <import> 2.5
@Import 匯入 Configuration 類 2.5
@ComponentScan 掃描指定 package 下標註 Spring 模式註解的類 3.1
  • 依賴注入註解
Spring 註解 場景說明 起始版本
@Autowired Bean 依賴注入,支援多中依賴查詢方式 2.5
@Qualifier 細粒度的 @Autowired 依賴查詢 2.5
  • @Enable 模組驅動
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 介面實現、ImportBeanDefinitionRegistrar 介面實現),來實現引入某個模組或功能。

  • 條件註解
Spring 註解 場景說明 起始版本
@Conditional 條件限定,引入某個 Bean 4.0
@Profile 從 Spring 4.0 開始,@Profile 基於 @Conditional 實現,限定 Bean 的 Spring 應用環境 4.0

20. 簡述 Spring Environment ?

統一 Spring 配置屬性的儲存,用於佔位符處理和型別轉換,還支援更豐富的配置屬性源(PropertySource);

通過 Environment Profiles 資訊,幫助 Spring 容器提供條件化地裝配 Bean。

21. Environment 完整的生命週期是怎樣的?

在 Spring 應用上下文進入重新整理階段之前,可以通過 setEnvironment(Environment) 方法提前設定 Environment 物件,在重新整理階段如果沒有 Environment 物件則會建立一個新的 Environment 物件

22. Spring 應用上下文的生命週期?

Spring 應用上下文就是 ApplicationContext,生命週期主要體現在 org.springframework.context.support.AbstractApplicationContext#refresh() 方法中,大致如下:

  1. Spring 應用上下文啟動準備階段,設定相關屬性,例如啟動時間、狀態標識、Environment 物件

  2. BeanFactory 初始化階段,初始化一個 BeanFactory 物件,載入出 BeanDefinition 們;設定相關元件,例如 ClassLoader 類載入器、表示式語言處理器、屬性編輯器,並新增幾個 BeanPostProcessor 處理器

  3. BeanFactory 後置處理階段,主要是執行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的處理,對 BeanFactory 和 BeanDefinitionRegistry 進行後置處理,這裡屬於 Spring 應用上下文的一個擴充套件點

  4. BeanFactory 註冊 BeanPostProcessor 階段,主要初始化 BeanPostProcessor 型別的 Bean(依賴查詢),在 Spring Bean 生命週期的許多節點都能見到該型別的處理器

  5. 初始化內建 Bean,初始化當前 Spring 應用上下文的 MessageSource 物件(國際化文案相關)、ApplicationEventMulticaster 事件廣播器物件、ThemeSource 物件

  6. Spring 事件監聽器註冊階段,主要獲取到所有的 ApplicationListener 事件監聽器進行註冊,並廣播早期事件

  7. BeanFactory 初始化完成階段,主要是初始化所有還未初始化的 Bean(不是抽象、單例模式、不是懶載入方式)

  8. Spring 應用上下文重新整理完成階段,清除當前 Spring 應用上下文中的快取,例如通過 ASM(Java 位元組碼操作和分析框架)掃描出來的後設資料,併發布上下文重新整理事件

  9. Spring 應用上下文啟動階段,需要主動呼叫 AbstractApplicationContext#start() 方法,會呼叫所有 Lifecycle 的 start() 方法,最後會發布上下文啟動事件

  10. Spring 應用上下文停止階段,需要主動呼叫 AbstractApplicationContext#stop() 方法,會呼叫所有 Lifecycle 的 stop() 方法,最後會發布上下文停止事件

  11. Spring 應用上下文關閉階段,釋出當前 Spring 應用上下文關閉事件,銷燬所有的單例 Bean,關閉底層 BeanFactory 容器;注意這裡會有一個鉤子函式(Spring 向 JVM 註冊的一個關閉當前 Spring 應用上下文的執行緒),當 JVM “關閉” 時,會觸發這個執行緒的執行

總結:

  • 上面的 12345678 都屬於 Sping 應用上下文的重新整理階段,完成了 Spring 應用上下文一系列的初始化工作;

  • 9 屬於 Spring 應用上下文啟動階段,和 Lifecycle 生命週期物件相關,會呼叫這些物件的 start() 方法,最後釋出上下文啟動事件;

  • 10 屬於 Spring 應用上下文停止階段,和 Lifecycle 生命週期物件相關,會呼叫這些物件的 stop() 方法,最後釋出上下文停止事件;

  • 11 屬於 Spring 應用上下文關閉階段,釋出上下文關閉事件,銷燬所有的單例 Bean,關閉底層 BeanFactory 容器。

23. Spring 應用上下文生命週期有哪些階段?

參考Spring 應用上下文的生命週期

  • 重新整理階段 - ConfigurableApplicationContext#refresh()
  • 啟動階段 - ConfigurableApplicationContext#start()
  • 停止階段 - ConfigurableApplicationContext#stop()
  • 關閉階段 - ConfigurableApplicationContext#close()

24. 簡述 ObjectFactory?

ObjectFactory(或 ObjectProvider) 可關聯某一型別的 Bean,僅提供一個 getObject() 方法用於返回目標 Bean 物件,ObjectFactory 物件被依賴注入或依賴查詢時並未實時查詢到關聯型別的目標 Bean 物件,在呼叫 getObject() 方法才會依賴查詢到目標 Bean 物件。

根據 ObjectFactory 的特性,可以說它提供的是延遲依賴查詢。通過這一特性在 Spring 處理迴圈依賴(欄位注入)的過程中就使用到了 ObjectFactory,在某個 Bean 還沒有完全初始化好的時候,會先快取一個 ObjectFactory 物件(呼叫其 getObject() 方法可返回當前正在初始化的 Bean 物件),如果初始化的過程中依賴的物件又依賴於當前 Bean,會先通過快取的 ObjectFactory 物件獲取到當前正在初始化的 Bean,這樣一來就解決了迴圈依賴的問題。

注意這裡是延遲依賴查詢而不是延遲初始化,ObjectFactory 無法決定是否延遲初始化,而需要通過配置 Bean 的 lazy 屬性來決定這個 Bean 物件是否需要延遲初始化,非延遲初始化的 Bean 在 Spring 應用上下文重新整理過程中就會初始化。

提示:如果是 ObjectFactory(或 ObjectProvider)型別的 Bean,在被依賴注入或依賴查詢時返回的是 DefaultListableBeanFactory#DependencyObjectProvider 私有內部類,實現了 ObjectProvider<T> 介面,關聯的型別為 Object。

25. 簡述 FactoryBean?

FactoryBean 關聯一個 Bean 物件,提供了一個 getObject() 方法用於返回這個目標 Bean 物件,FactoryBean 物件在被依賴注入或依賴查詢時,實際得到的 Bean 就是通過 getObject() 方法獲取到的目標型別的 Bean 物件。如果想要獲取 FactoryBean 本身這個物件,在 beanName 前面新增 & 即可獲取。

我們可以通過 FactoryBean 幫助實現複雜的初始化邏輯,例如在 Spring 繼整合 MyBatis 的專案中,Mapper 介面沒有實現類是如何被注入的?其實 Mapper 介面就是一個 FactoryBean 物件,當你注入該介面時,實際的到的就是其 getObject() 方法返回的一個代理物件,關於資料庫的操作都是通過該代理物件來完成。

26. ObjectFactory、FactoryBean 和 BeanFactory 的區別?

根據其名稱可以知道其字面意思分別是:物件工廠,工廠 Bean

ObjectFactory、FactoryBean 和 BeanFactory 均提供依賴查詢的能力。

  • ObjectFactory 提供的是延遲依賴查詢,想要獲取某一型別的 Bean,需要呼叫其 getObject() 方法才能依賴查詢到目標 Bean 物件。ObjectFactory 就是一個物件工廠,想要獲取該型別的物件,需要呼叫其 getObject() 方法生產一個物件。

  • FactoryBean 不提供延遲性,在被依賴注入或依賴查詢時,得到的就是通過 getObject() 方法拿到的實際物件。FactoryBean 關聯著某個 Bean,可以說在 Spring 中它就是某個 Bean 物件,無需我們主動去呼叫 getObject() 方法,如果想要獲取 FactoryBean 本身這個物件,在 beanName 前面新增 & 即可獲取。

  • BeanFactory 則是 Spring 底層 IoC 容器,裡面儲存了所有的單例 Bean,ObjectFactory 和 FactoryBean 自身不具備依賴查詢的能力,能力由 BeanFactory 輸出。

27. @Bean 的處理流程是怎樣的?

Spring 應用上下文生命週期,在 BeanDefinition(@Component 註解、XML 配置)的載入完後,會執行所有 BeanDefinitionRegistryPostProcessor 型別的處理器,Spring 內部有一個 ConfigurationClassPostProcessor 處理器,它會對所有的配置類進行處理,解析其內部的註解(@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean),其中 @Bean 註解標註的方法會生成對應的 BeanDefinition 物件並註冊。詳細步驟可檢視後續文章。

28. BeanFactory 是如何處理迴圈依賴?

前言,下面的“迴圈依賴”換成“迴圈依賴注入”比較合適,在 Spring 中通過 depends-on 配置的依賴物件如果出現迴圈依賴會丟擲異常

說明:這裡的迴圈依賴指的是單例模式下的 Bean 欄位注入時出現的迴圈依賴。構造器注入對於 Spring 無法自動解決(應該考慮程式碼設計是否有問題),可通過延遲初始化來處理。Spring 只解決單例模式下的迴圈依賴。

在 Spring 底層 IoC 容器 BeanFactory 中處理迴圈依賴的方法主要藉助於以下 3 個 Map 集合:

  1. singletonObjects(一級 Map),裡面儲存了所有已經初始化好的單例 Bean,也就是會儲存 Spring IoC 容器中所有單例的 Spring Bean;
  2. earlySingletonObjects(二級 Map),裡面會儲存從 三級 Map 獲取到的正在初始化的 Bean
  3. singletonFactories(三級 Map),裡面儲存了正在初始化的 Bean 對應的 ObjectFactory 實現類,呼叫其 getObject() 方法返回正在初始化的 Bean 物件(僅例項化還沒完全初始化好),如果存在則將獲取到的 Bean 物件並儲存至 二級 Map,同時從當前 三級 Map 移除該 ObjectFactory 實現類。

當通過 getBean 依賴查詢時會首先依次從上面三個 Map 獲取,存在則返回,不存在則進行初始化,這三個 Map 是處理迴圈依賴的關鍵。

例如兩個 Bean 出現迴圈依賴,A 依賴 B,B 依賴 A;當我們去依賴查詢 A,在例項化後初始化前會先生成一個 ObjectFactory 物件(可獲取當前正在初始化 A)儲存在上面的 singletonFactories 中,初始化的過程需注入 B;接下來去查詢 B,初始 B 的時候又要去注入 A,又去查詢 A ,由於可以通過 singletonFactories 直接拿到正在初始化的 A,那麼就可以完成 B 的初始化,最後也完成 A 的初始化,這樣就避免出現迴圈依賴。

問題一:為什麼需要上面的 二級 Map

因為通過 三級 Map獲取 Bean 會有相關 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 的處理,避免重複處理,處理後返回的可能是一個代理物件

例如在迴圈依賴中一個 Bean 可能被多個 Bean 依賴, A -> B(也依賴 A) -> C -> A,當你獲取 A 這個 Bean 時,後續 B 和 C 都要注入 A,沒有上面的 二級 Map的話,三級 Map 儲存的 ObjectFactory 實現類會被呼叫兩次,會重複處理,可能出現問題,這樣做在效能上也有所提升

問題二:為什麼不直接呼叫這個 ObjectFactory#getObject() 方法放入 二級Map 中,而需要上面的 三級 Map

對於不涉及到 AOP 的 Bean 確實可以不需要 singletonFactories(三級 Map),但是 Spring AOP 就是 Spring 體系中的一員,如果沒有singletonFactories(三級 Map),意味著 Bean 在例項化後就要完成 AOP 代理,這樣違背了 Spring 的設計原則。Spring 是通過 AnnotationAwareAspectJAutoProxyCreator 這個後置處理器在完全建立好 Bean 後來完成 AOP 代理,而不是在例項化後就立馬進行 AOP 代理。如果出現了迴圈依賴,那沒有辦法,只有給 Bean 先建立代理物件,但是在沒有出現迴圈依賴的情況下,設計之初就是讓 Bean 在完全建立好後才完成 AOP 代理。

29. Spring 中幾種初始化方法的執行順序?

有以下初始化方式:

  • Aware 介面:實現了 Spring 提供的相關 XxxAware 介面,例如 BeanNameAware、ApplicationContextAware,其 setXxx 方法會被回撥,可以注入相關物件

  • @PostConstruct 註解:該註解是 JSR-250 的標準註解,Spring 會呼叫該註解標註的方法

  • InitializingBean 介面:實現了該介面,Spring 會呼叫其 afterPropertiesSet() 方法

  • 自定義初始化方法:通過 init-method 指定的方法會被呼叫

在 Spring 初始 Bean 的過程中上面的初始化方式的執行順序如下:

  1. Aware 介面的回撥

  2. JSR-250 @PostConstruct 標註的方法的呼叫

  3. InitializingBean#afterPropertiesSet 方法的回撥

  4. init-method 初始化方法的呼叫

相關文章