一、背景
在最近的業務需求開發過程中遇到了“傳說中”的循環依賴問題,在之前學習Spring的時候經常會看到Spring是如何解決循環依賴問題的,所謂循環依賴即形成了一個環狀的依賴關系,這個環中的某一個點產生不穩定變化都會導致整個鏈路產生不穩定的變化;此外循環依賴還會導致應用程序啟動失敗、內存溢出、甚至出現一些難以排查的問題,于是便系統性的對該問題進行學習和總結并整理文章如下。
二.、循環依賴
2.1. 什么是循環依賴?
循環依賴指的是多個對象之間的依賴關系形成了一個閉環。圖1、2分別是兩個對象和多個對象形成循環依賴圖示,實際編程中由于依賴層次深、關系復雜等因素,導致依賴關系難以清晰梳理。
圖1:兩個對象間的循環依賴
圖2:多個對象間的循環依賴
2.2. 為什么會產生循環依賴?
Spring創建bean的本質還是創建對象,一個完整的對象包含兩部分:當前對象的實例化和對象屬性的實例化。在Spring中,對象的實例化是通過反射實現的,而對象的屬性則是在對象實例化之后通過一定的方式設置的。
圖3:Spring創建Bean流程
接下來以Demo中的類A、B為例描述循環依賴產生過程:
Demo中的類A、B中各自都以對方為自己的全局屬性,并且在Spring中實例化bean是通過ApplicationContext.getBean()方法來進行的。
如果獲取的對象依賴了另一個對象,那么會首先創建當前對象,然后通過遞歸的調用ApplicationContext.getBean()方法來獲取所依賴的對象,最后將獲取到的對象注入到當前對象中。
@Component
public class A {private B b;public void setB(B b) {this.b = b;}
}
@Component
public class B {private A a;public void setA(A a) {this.a = a;}
}
此處以Demo中初始化A對象為例,
第一步:首先Spring嘗試通過ApplicationContext.getBean()方法獲取A對象的實例,由于Spring容器中還沒有A對象實例,因而其會創建一個A對象,然后發現其依賴了B對象,所以會嘗試遞歸的通過ApplicationContext.getBean()方法獲取B對象的實例,但是Spring容器中此時也沒有B對象的實例,因而其還是會先創建一個B對象的實例。( 此時A對象和B對象都已經創建了,并且保存在Spring容器中了,只不過A對象的屬性b和B對象的屬性a都還沒有設置進去。)
第二步:在前面Spring創建B對象之后,Spring發現B對象依賴了屬性a,因而此時還是會嘗試遞歸的調用ApplicationContext.getBean()方法獲取A對象的實例,因為Spring中已經有一個A對象的實例,雖然只是半成品(其屬性b還未初始化),但其也還是目標bean,因而會將該A對象的實例返回。(此時,B對象的屬性a就設置進去了,然后還是ApplicationContext.getBean()方法遞歸的返回,也就是將B對象的實例返回,此時就會將該實例設置到A對象的屬性b中。)
第三步:在上面這個遞歸過程的最后,Spring將獲取到的B對象實例設置到了A對象的屬性b中了,這里的A對象其實和前面設置到實例B中的半成品A對象是同一個對象,其引用地址是同一個,這里為A對象的b屬性設置了值,其實也就是為那個半成品的a屬性設置了值。
實際加載過程流程圖如圖4所示:其中圖中getBean()表示調用Spring的ApplicationContext.getBean()方法,而該方法中的參數,則表示我們要嘗試獲取的目標對象。圖中的黑色箭頭表示一開始的方法調用走向,走到最后,返回了Spring中緩存的A對象之后,表示遞歸調用返回了,此時使用紅色的箭頭表示。從圖中我們可以很清楚的看到,B對象的a屬性是在第三步中注入的半成品A對象,而A對象的b屬性是在第二步中注入的成品B對象,此時半成品的A對象也就變成了成品的A對象,因為其屬性已經設置完成了。
圖4:Bean加載流程圖
三、Spring緩存機制
3.1. Spring是如何解決循環依賴的?
Spring在DefaultSingletonBeanRegistry類中維護了三個Map,也就是我們通常說的三級緩存。
singletonObjects (一級緩存) 它是我們最熟悉的朋友,俗稱“單例池”“容器”,緩存創建完成單例Bean的地方。
earlySingletonObjects(二級緩存) 映射Bean的早期引用,也就是說在這個Map里的Bean不是完整的,甚至還不能稱之為“Bean”,只是一個Instance。
singletonFactories(三級緩存) 映射創建Bean的原始工廠。
圖5:Spring三級緩存
3.2. Spring源碼之“獲取Bean
接下來結合具體的Spring源碼進行分析,首先分析“獲取Bean”的源碼,注意getSingleton()方法。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {//第1級緩存 用于存放 已經屬性賦值、完成初始化的 單例Beanprivate final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//第2級緩存 用于存在已經實例化,還未做代理屬性賦值操作的 單例Beanprivate final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//第3級緩存 存儲創建單例Bean的工廠private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//已經注冊的單例池里的beanNameprivate final Set<String> registeredSingletons = new LinkedHashSet<>(256);//正在創建中的beanName集合private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));//緩存查找bean 如果第1級緩存沒有,那么從第2級緩存獲取。如果第2級緩存也沒有,那么從第3級緩存創建,并放入第2級緩存。protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName); //第1級if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName); //第2級if (singletonObject == null && allowEarlyReference) {//第3級緩存 在doCreateBean中創建了bean的實例后,封裝ObjectFactory放入緩存的bean實例ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//創建未賦值的beansingletonObject = singletonFactory.getObject();//放入到第2級緩存this.earlySingletonObjects.put(beanName, singletonObject);//從第3級緩存刪除this.singletonFactories.remove(beanName);}}}}return singletonObject;}
}
3.3. Spring源碼之“添加到第1級緩存”
其中“添加到第1級緩存”的源碼為:
protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {// 放入第1級緩存this.singletonObjects.put(beanName, singletonObject);// 從第3級緩存刪除this.singletonFactories.remove(beanName);// 從第2級緩存刪除this.earlySingletonObjects.remove(beanName);// 放入已注冊的單例池里this.registeredSingletons.add(beanName);}}
添加到“第3級緩存”的源碼為:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {// 若第1級緩存沒有bean實例if (!this.singletonObjects.containsKey(beanName)) {// 放入第3級緩存this.singletonFactories.put(beanName, singletonFactory);// 從第2級緩存刪除,確保第2級緩存沒有該beanthis.earlySingletonObjects.remove(beanName);// 放入已注冊的單例池里this.registeredSingletons.add(beanName);}}}
“創建Bean”的源碼為下面所示,通過這段代碼,我們可以知道:Spring 在實例化對象之后,就會為其創建一個 Bean 工廠,并將此工廠加入到三級緩存中。
因此,Spring 一開始提前暴露的并不是實例化的 Bean,而是將 Bean 包裝起來的ObjectFactory。為什么要這么做呢?
這實際上涉及到 AOP。如果創建的 Bean 是有代理的,那么注入的就應該是代理 Bean,而不是原始的 Bean。但是,Spring一開始并不知道 Bean是否會有循環依賴,通常情況下(沒有循環依賴的情況下),Spring 都會在“完成填充屬性并且執行完初始化方法”之后再為其創建代理。但是,如果出現了循環依賴,Spring 就不得不為其提前創建"代理對象";否則,注入的就是一個原始對象,而不是代理對象。因此,這里就涉及到"應該在哪里提前創建代理對象。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {BeanWrapper instanceWrapper = null;if (instanceWrapper == null) {//實例化對象instanceWrapper = this.createBeanInstance(beanName, mbd, args);}final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;//判斷是否允許提前暴露對象,如果允許,則直接添加一個 ObjectFactory 到第3級緩存boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {//添加到第3級緩存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}//填充屬性this.populateBean(beanName, mbd, instanceWrapper);//執行初始化方法,并創建代理exposedObject = initializeBean(beanName, exposedObject, mbd);return exposedObject;
}
Spring通過在ObjectFactory中去提前創建代理對象,該對象會執行getObject()方法來獲取Bean。執行方法如下:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);// 記錄已被代理的對象this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);}
}
提前進行對象的代理工作,并在 earlyProxyReferences map中記錄已被代理的對象,是為了避免在后面重復創建代理對象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);// 記錄已被代理的對象this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);}
}
再次分析獲取bean的方法getSingleton()方法,可知:提前暴露的對象,雖然已實例化,但是沒有進行屬性填充,還沒有完成初始化,是一個不完整的對象。 這個對象存放在二級緩存中,對于三級緩存機制十分重要,是解決循環依賴一個非常巧妙的設計。接下來我們結合Spring緩存機制來分析上面Demo中A、B循環依賴。
A 調用doCreateBean()創建Bean對象:由于還未創建,從第1級緩存singletonObjects查不到,此時只是一個半成品(提前暴露的對象),放入第3級緩存singletonFactories。
A在屬性填充時發現自己需要B對象,但是在三級緩存中均未發現B,于是創建B的半成品,放入第3級緩存singletonFactories。
B在屬性填充時發現自己需要A對象,從第1級緩存singletonObjects和第2級緩存earlySingletonObjects中未發現A,但是在第3級緩存singletonFactories中發現A,將A放入第2級緩存earlySingletonObjects,同時從第3級緩存singletonFactories刪除。
將A注入到對象B中。
B完成屬性填充,執行初始化方法,將自己放入第1級緩存singletonObjects中(此時B是一個完整的對象),同時從第3級緩存singletonFactories和第2級緩存earlySingletonObjects中刪除。
A得到“對象B的完整實例”,將B注入到A中。
A完成屬性填充,執行初始化方法,并放入到第1級緩存singletonObjects中。
在創建過程中,都是從第三級緩存(對象工廠創建不完整對象),將提前暴露的對象放入到第二級緩存;從第二級緩存拿到后,完成初始化,并放入第一級緩存。