本系列所有基於 Spring 5.2.2.BUILD-SNAPSHOT
版本。由於 Spring 整個體系太過於龐大,因此只會進行關鍵部分的源碼解析。html
本篇文章主要介紹 Spring IoC 是怎麼解決循環依賴的問題的。java
循環依賴就是循環引用,就是兩個或多個 bean
相互之間的持有對方,好比A引用B,B引用A,像下面僞代碼所示:git
public class A { private B b; // 省略get和set方法... }
public class B { private A a; // 省略get和set方法... }
Spring IoC 容器對循環依賴的處理有三種狀況:github
BeanCurrentlylnCreationException
異常。setter
循環依賴:此依賴 Spring 經過三級緩存來解決。BeanCurrentlylnCreationException
異常。仍是假設上面的A和B類是構造器循環依賴,以下所示:spring
public class A { private B b; public A(B b) { this.b = b; } // 省略get和set方法... }
public class B { private A a; public B(A a) { this.a = a; } // 省略get和set方法... }
而後咱們在 XML 中配置了構造器自動注入,以下:緩存
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="constructor" /> <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="constructor" /> </beans>
那麼咱們在獲取 A 時,首先會進入 doGetBean()
方法(該方法在Spring IoC bean 的加載中分析過),會進行到以下代碼塊:app
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // 省略其它代碼... // 若是 bean 的做用域是單例 if (mbd.isSingleton()) { // 建立和註冊單例 bean sharedInstance = getSingleton(beanName, () -> { try { // 建立 bean 實例 return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); // 獲取bean實例 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // 省略其它代碼... }
上面方法中的 getSingleton()
方法會判斷是不是第一次建立該 bean
,若是是第一次會先去建立 bean
,也就是調用 ObjectFacoty
的 getObject()
方法,即調用 createBean()
方法建立 bean
前,會先將當前正要建立的 bean
記錄在緩存 singletonsCurrentlyInCreation
中。函數
在建立A時發現依賴 B,便先去建立 B;B在建立時發現依賴A,此時A由於是經過構造函數建立,因此沒建立完,便又去建立A,發現A存在於 singletonsCurrentlyInCreation
,即正在建立中,便拋出 BeanCurrentlylnCreationException
異常。post
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); // 加鎖 synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); // 一級緩存中不存在當前 bean,也就是當前 bean 第一次建立 if (singletonObject == null) { // 若是當前正在銷燬 singletons,拋出異常 if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)"); } // 建立單例 bean 以前的回調 beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { // 獲取 bean 實例 singletonObject = singletonFactory.getObject(); newSingleton = true; } // 省略異常處理... finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } // 建立單例 bean 以後的回調 afterSingletonCreation(beanName); } if (newSingleton) { // 將 singletonObject 放入一級緩存,並從二級和三級緩存中移除 addSingleton(beanName, singletonObject); } } // 返回 bean 實例 return singletonObject; } } // 單例 bean 建立前的回調方法,默認實現是將 beanName 加入到當前正在建立 bean 的緩存中, // 這樣即可以對循環依賴進行檢測 protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } } // 單例 bean 建立後的回調方法,默認實現是將 beanName 從當前正在建立 bean 的緩存中移除 protected void afterSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); } } protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 這邊bean已經初始化完成了,放入一級緩存 this.singletonObjects.put(beanName, singletonObject); // 移除三級緩存 this.singletonFactories.remove(beanName); // 移除二級緩存 this.earlySingletonObjects.remove(beanName); // 將 beanName 添加到已註冊 bean 緩存中 this.registeredSingletons.add(beanName); } }
仍是假設上面的A和B類是 field 屬性依賴注入循環依賴,以下所示:ui
public class A { private B b; // 省略get和set方法... }
public class B { private A a; // 省略get和set方法... }
而後咱們在 XML 中配置了按照類型自動注入,以下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="byType" /> <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="byType" /> </beans>
Spring 在解決單例循環依賴時引入了三級緩存,以下所示:
// 一級緩存,存儲已經初始化完成的bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級緩存,存儲已經實例化完成的bean private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級緩存,存儲建立bean實例的ObjectFactory private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 按前後順序記錄已經註冊的單例bean private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
首先在建立A時,會進入到 doCreateBean()
方法(前面的流程能夠查看Spring IoC bean 的建立一文),以下:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 獲取bean的實例 BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // 經過構造函數反射建立bean的實例,可是屬性並未賦值 instanceWrapper = createBeanInstance(beanName, mbd, args); } // 獲取bean的實例 final Object bean = instanceWrapper.getWrappedInstance(); // 省略其它代碼... // bean的做用域是單例 && 容許循環引用 && 當前bean正在建立中 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); // 若是容許bean提早曝光 if (earlySingletonExposure) { // 將beanName和ObjectFactory造成的key-value對放入singletonFactories緩存中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 省略其它代碼... }
在調用 addSingletonFactory()
方法前A的實例已經建立出來了,只是還未進行屬性賦值和初始化階段,接下來將它放入了三級緩存中,以下:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); // 加鎖 synchronized (this.singletonObjects) { // 若是一級緩存中不包含當前bean if (!this.singletonObjects.containsKey(beanName)) { // 將ObjectFactory放入三級緩存 this.singletonFactories.put(beanName, singletonFactory); // 從二級緩存中移除 this.earlySingletonObjects.remove(beanName); // 將beanName加入到已經註冊過的單例bean緩存中 this.registeredSingletons.add(beanName); } } }
接下來A進行屬性賦值階段(會在後續文章中單獨分析這個階段),發現依賴B,便去獲取B,發現B尚未被建立,因此走建立流程;在B進入屬性賦值階段時發現依賴A,就去調用 getBean()
方法獲取A,此時會進入 getSingleton()
方法(該方法的調用流程在Spring IoC bean 的加載一文中分析過),以下:
public Object getSingleton(String beanName) { // allowEarlyReference設置爲true表示容許早期依賴 return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 先從一級緩存中,檢查單例緩存是否存在 Object singletonObject = this.singletonObjects.get(beanName); // 若是爲空,而且當前bean正在建立中,鎖定全局變量進行處理 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 從二級緩存中獲取 singletonObject = this.earlySingletonObjects.get(beanName); // 二級緩存爲空 && bean容許提早曝光 if (singletonObject == null && allowEarlyReference) { // 從三級緩存中獲取bean對應的ObjectFactory ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 調用預先設定的getObject(),獲取bean實例 singletonObject = singletonFactory.getObject(); // 放入到二級緩存中,並從三級緩存中刪除 // 這時bean已經實例化完但還未初始化完 // 在該bean未初始化完時若是有別的bean引用該bean,能夠直接從二級緩存中取出返回 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
嘗試一級緩存 singletonObjects
(確定沒有,由於A還沒初始化徹底),嘗試二級緩存 earlySingletonObjects
(也沒有),嘗試三級緩存 singletonFactories
,因爲A經過 ObjectFactory
將本身提早曝光了,因此B可以經過 ObjectFactory.getObject()
拿到A對象(雖然A尚未初始化徹底,可是總比沒有好呀)。B拿到A後順利建立並初始化完成,調用上面分析過的 addSingleton()
方法將本身放入一級緩存中。此時返回A中,A也能順利拿到徹底初始化的B進行後續的階段,最後也將本身放入一級緩存中,並從二級和三級緩存中移除。
過程圖以下所示:
對於非單例的 bean
,Spring 容器沒法完成依賴注入,由於 Spring 容器不進行緩存,所以沒法提早暴露一個建立中的 bean
。
本文主要介紹了 Spring 對三種循環依賴的處理,其實還有一種字段循環依賴,好比 @Autowired
註解標註的字段,但它和 setter
循環依賴的解決方法同樣,這裏就沒有多說。
最後,我模仿 Spring 寫了一個精簡版,代碼會持續更新。地址:https://github.com/leisurexi/tiny-spring。