【Spring系列】- Spring迴圈依賴

怒放吧德德 發表於 2022-12-04
Spring

Spring迴圈依賴

😄生命不息,寫作不止
🔥 繼續踏上學習之路,學之分享筆記
👊 總有一天我也能像各位大佬一樣
🏆 一個有夢有戲的人 @怒放吧德德
🌝分享學習心得,歡迎指正,大家一起學習成長!

image

什麼是迴圈依賴?

什麼是迴圈依賴呢?簡單來說就是beanA依賴於beanB,beanB依賴於beanA(也就是A類中使用了B類,B類使用了A類)。在bean建立的生命週期中,當建立了beanA的時候,會檢索A物件內部中需要填充的物件,發現A中是需要用到B物件,會先去單例池中尋找,如果沒有找到,就會去建立B的bean物件。在beanB的生命週期中,建立方式依然是相同的,因此也會去填充beanA,發現單例池中並沒有A的bean物件,這樣就造成了迴圈依賴。
image
我們可以看看兩個相互依賴的bean的生命週期
image

如何解決迴圈依賴

兩個bean物件在還沒有建立成bean物件就由於相互的依賴而進入的迴圈,那麼,要如何去解開他們的迴圈呢?在spring中會使用三級快取。在springboot中有許多的解決方法,比如加配置,加註解等等操作。

打破迴圈依賴

想要打破迴圈依賴,就只需要一個快取即可,也就是使用一個Map集合。簡單的說就是將所需的物件存入裡面,在檢測單例池沒有所需的bean物件的時候,就透過beanName去查詢一級快取,這裡要特別注意的是,一級快取此時儲存的是普通物件,是透過構造方法例項化的物件。
image
使用map來儲存物件,可見能夠解除迴圈依賴,但是這裡儲存的是普通物件,而不是一個代理物件。那麼要如何解決呢?

提前AOP

可以透過提前AOP來得到代理物件,再把代理物件存到map集合中。在例項化普通物件後,直接進行AOP,生成代理物件,再將代理物件存到map集合中,然後在後面也就不需要進行aop了。但是,並不是每次都將AOP提前的,是在發現了迴圈依賴才是需要去將AOP提前。

判斷是否迴圈依賴

要判斷是否迴圈依賴也不是很難,就只需要一個集合creatingSet<'beanName'>。在例項化物件之前將這個bean的名字存到set集合中,表示這個bean物件正在建立中,等待依賴他的物件例項化後,會去判斷set集合中是否有所依賴的beanName,如果有的話,就表示出現了迴圈依賴,這樣就可以進行提前AOP。然而,在bean物件建立完畢並且新增到單例池之後,就要去吧set集合把這個beanName移除掉,不然就會造成每次都需要提前AOP。

一級快取

一級快取singletonObjects存放的是已經初始化好的bean,即已經完成初始化好的注入物件的代理

二級快取

透過提前AOP獲得的代理物件,是不能夠直接加入到單例池中的。因為生成的代理物件是需要利用到普通物件的,而這時候的普通物件是不完整的(沒有透過完整的生命週期),裡面的屬性可能是為空的。也會造成這個代理物件不是單例的。我們可以採用二級快取earlySingletonObjects,將提前AOP生成的代理存放到earlySingletonObjects集合中。
在填充屬性的時候,先到單例池尋找,沒找到就去creatingSet看是否存在迴圈依賴,存在迴圈依賴之後就去earlySingletonObjects尋找是否有代理物件,沒有再去提前AOP建立代理物件,並存入快取中。在最後填充其他屬性之後從二級快取中去get代理物件,存入單例池中。

三級快取

打破迴圈並不是只透過二級快取,而是還需要一個三級快取singletonFactory。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));spring在建立bean的時候就會生成lambda表示式,存到三級快取中。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
			exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

而執行lambda表示式就會去執行wrapIfNecessary這個方法

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}

這個方法就能夠去建立代理物件

Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;

三級快取主要才是用來解決迴圈依賴。在例項化物件的時候就將lambda表示式存入三級快取中,singletonFActories.put("beanName", addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)));這個表示式的bean實際上就是普通物件,最後會生成的是代理物件。當遇到迴圈依賴的時候,就會先去二級快取找再去三級快取找,並且一定會找到,找到的是lambda表示式,然後去執行lambda表示式去生成代理物件最後當道二級快取中。

博文推薦

Spring 迴圈依賴及三級快取_程式源程式的部落格-CSDN部落格_三級快取


👍創作不易,如有錯誤請指正,感謝觀看!記得點贊哦!👍