Spring源碼-循環依賴(附25張調試截圖)

Spring 在哪些狀況下會出現循環依賴錯誤?哪些狀況下能自身解決循環依賴,又是如何解決的?本文將介紹筆者經過本地調試 Spring 源碼來觀察循環依賴的過程。java

1. 註解屬性注入

首先本地準備好一份 Spring 源碼,筆者是從 Github 上 Clone 下來的一份,而後用 IDEA 導入,再建立一個 module 用於存放調試的代碼。spring

調試模塊目錄

本次調試有三個類,A、B 經過註解 @Autowired 標註在屬性上構成循環依賴,Main 爲主函數類。緩存

@Component("A")
public class A {
	@Autowired
	B b;
}
複製代碼
@Component("B")
public class B {
	@Autowired
	A a;
}
複製代碼
public class Main {
	public static void main(String[] args) {
		ApplicationContext context =
				new ClassPathXmlApplicationContext("spring-config.xml");
		A a = (A) context.getBean("A");
		B b = (B) context.getBean("B");
	}
}
複製代碼

能夠先試着運行下,並不會報錯,說明這種狀況下的循環依賴能由 Spring 解決。函數

咱們要觀察如何解決循環依賴,首先須要知道 @Autowired 標註的屬性是如何注入的,如 B 是怎麼注入到 A 中的。post

因爲 A、B 的 scope 是 single,且默認 non-lazy,因此在 ClassPathXmlApplicationContext 初始化時會預先加載 A、B,並完成實例化、屬性賦值、初始化等步驟。ClassPathXmlApplicationContext 的構造方法以下:this

ClassPathXmlApplicationContext的構造方法

其中 refresh 是容器的啓動方法,點進去,而後找到咱們須要的那一步,即實例化 A、B 的步驟:spa

refresh方法

finishBeanFactoryInitialization 會先完成工廠的實例化,而後在最後一步實例化 A、B:3d

finishBeanFactoryInitialization

preInstantiateSingletons 將依次對 non-lazy singleton 依次實例化,其中就有 A、B:調試

preInstantiateSingletions

A、B 不是工廠類,則直接經過 getBean 觸發初始化。首先會觸發 A 的初始化。code

getBean => doGetBean ,再經過 getSingleton 獲取 Bean。注意在 doGetBean 中有兩個 getSingleton 方法會前後執行,本文用 getSingleton-CgetSingleton-F 來區分。第一個是嘗試從緩存中獲取,這時緩存中沒有 A,沒法得到,則執行第二個,經過工廠得到。

public Object getSingleton(String beanName) 複製代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 複製代碼

這裏會執行 getSingleton-F 來獲取單例 Bean:

doGetBean

這裏爲 getSingleton-F 傳入了個 Lambda 表達式給 ObjectFactory 接口類型的 singletonFatory

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 複製代碼
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}
複製代碼

因此將會建立一個 ObjectFactory 的匿名類對象 singletonFactory,而其 getObject 方法的實現將調用 createBean

getSingleton-F

getObject => createBean => doCreateBean,建立 A 的 Bean,關於 doCreateBean ,在 《如何記憶 Spring 的生命週期》 中有進行介紹。主要有 4 個步驟:(1)實例化,(2)屬性賦值,(3)初始化,(4)註冊回調函數。下面看下 B 是在哪一步注入到 A 中。

首先看下是否是實例化。

doCreateBean-實例化

在實例化完成後,bean 中的 b 仍爲 null,說明不是實例化。那再看下一步,屬性賦值。

doCreateBean-populateBean

populateBean 執行後,bean 中的 b 再也不是 null 了,而已是 B 的對象了,並且 b 的 a 屬性也不是 null,是此時正在建立的 bean,說明已經成功完成了依賴注入。因此 "@Autowired 標註的屬性是如何注入的" 和 "Spring 如何解決循環依賴" 兩個問題的答案都在 populateBean 這一步中。那再從新進入 populateBean 看下。

populateBean-BeanPostProcessor

其中會依次調用 BeanPostProcessorpostProcessProperties 方法。在 getBeanPostProcessors 返回的 List 中有 AutowiredAnnotationBeanPostProcessor ,將負責 @Autowired 的注入。

AutowiredAnnotationBeanPostProcessorpostProcessProperties 方法以下所示:

AutowiredAnnotationBeanPostProcessor-postProcessProperties

先找到被 @Autowired 標註的 b 屬性,再經過 inject 注入。

InjectionMetadata-inject

進入 inject 方法,因爲 A 依賴 B,這裏將經過 beanFactory.resolveDependency 得到 B 的 bean。

AutowiredAnnotationBeanPostProcessor-獲取B的bean

在成功獲取 B 的 Bean 後,再經過反射注入。

AutowiredAnnotationBeanPostProcessor-反射注入

如今須要關注的就是 resolveDependency,這裏解決 A => B 的依賴,須要去獲取 B,將仍然經過 getBean 獲取,和以前說 getBean 獲取 A 的過程相似,只是此次換成了 B,調用棧以下:

doCreateBean-B 調用棧

仍然將經過 doCreateBean 來建立 B 的 bean。

