循環(huán)依賴問題
什么是循環(huán)依賴
首先看一下下面的Spring配置文件
<!-- beanA依賴于beanB -->
<bean id="beanA" class="top.okay3r.ClassA">
<property name="beanB" ref="beanB"/>
</bean>
<!-- beanB依賴于beanA -->
<bean id="beanB" class="top.okay3r.ClassB">
<property name="beanA" ref="beanA"/>
</bean>
當IOC容器讀取上面的配置時蛙吏,就會先對beanA進行加載;在對beanA進行屬性填充時鞋吉,會發(fā)現(xiàn)beanA依賴于beanB鸦做,然后就會對beanB進行加載;當對beanB進行屬性填充時谓着,又會發(fā)現(xiàn)beanB依賴于beanA泼诱,于是就加載beanA...
可以想到,如果Spring的容器對于這種循環(huán)依賴問題不作出響應的處理赊锚,那么就會無限執(zhí)行上面的過程治筒。最終的結果就可能造成OOM從而導致程序崩潰
Spring中bean注入的方式
我們知道在Spring中屉栓,注入bean的方式有【構造器注入】和【setter注入】兩種方式。但在我們使用Spring管理bean時耸袜,可能會遇到一種特殊的情況系瓢,那么就是上面所說的循環(huán)依賴問題
我們再看一下Spring創(chuàng)建bean的過程
Spring創(chuàng)建bean的過程
如果閱讀過IOC相關的源碼就會知道,創(chuàng)建bean的過程大體可以分為初始化bean
句灌,對bean的屬性進行填充
夷陋,對bean進行初始化
三個步驟
- 初始化bean:即new一個bean實例,是通過反射調(diào)用構造器實現(xiàn)的
- 對bean的屬性進行填充:可以理解為對<property>標簽相應的屬性進行賦值
- 對bean進行初始化:即調(diào)用事先配置好的
init-method
方法胰锌,所以可以將一些初始化的行為寫到這個方法中
然后就來分析一下兩種注入方式
構造器注入
在普通的java程序中骗绕,如果已經(jīng)new出了一個對象,我們就知道這個對象已經(jīng)是可用的了资昧,不論它的屬性是否完整酬土。
但在Spring中,創(chuàng)建出來的bean必須要完成三個步驟才能被認為是可用的格带,才會將這個“完整”的bean放入到IOC容器中撤缴。
因為構造器注入是在實例化對象時反射調(diào)用構造器去注入?yún)?shù),所以既然beanA叽唱、beanB的都拿不到完整的依賴屈呕,就會進行無限的循環(huán)調(diào)用,從而無法解決【循環(huán)依賴問題】棺亭。解決辦法就只有是修改依賴關系了
setter注入
再看一下setter注入方式
setter注入方式就是new出一個對象后虎眨,調(diào)用該對象的set方法對屬性進行賦值。此時對象已經(jīng)被new出來了镶摘,只不過是不完整而已嗽桩。
如果出現(xiàn)了循環(huán)依賴的問題,這就要比構造器注入的方式好的多
所以Spring對于循環(huán)依賴問題的解決就是針對于setter方法的
接下來就開始分析Spring是如何解決循環(huán)依賴問題的
Spring對于循環(huán)依賴的解決
先提前知道一下問題大概是怎樣解決的
首先我們要知道凄敢,Spring對于循環(huán)依賴的問題是采用【緩存】的方式解決的
看一下Spring源碼中的DefaultSingletonBeanRegistry類(注:SingletonBeanRegistry接口提供了關于訪問單例bean的功能碌冶,DefaultSingletonBeanRegistry就是該接口的默認實現(xiàn))
/** 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涝缝,即只是new出來扑庞,并沒有屬性值的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);
大概捋一遍bean的獲取嫩挤、創(chuàng)建過程
因為循環(huán)依賴都是產(chǎn)生在獲取bean時害幅,所以我們直接從AbstractBeanFactory的getBean()方法開始
- AbstractBeanFactory#getBean()沒什么自身的實現(xiàn)消恍,只調(diào)用了doGetBean()
- AbstractBeanFactory#doGetBean(),在這個方法中調(diào)用了getSingleton(beanName)獲取實例:
Object sharedInstance = getSingleton(beanName);
- 判斷sharedInstance是否為null,如果不為null則調(diào)用getObjectForBeanInstance處理以现,然后返回狠怨。也就是IOC容器獲取bean成功约啊,可以拿去使用了发框。如果sharedInstance為null拙徽,則調(diào)用getSingleton(beanName,Object{...})方法
- DefaultSingletonBeanRegistry#getSingleton中,首先會從【一級緩存】中get一下bean谦去,如果獲取不到憎蛤,則會進入創(chuàng)建bean的流程
- 創(chuàng)建bean的主要邏輯就是走AbstractAutowireCapableBeanFactory#doCreateBean外傅,先是使用createBeanInstance方法創(chuàng)建bean的實例,然后對bean進行初始化俩檬,再進行屬性填充....然后返回bean
- 獲取到bean萎胰,完成
上面并沒有涉及到循環(huán)依賴和二級、三級緩存的問題棚辽,因為對于循環(huán)依賴的處理技竟,都表現(xiàn)在代碼中的細節(jié)之處
對應上面的過程,從源碼上開始分析
首先看doGetBean方法
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException{
// 從緩存中獲取單例bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) { //如果獲取到單例bean屈藐,則走下面代碼
//......
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}else {//如果沒有獲取到單例bean榔组,則走下面代碼
//......
// 如果是單例的Bean,請下面的代碼
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 創(chuàng)建單例Bean的主要方法联逻,返回的bean是完整的
return createBean(beanName, mbd, args);
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//......
}
return (T) bean;
}
}
上面的代碼中搓扯,sharedInstance是通過getSingleton()方法獲得的,實際上getSingleton(beanName)方法沒什么邏輯包归,內(nèi)部調(diào)用了getSingleton(beanName, boolean)這個方法擅编,所以接下來就進入到這個方法中
getSingleton(beanName, boolean)的實現(xiàn)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中獲取單例對象
Object singletonObject = this.singletonObjects.get(beanName);
// isSingletonCurrentlyInCreation : 判斷當前單例bean是否正在創(chuàng)建中,也就是沒有初始化完成箫踩。比如beanA的構造器依賴了beanB對象所以得先去創(chuàng)建B對象爱态,或者在A的populateBean過程中依賴了B對象,得先去創(chuàng)建B對象境钟,這時的beanA就是處于創(chuàng)建中的狀態(tài)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從二級緩存中獲取單例bean
singletonObject = this.earlySingletonObjects.get(beanName);
// allowEarlyReference :是否允許從singletonFactories中通過getObject拿到對象
if (singletonObject == null && allowEarlyReference) {
// 從三級緩存中獲取單例bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通過單例工廠獲取單例bean
singletonObject = singletonFactory.getObject();
// 從三級緩存移動到了二級緩存锦担,并移除singletonFactory
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
從上面的代碼中可以總結出以下幾點:
- 先從【一級緩存】中查找,有則直接返回
- 如果在【一級緩存】中獲取不到慨削,并且對象正在創(chuàng)建中(beanName包含在singletonsCurrentlyInCreation)洞渔,那么就再從【二級緩存】中查找,有則直接返回
- 如果還是獲取不到缚态,且允許singletonFactories(allowEarlyReference=true)通過getObject()獲取磁椒,就從【三級緩存】中獲取(
singletonFactory.getObject()
)玫芦。通過ObjectFactory獲取到的對象浆熔,是進行代理后的對象(假設有AOP)。將從【三級緩存】中獲取到的對象放到【二級緩存】中桥帆,同時刪除此beanName對應的【三級緩存數(shù)據(jù)】
再看一下doGetBean()方法中剛剛沒有講到的“if-else”部分
如果getSingleton()方法獲取到了bean医增,即sharedInstance不為null慎皱,則對其進行處理然后返回
如果sharedInstance為null,就要走else中的代碼了
首先判斷一下是否為單例茫多,(mbd是通過讀取配置文件中bean標簽生成的bean的定義信息,具體獲得的方法這里不詳細說了)忽刽。因為多例的bean是不需要放入到IOC容器中的天揖,所以這里只處理單例bean
如果為單例,則調(diào)用getSingleton(String beanName, ObjectFactory<?> singletonFactory)
方法
getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// ......
// 創(chuàng)建 bean 實例
singletonObject = singletonFactory.getObject();
newSingleton = true;
if (newSingleton) {
// 添加新創(chuàng)建的bean添加到【一級緩存】中跪帝,并刪除其他緩存中對應的bean
addSingleton(beanName, singletonObject);
}
// ......
// 返回 singletonObject
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 將新創(chuàng)建的bean添加到【一級緩存】中
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
// 從其他緩存中移除相關的bean
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
上面的代碼主要包含了兩個功能
- 獲取完整的bean實例
- 將新的bean添加到【一級緩存】中宝剖,以后getBean的時候就可以直接獲取了
可以看到bean實例是由singletonFactory.getObject()
拿到的,也就是通過doGetBean()
方法中判斷是否單例后的匿名內(nèi)部類獲取到的歉甚,從而知道獲取到的bean是由createBean()方法創(chuàng)建的
creatBean()方法調(diào)用了doCreatBean()方法万细,所以實際的創(chuàng)建邏輯就再doCreatBean()中
doCreatBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 默認調(diào)用無參構造實例化Bean
// 構造方法的依賴注入,就是發(fā)生在這一步
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 實例化后的Bean對象纸泄,這里獲取到的是一個原始對象赖钞,即沒有進行屬性填充的對象
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
//......
// 解決循環(huán)依賴的關鍵步驟
// earlySingletonExposure:是否”提前暴露“原始對象的引用
// 因為不論這個bean是否完整,他前后的引用都是一樣的聘裁,所以提前暴露的引用到后來也指向完整的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 如果需要提前暴露單例bean雪营,則將該bean工廠放入【三級緩存】中
if (earlySingletonExposure) {
// 將剛創(chuàng)建的bean工廠放入三級緩存中singleFactories(key是beanName,value是FactoryBean)
// 同樣也會移除【二級緩存】中對應的bean衡便,即便沒有
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充屬性(依賴注入)
populateBean(beanName, mbd, instanceWrapper);
//調(diào)用初始化方法献起,完成bean的初始化操作
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//......
return exposedObject;
}
ok,看到這里镣陕,整個在有循環(huán)依賴問題下創(chuàng)建谴餐、獲取bean的流程就結束了
舉個例子,從頭串一下流程呆抑。假設beanA->beanB, beanB->beanA岂嗓,即A、B相互依賴
- 調(diào)用doGetBean()方法鹊碍,想要獲取beanA厌殉,于是調(diào)用getSingleton()方法從緩存中查找beanA
- 在getSingleton()方法中,從一級緩存中查找侈咕,沒有公罕,返回null
- doGetBean()方法中獲取到的beanA為null,于是走對應的處理邏輯耀销,調(diào)用getSingleton()的重載方法(參數(shù)為ObjectFactory的)
(ps:現(xiàn)在是2020.2.14 凌晨1點07分楼眷,情人節(jié)了,因為疫情不能和小楊一起,在我的第一篇博客中紀念一下這個節(jié)日??摩桶,祝所有人情人節(jié)快樂) - 在getSingleton()方法中桥状,先將beanA_name添加到一個集合中帽揪,用于標記該bean正在創(chuàng)建中硝清。然后回調(diào)匿名內(nèi)部類的creatBean方法
- 進入AbstractAutowireCapableBeanFactory#doCreateBean,先反射調(diào)用構造器創(chuàng)建出beanA的實例转晰,然后判斷:是否為單例芦拿、是否允許提前暴露引用(對于單例一般為true)、是否正在創(chuàng)建中(即是否在第四步的集合中)查邢。判斷為true蔗崎,則將beanA添加到【三級緩存】中
- 對beanA進行屬性填充,此時檢測到beanA依賴于beanB扰藕,于是開始查找beanB
- 調(diào)用doGetBean()方法缓苛,和上面beanA的過程一樣,到緩存中查找beanB邓深,沒有則創(chuàng)建未桥,然后給beanB填充屬性
- 此時beanB依賴于beanA,調(diào)用getSingleton()獲取beanA芥备,依次從一級冬耿、二級、三級緩存中找萌壳,此時從三級緩存中獲取到beanA的創(chuàng)建工廠,通過創(chuàng)建工廠獲取到singletonObject袱瓮,此時這個singletonObject指向的就是上面在doCreateBean()方法中實例化的beanA
- 這樣beanB就獲取到了beanA的依賴,于是beanB順利完成實例化尺借,并將beanA從三級緩存移動到二級緩存中
- 隨后beanA繼續(xù)他的屬性填充工作,此時也獲取到了beanB褐望,beanA也隨之完成了創(chuàng)建,回到getSingleton()方法中繼續(xù)向下執(zhí)行瘫里,將beanA從二級緩存移動到一級緩存中
最后
整個過程大概就是這樣了,由于spring的源碼比較多谨读,就只挑選了重點部分進行注釋
其實主要思想就是利用二級、三級緩存對未初始化完成的bean進行提前的引用暴露,也就是將其設置為可引用的铐尚,這樣當依賴于他的bean在進行屬性填充時就可以直接拿到引用,解決了死循環(huán)的問題
============================================================
還有幾個比較重要的點宣增,在這里指出位置玫膀,可以根據(jù)這些去查找看
- 這三個級別的緩存,在同一時間爹脾,同一beanName對應的bean只會存在于一個緩存中
- 如果沒有循環(huán)依賴的問題帖旨,二級、三級緩存是沒有用處的灵妨,體現(xiàn)在AbstractAutowireCapableBeanFactory#doCreateBean的判斷earlySingletonExposure這個地方
- 判斷循環(huán)依賴解阅,是用一個Set集合實現(xiàn)的,正在創(chuàng)建中的beanName會加到這個集合中
- 三級緩存其實還有創(chuàng)建AOP代理的功能泌霍,在AbstractAutowireCapableBeanFactory#createBean調(diào)用resolveBeforeInstantiation的位置货抄。而如果沒有循環(huán)依賴問題,那么代理就是在調(diào)用init-method過程中創(chuàng)建的
- bean實例化之后朱转,屬性填充之前蟹地,如果有循環(huán)依賴,就將這個bean封裝到一個ObjectFactory然后放到三級緩存中(為了提前暴露引用)
- 三級緩存中的ObjectFactory第一次拿出被他保存bean后肋拔,這個bean就會進入二級緩存
- bean被創(chuàng)建完整后锈津,進入一級緩存
》》》》》》》》》》》》》》》》》》》》》
有些東西不知道怎么轉述成語言表達出來,還有如果有不好的或者說錯的地方希望看過的大佬能幫忙指正凉蜂,謝謝~~