1. 程式人生 > 其它 >spring如何解決迴圈依賴的

spring如何解決迴圈依賴的

1、開篇

本節課會聊聊spring IOC如何解決迴圈依賴問題。包括如下內容:

  • 什麼是迴圈依賴

  • Spring IoC處理迴圈依賴的思路

  • 處理迴圈依賴舉例

2、什麼是迴圈依賴

Spring IoC中的迴圈依賴其實就是迴圈引用,兩個或者兩個以上的 Bean 互相持有對方,最終形成閉環。如圖1所示,如A依賴於B,B依賴於C,C又依賴於A。這樣這樣一個場景,初始化A的時候需要完成B的初始化,而完成B的初始化又需要完成C的初始化,最後C又依賴於A,如此這般A永遠也無法完成初始化的操作。這種物件的相互依賴形成閉環的關係被稱作迴圈依賴。

圖1 迴圈依賴

在Spring IoC的使用場景中有兩類迴圈依賴是無解的:

  • 構造器的迴圈依賴:構造器要呼叫建構函式new 一個物件出來,而引數又依賴於另一個物件。建立類A依賴於類B,new 的時候去建立類B發現類B不存在就會出錯丟擲 BeanCurrentlyInCreationException 異常。

  • prototype 原型bean迴圈依賴:原型bean的初始化過程中不論是通過構造器引數迴圈依賴還是通過set方法產生的迴圈依賴也會丟擲異常。

然而針對 singleton bean的迴圈依賴的場景可以通過三級快取的方式解決。下面就根據該解決方案展開說明。

3、Spring IoC處理迴圈依賴的思路

在整理Spring IoC處理singleton bean迴圈依賴的思路之前先來複習一下bean的生命週期,其包括的三個步驟:

  • 例項化:執行了bean的構造方法,bean中依賴的物件還未賦值

  • 設定屬性:給bean中依賴的物件賦值,若被依賴的物件尚未初始化,則先進行該物件的生命週期(遞迴)。

  • 初始化:執行bean的初始化方法,回撥方法等。

解決迴圈依賴的思路就藏在這三個步驟中,在例項化與設定屬性兩個步驟之間引入快取機制,將已經建立好例項但是並沒有設定屬性的bean放到快取裡,快取中是沒有屬性設定的例項物件。假設A物件和B物件互相依賴,A物件的建立需要引用到B物件,而B物件的建立也需要A物件。在建立A物件的時候可以將其放入到快取中,當B物件建立的時候直接從快取裡引用A物件(此時的A物件只完成了例項化,沒有進行設定屬性的操作,因此不是完成的A物件,我們稱之為半成品A物件),當B物件利用這個半成品的A物件完成例項建立以後(三個步驟都完成),再被A物件引用進去,則A物件也完成了建立。

上文提到的快取在這裡做一個解釋,我們將其分為三級,每級快取都起到不同的作用,如下表格所示:

  • 一級快取:用於存放完全初始化好的 bean,也就是完成三個步驟的bean,拿出來的bean是可以直接使用的。

  • 二級快取:存放原始的 bean 物件,此時的物件只進行了例項化但是沒有填充屬性,也就是我們所說的“半成品物件”,它的建立是用來解決迴圈依賴的。

  • 三級快取:用來存放 bean 工廠物件,這個工廠物件是用來產生bean物件的例項的。

原始碼

級別

描述

singletonObjects

一級快取

用於存放完全初始化好的 bean,從該快取中取出的 bean 可以直接使用

earlySingletonObjects

二級快取

存放原始的 bean 物件(尚未填充屬性),用於解決迴圈依賴

singletonFactories

三級快取

存放 bean 工廠物件,用於解決迴圈依賴

解決迴圈依賴的整個過程是:

先從一級快取裡取bean例項,如果沒有對應的bean例項,二級快取裡取,如果二級快取中也沒有bean例項,singletonFactories三級快取裡獲取。由於三級快取存放著產生bean例項的工廠類,因此可以通過該工廠類產生bean例項。

這裡可以呼叫工廠類暴露的getObject方法返回早期暴露物件引用,也是我們所說的半成品bean,也可以成為earlySingletonObject。並且將這個半成品bean放到二級快取裡,在三級快取裡刪除該bean。什麼時候這個半成品填充了屬性以後,就被移動到一級快取中,也就是被作為可以使用的已經完成初始化的例項bean了,處理迴圈依賴的過程宣告完畢。下面通過一個例子讓大家更好理解這個思路。

4、處理迴圈依賴舉例

根據上面的思路,這裡假設A 和 B 互相依賴,如圖2所示,在A 建立例項的時候使用了getBean方法,通過createBeanInstatnce方法對A進行例項化。此時的A只是被例項化出來了,並沒有進行填充屬性的操作,然後通過addSingletonFactory的方法將建立A的工廠類新增到三級快取中。上面的思路中提到了這個放到三級快取中的工廠類是用來生成bean例項用的。

接著往下,當通過populateBean填充例項A屬性的時候發現,A依賴B。此時開始通過getBean方法建立B的例項,依舊通過createBeanInstatnce方法對B進行例項化,也把建立B例項的工廠類通過addSingletonFactory方法新增到三級快取中。在使用populateBean方法填充B的屬性時,發現B依賴A,此時通過getBean方法對A進行例項化。

這個時候就出現迴圈依賴的情況了,getBean方法先從一級快取中獲取 A 的例項,發現沒有,再去二級快取中找,還是找不到,沒有辦法只有找三級快取中的A 例項建立工廠去建立A的例項。在前面的步驟中A 已經將工廠類通過addSingletonFactory方法存放到了三級快取中,於是呼叫A的工廠類創造A的例項,並且將其放到二級快取中返回給B 用來填充B的屬性,當B完成屬性填充以後產生了B的例項,返回給populateBean(A)使用,此時A獲取了B的例項(完成屬性填充的B例項)。

所以,A 也可以完成屬性填充從而產生A 的初始化以後的例項並且將其放到一級快取中。由於B之前使用的是A的例項是沒有做屬性填充的,也就是半成品的A例項,因此此時從一級快取中獲取成品的A例項完成B物件的初始化。

圖2 A B 相互迴圈依賴,如何處理。

5、總結

本節課提出了Spring IoC遇到的迴圈依賴的問題,並且通過分析bean建立的過程和三級快取技術,找到了解決singleton bean 迴圈依賴的辦法,然後通過一個簡單的迴圈依賴處理的例子加強對這一思路的理解。