那麼問題來了,以前說過 doCreateBean 會進行屬性賦值,那麼因爲 B 又依賴 A,因此須要將 A 注入到 B 中,但是 A 也還正在進行 doGetBean。那麼 Spring 是怎麼解決的循環依賴,關注 B 的 populateBean 就能知道答案了。

因爲 B 依賴於 A,因此須要將 A 注入 B,該過程和前面說的 」將 B 注入 A「相似,經過 getBean 來得到 A。

爲B注入A再次調用getBean

getBean => doGetBean => getSingleton,又是熟悉的步驟,但此次 getSingleton-C 中發生了不同的事,可以成功得到 A 的緩存。

getSingleton-C 獲取到A

首先嚐試在 singletoObjects 中獲取,失敗。接着嘗試從 earlySingletonObjects 中獲取,失敗。最後在 singletonFactories 中獲取到 singletonFactory,並經過 getObject 獲取到 A 的 bean。

這三者被稱做三級緩存,在 getSingleton-C 方法中會依次從這三級緩存中嘗試獲取單例 Bean。當從第三級緩存獲取到 A 後,會將其從第三級緩存中移除,加入到第二級緩存。

/** Cache of singleton objects: bean name to bean instance. */
// 緩存單例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
// 緩存正在建立,還未建立完成的單例Bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory. */
// 緩存單例bean的工廠
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
複製代碼

咱們能夠看到 singletonFactories 中存有 A 和 B,它們是何時被加到三級緩存中的呢?就是在 doCreateBean 中作 populateBean 的前一步經過 addSingeletonFactorybeanNameObjectFactory 的匿名工廠類加入到第三級緩存中。當調用 singletonFactory.getObject 方法時,將調用 getEarlyBeanReference 獲取 A 的 Bean。

doCreateBean-addSingletonFactory

getEarlyBeanReference 會返回 A 的引用,雖然 A 還在建立,未完成。

getEarlyBeanReference

讓咱們想下後面會發生的事:

  1. B 成功獲取到了 A 的引用,完成屬性賦值;
  2. B 完成 doCreateBean,將返回,A 成功獲取到 B 的 Bean,完成屬性賦值,最後完成 A 的 Bean 的建立。

最後 A、B 的 Bean 都完成建立。

之因此經過註解屬性注入不會存在循環依賴問題,是由於 Spring 記錄了正在建立的 Bean,並提早將正在建立的 Bean 的引用交給了須要依賴注入的 Bean,從而完成閉環,讓 B 建立成功,不會繼續嘗試建立 A。

在這個過程當中最關鍵的是 Bean 的引用,而要有 Bean 的引用便必須完成 doCreateBean 中的第一步實例化

咱們這裏是將 @Autowired 標註在屬性上,而依賴注入發生在第二步屬性賦值,這時才能成功獲取到引用。

下面咱們試下修改 A、B 爲構造器注入,讓依賴注入發生在第一步實例化中。

2. 構造器注入

@Component("A")
public class A {
	B b;

	@Autowired
	public A(B b) {
		this.b = b;
	}
}
複製代碼
@Component("B")
public class B {
	A a;

	@Autowired
	public B(A a) {
		this.a = a;
	}
}
複製代碼

構造器注入-doCreate

構造器的注入將發生在 doCreateBean 的第一步 createBeanInstance,具體方法以下:

createBeanInstance

獲取 A 的構造器,執行 autowireConstructor 。而後調用ConstructorResolvercreateArgument 方法處理構造函數的參數,因爲構造器被 @Autowired 標註,將使用 resolveAutowiredArgument 處理注入參數,接着又是熟悉的步驟,調用棧以下:

構造器注入-建立B

處理依賴注入,會經過 getBean 得到 B,在 doCreateBean 中進行 B 實例化。

那咱們就再進入 B 實例化的第一步 createBeanInstance 方法,調用棧以下:

構造器注入-B依賴A-建立A

B 的構造方法依賴 A,則嘗試經過 doGetBean 獲取 A。因爲 A 沒有在 doCreateBean 中完成實例化,因此 getSingleton-C 中沒法得到 A 的緩存,則只能經過 getSingleton-F 方法嘗試得到 A。

getSingleton-循環檢查

但在 getSingleton-F 中的 beforeSingletonCreation 方法將對循環依賴進行檢查。

beforeSingletonCreation

singletonsCurrentlyInCreation 是一個 set,因爲 A 已經 都在 getSingleton-F 中執行過一遍了,已經被添加到了 singletonsCurrentlyInCreation,因此這裏第二次經過 getSingleton-F 獲取 A 時,add 返回 false,將拋出 BeanCurrentlyInCreationException 異常。

小結

對比以上兩種方式 「屬性注入」 和 「構造器注入」,都是 A => B => A,區別在於 B => A 時,「屬性注入」 在 getSingleton-C 中會經過緩存獲取到 A 的引用,而 「構造器注入」,則因爲不存在 A 引用,也天然沒法經過緩存得到,便會嘗試再次經過 getSingleton-F 獲取,而及時被 beforeSingletonCreation 檢查拋出循環依賴異常。

相關文章
相關標籤/搜索