Spring 在哪些狀況下會出現循環依賴錯誤?哪些狀況下能自身解決循環依賴,又是如何解決的?本文將介紹筆者經過本地調試 Spring 源碼來觀察循環依賴的過程。java
首先本地準備好一份 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
其中 refresh
是容器的啓動方法,點進去,而後找到咱們須要的那一步,即實例化 A、B 的步驟:spa
finishBeanFactoryInitialization
會先完成工廠的實例化,而後在最後一步實例化 A、B:3d
preInstantiateSingletons
將依次對 non-lazy singleton 依次實例化,其中就有 A、B:調試
A、B 不是工廠類,則直接經過 getBean
觸發初始化。首先會觸發 A 的初始化。code
getBean
=> doGetBean
,再經過 getSingleton
獲取 Bean。注意在 doGetBean
中有兩個 getSingleton
方法會前後執行,本文用 getSingleton-C
和 getSingleton-F
來區分。第一個是嘗試從緩存中獲取,這時緩存中沒有 A,沒法得到,則執行第二個,經過工廠得到。
public Object getSingleton(String beanName) 複製代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 複製代碼
這裏會執行 getSingleton-F
來獲取單例 Bean:
這裏爲 getSingleton-F
傳入了個 Lambda 表達式給 ObjectFactory
接口類型的 singletonFatory
。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 複製代碼
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
複製代碼
因此將會建立一個 ObjectFactory
的匿名類對象 singletonFactory
,而其 getObject
方法的實現將調用 createBean
。
getObject
=> createBean
=> doCreateBean
,建立 A 的 Bean,關於 doCreateBean
,在 《如何記憶 Spring 的生命週期》 中有進行介紹。主要有 4 個步驟:(1)實例化,(2)屬性賦值,(3)初始化,(4)註冊回調函數。下面看下 B 是在哪一步注入到 A 中。
首先看下是否是實例化。
在實例化完成後,bean 中的 b 仍爲 null,說明不是實例化。那再看下一步,屬性賦值。
在 populateBean
執行後,bean 中的 b 再也不是 null 了,而已是 B 的對象了,並且 b 的 a 屬性也不是 null,是此時正在建立的 bean,說明已經成功完成了依賴注入。因此 "@Autowired
標註的屬性是如何注入的" 和 "Spring 如何解決循環依賴" 兩個問題的答案都在 populateBean
這一步中。那再從新進入 populateBean
看下。
其中會依次調用 BeanPostProcessor
的 postProcessProperties
方法。在 getBeanPostProcessors
返回的 List 中有 AutowiredAnnotationBeanPostProcessor
,將負責 @Autowired
的注入。
AutowiredAnnotationBeanPostProcessor
的 postProcessProperties
方法以下所示:
先找到被 @Autowired
標註的 b 屬性,再經過 inject
注入。
進入 inject
方法,因爲 A 依賴 B,這裏將經過 beanFactory.resolveDependency
得到 B 的 bean。
在成功獲取 B 的 Bean 後,再經過反射注入。
如今須要關注的就是 resolveDependency
,這裏解決 A => B 的依賴,須要去獲取 B,將仍然經過 getBean
獲取,和以前說 getBean
獲取 A 的過程相似,只是此次換成了 B,調用棧以下:
仍然將經過 doCreateBean
來建立 B 的 bean。
那麼問題來了,以前說過 doCreateBean
會進行屬性賦值,那麼因爲 B 又依賴 A,因此須要將 A 注入到 B 中,但是 A 也還正在進行 doGetBean
。那麼 Spring 是怎麼解決的循環依賴,關注 B 的 populateBean
就能知道答案了。
因爲 B 依賴於 A,因此須要將 A 注入 B,該過程和前面說的 」將 B 注入 A「相似,經過 getBean
來得到 A。
getBean
=> doGetBean
=> getSingleton
,又是熟悉的步驟,但此次 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
的前一步經過 addSingeletonFactory
把 beanName
和 ObjectFactory
的匿名工廠類加入到第三級緩存中。當調用 singletonFactory.getObject
方法時,將調用 getEarlyBeanReference
獲取 A 的 Bean。
getEarlyBeanReference
會返回 A 的引用,雖然 A 還在建立,未完成。
讓咱們想下後面會發生的事:
doCreateBean
,將返回,A 成功獲取到 B 的 Bean,完成屬性賦值,最後完成 A 的 Bean 的建立。最後 A、B 的 Bean 都完成建立。
之因此經過註解屬性注入不會存在循環依賴問題,是由於 Spring 記錄了正在建立的 Bean,並提早將正在建立的 Bean 的引用交給了須要依賴注入的 Bean,從而完成閉環,讓 B 建立成功,不會繼續嘗試建立 A。
在這個過程當中最關鍵的是 Bean 的引用,而要有 Bean 的引用便必須完成 doCreateBean
中的第一步實例化。
咱們這裏是將 @Autowired
標註在屬性上,而依賴注入發生在第二步屬性賦值,這時才能成功獲取到引用。
下面咱們試下修改 A、B 爲構造器注入,讓依賴注入發生在第一步實例化中。
@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;
}
}
複製代碼
構造器的注入將發生在 doCreateBean
的第一步 createBeanInstance
,具體方法以下:
獲取 A 的構造器,執行 autowireConstructor
。而後調用ConstructorResolver
的 createArgument
方法處理構造函數的參數,因爲構造器被 @Autowired
標註,將使用 resolveAutowiredArgument
處理注入參數,接着又是熟悉的步驟,調用棧以下:
處理依賴注入,會經過 getBean
得到 B,在 doCreateBean
中進行 B 實例化。
那咱們就再進入 B 實例化的第一步 createBeanInstance
方法,調用棧以下:
B 的構造方法依賴 A,則嘗試經過 doGetBean
獲取 A。因爲 A 沒有在 doCreateBean
中完成實例化,因此 getSingleton-C
中沒法得到 A 的緩存,則只能經過 getSingleton-F
方法嘗試得到 A。
但在 getSingleton-F
中的 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
檢查拋出循環依賴異常。