Spring大白話之三級緩存如何解決循環(huán)依賴問題
前言
在使用Spring 開發(fā)過程中,我們需要對定義后的bean 通過構(gòu)造方法,或者bean 注入的方式注入到某個類中進而使用改bean 對應(yīng)的方法,在此過程中就會出現(xiàn)一個類中注入了n多個bean,幾個類中bean 相互注入,出現(xiàn) A 依賴B,B依賴C,C又依賴A/B 這種循環(huán)情況的出現(xiàn)。
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、Spring 循環(huán)依賴是什么?
循環(huán)依賴是指在Spring容器中,存在兩個或多個Bean之間的互相依賴關(guān)系,形成了一個閉環(huán)的依賴鏈。具體來說,當Bean A依賴Bean B,同時Bean B又依賴Bean A時,就產(chǎn)生了循環(huán)依賴。循環(huán)依賴可能會導(dǎo)致以下問題:
死鎖:如果循環(huán)依賴的解析過程不正確,可能會導(dǎo)致死鎖。當容器無法確定如何先實例化哪個Bean時,可能會造成死鎖情況,導(dǎo)致應(yīng)用程序無法繼續(xù)正常執(zhí)行。
未完全初始化的Bean:在解決循環(huán)依賴時,Spring使用了代理對象來解決依賴問題。這意味著當Bean A注入到Bean B中時,A可能是一個未完全初始化的代理對象,而不是完全實例化,屬性注入,初始化的對象。這可能導(dǎo)致在早期階段的Bean存在一些限制和潛在的問題。
為了解決循環(huán)依賴的問題,Spring使用了一個兩階段的解析過程:實例化階段和注入階段。在實例化階段,Spring創(chuàng)建對象并將其放入緩存中;在注入階段,Spring解決依賴關(guān)系并完成注入。
SpringBoot 從 2.6 之前默認開啟循環(huán)依賴,之后 開始默認不允許出現(xiàn) Bean 循環(huán)引用,如果需要則進行手動開啟:
spring: main: allow-circular-references:true
二、Spring 三級緩存解決單例的循環(huán)依賴
Spring 三級緩存 實際上使用了3個Map 來打破單例對象循環(huán)依賴的問題,singletonObjects:這是一級緩存,保存已經(jīng)實例化且完成了所有的依賴注入和初始化的單例bean實例。earlySingletonObjects:這是二級緩存,保存已經(jīng)實例化但尚未完成所有的依賴注入和初始化的單例bean實例。
它主要用于解決屬性注入時的循環(huán)依賴問題。singletonFactories:這是三級緩存,保存創(chuàng)建單例bean實例的ObjectFactory。
2.1 Bean 單例對象生成的過程
對象的生成必須先通過其構(gòu)造方法進行實例化,然后對其屬性賦值,然后執(zhí)行初始化;以下以 Aservice ,Bservice,Cservice 為例進行研究;
public interface Aservice { } public interface Bservice { } public interface Cservice { } @Service public class AserviceImpl implements Aservice { @Autowired private Bservice bservice; } @Service public class BserviceImpl implements Bservice { @Autowired private Aservice aservice; } @Service public class CserviceImpl implements Cservice { @Autowired private Aservice aservice; @Autowired private Bservice bservice; }
其中Aservice 依賴Bservice 的bean ,Bservice 依賴Aservice 的bean,Cservice 依賴Aservice ,Bservice 的bean ;
思考循環(huán)依賴的注入過程:
- 首先 A 實例化,通過A 類的構(gòu)造方法進行 實例的構(gòu)建并返回; 對其中的bservice 屬性值進行依賴注入;
- 此時需要先從單例池中去獲取,Bservice 的bean ,Bservice 的bean 還沒有被創(chuàng)建,所以此時需要先創(chuàng)建Bservice 的bean ;
- 調(diào)用Bservice 的構(gòu)造方法進行實例的創(chuàng)建,然后對Bservice 進行屬性注入時需要 對Aservice 進行 屬性值設(shè)置;
然后在從單例池中獲取Aservice 的bean,發(fā)現(xiàn)沒有Aservice 的bean ,這個時候如果重復(fù)在走Aservice 的bean 創(chuàng)建過程就會陷入死循環(huán)
,顯然我們的項目此時是可以成功啟動的,也即沒有陷入死循環(huán)中,那么Spring 是怎么解決的?
如果說在創(chuàng)建Aservice 的bean時是分為兩步:
- 步驟1: 先通過其構(gòu)造方法完成實例化;
- 步驟2: 對其屬性進行填充;
那么如果我們在步驟1 之后 ,就將還沒有完成初始化的Aservice 的bean 放入到某個地方,然后在初始化其他bean 的時候 如果發(fā)現(xiàn)依賴了Aservice 的bean 此時可以直接注入Aservice 的bean完成對其的引用,即使Aservice 的bean還沒有進行完整的初始化,我們對Aservice 的bean 進行了提前暴露;
這樣在Aservice 的bean真正完成初始化之后,對Aservice 的bean引用也隨即完成;這樣就打破了bean 的循環(huán)依賴,bean 可以正常初始化了;
現(xiàn)在Bservice,Cservice 中都依賴了Aservice 的bean ,顯然無法對Aservice 的單例bean 實例化兩次 ,那么就需要有個地方來存放Aservice 的這個還沒有完全初始化的bean,這樣后續(xù)其它的bean 在注入Aservice 的bean 時 會發(fā)現(xiàn) Aservice 的bean 已經(jīng)有了,所以就可以直接使用,不需要在額外創(chuàng)建,這里spring 使用 map (二級緩存)來存放已經(jīng)實例化,但是還沒有完全初始化的 bean , 以便于在發(fā)生循環(huán)依賴時,如果從單例池中獲取不到對應(yīng)的bean 就到二級緩存中在獲取一次,如果獲取到了可以直接使用,如果獲取不到則需要去生成這個bean 并將其放入到二級緩存中;
因為Bservice 中已經(jīng)將Aservice的bean 放入到了二級緩存中,所以Cservice 可以直接從二級緩存中獲取到service 的單例bean ;
到此看起來spring 已經(jīng)通過二級緩存來提前暴露未初始化完成的bean 而解決了循環(huán)依賴,那么為什么還有三級緩存的概念?
在原碼中可以看到三級緩存也是一個map ,并且其value 存的是一個對象的工廠
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
我們可能已經(jīng)聽說過三級緩存放入的是Lambda表達式 的匿名函數(shù),這個函數(shù)會在使用到的時候被調(diào)用,那么spring 為什么選擇放一個匿名函數(shù)而不是直接放入一個bean 呢;顯然如果直接放入一個bean 那么三級緩存的作用就和二級緩存相同了;所以spring 這樣做肯定是有一些原因的,先來看下在原碼中三級緩存放入的Lambda是什么:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; SmartInstantiationAwareBeanPostProcessor bp; if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) { for(Iterator var5 = this.getBeanPostProcessorCache().smartInstantiationAware.iterator(); var5.hasNext(); exposedObject = bp.getEarlyBeanReference(exposedObject, beanName)) { bp = (SmartInstantiationAwareBeanPostProcessor)var5.next(); } } return exposedObject; }
從以上代碼可以執(zhí)行先把初始的 bean 對象賦給了 exposedObject ,然后如果發(fā)現(xiàn)這個bean 是否滿足mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()
條件,進而判斷當前的bean是否是一個合成的代理對象。
在AOP中,合成代理對象是通過特定的機制(如JDK動態(tài)代理或CGLIB動態(tài)代理)創(chuàng)建的。這個條件可以用來檢查當前創(chuàng)建的bean是否是這種合成的代理對象;
如果需要合成代理對象則 進入exposedObject = bp.getEarlyBeanReference(exposedObject, beanName) 進行代理對象的創(chuàng)建:
AbstractAutoProxyCreator.getEarlyBeanReference
// 省略代碼 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 exposedObject = bean; try { // 遞歸填充改bean 的其他屬性 this.populateBean(beanName, mbd, instanceWrapper); exposedObject = this.initializeBean(beanName, exposedObject, mbd); } catch (Throwable var18) { if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) { throw (BeanCreationException)var18; } throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18); } // 省略代碼 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) { Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } else { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } } else { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } }
從上面代碼可以看到 如果這個對象在緩存中沒有而且是必須要進行依賴注入的, 會對實例化化后的改對象,使用this.createProxy 為其生產(chǎn)代理對象并進行返回;所以使用了這個Lambda表達式目的就是返回一個普通對象的bean 或者代理對象的bean,那么為什么不直接在bean 實例化之后,就直接調(diào)用getEarlyBeanReference 方法,這樣將生成的普通對象或者代理對象的bean 直接放入到二級緩存中,這樣豈不是更為直接,顯然這樣做也是可以解決spring 的循環(huán)依賴的問題,而且在二級緩存中存放的對象就是普通對象或者代理生成的對象。
雖然可以這樣做但是違反了spring 對代理對象生成的原則,Spring 的設(shè)計原則是盡可能保證普通對象創(chuàng)建完成之后,再生成其 AOP 代理(盡可能延遲代理對象的生成),因為這樣做的話,所有代理都提前到了實例化之后,初始化階段前,顯然與盡可能延遲代理對象的生成 原則是違背的。所以在此使用 Lambda表達式 ,在真正需要創(chuàng)建對象bean 的提前引用時,才通過 Lambda表達式 來進行創(chuàng)建 ,來遵循盡可能延遲代理對象的生成 原則。
沒有依賴,有AOP 這種情況中,我們知道 AOP 代理對象的生成是在成品對象創(chuàng)建完成之后創(chuàng)建的,這也是 Spring 的設(shè)計原則,代理對象盡量推遲創(chuàng)建,循環(huán)依賴 + AOP 這種情況中, 代理對象的生成提前了,因為必須要保證其 AOP 功能,那么在bean 初始化完成之后,又到了要對改對象進行代理增強的環(huán)節(jié),此時spring 又是怎么判斷改bean 已經(jīng)被增強為代理對象,而不需要重新創(chuàng)建代理對象?
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = this.getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return this.wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } 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); }
從以上源碼中可以看到 在postProcessAfterInitialization bean 被初始化完成之后執(zhí)行的方法 從this.getCacheKey 獲取到的cacheKey ,最后比較兩個bean 是否是一個,如果是則說明bean 已經(jīng)進行過代理,否則則重新執(zhí)行wrapIfNecessary 生成代理對象;
2.2 三級緩存工作過程
既然在spring 容器中bean 是單例的,那么就不可能存在改bean 的多個對象,也即對bean 的所有引用都指向同一個對象;此時就有一個問題,當一個bean 被依賴注入時,怎么知道這個單例的bean 是否已經(jīng)被初始化?
所以就需要將已經(jīng)完成初始化的bean 放入到一個地方中,這個地方要滿足如果這個bean 已經(jīng)被初始化過了,則不需要進行在進行初始化,而且存和取都比較方便,spring 選用map 來對其進行存儲,其中key 為bean的那么,value 為單列bean:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
當Spring創(chuàng)建一個單例bean時,會先檢查一級緩存(singletonObjects)
,如果在其中找到bean實例,則直接返回。如果沒有找到,則繼續(xù)執(zhí)行bean的創(chuàng)建流程,對改bean 進行實例化,如果改bean允許早期暴露則將獲取改對象的一個方法即將工廠方法存儲在三級緩存(singletonFactories)中
。
在創(chuàng)建bean 如aservice的屬性注入過程中,如果發(fā)現(xiàn)還依賴了其它的bean,如:bservice 則進入到bservice 的創(chuàng)建過程:首先對bservice 進行實例化(如果bservice也運行早期暴露,則也會被加入到第三級緩存中)、屬性注入、初始化;
如果此時bservice 中依賴了aservice 的bean,出現(xiàn)循環(huán)依賴問題,先從一級緩存中獲取aservice 的bean,此時aservice的bean 是正在被創(chuàng)建的狀態(tài),則從二級緩存找 Spring會先嘗試從二級緩存(earlySingletonObjects)
中獲取bean實例的早期引用,以解決循環(huán)依賴,如果二級緩存中aservice 的早起引用不存在;則到三級緩存中,執(zhí)行aservice bean 的lamma 表達式,獲取到aservice 的普通對象/代理對象,并將其放入到第二級緩存,同時移除aservice 在第三級的緩存。
一旦bean創(chuàng)建完成,如:bservice 完成了依賴注入,初始化,aop(如果需要被代理)則會將其放入一級緩存(singletonObjects)
,并從二級緩存(earlySingletonObjects)
和三級緩存(singletonFactories)
中移除,即將bservice 放入到一級緩存(單例池中),同時從二級和三級緩存中移除bservice的早期應(yīng)用和 獲取早期引用的lamma 表達式。然后,在aservice 的屬性依賴注入中,就可以從一級緩存中獲取到bservice 的單例對象,進行屬性注入,然后初始化,和aop(如果需要被aop代理),最后從二級和三級緩存中移除aservice的早期應(yīng)用和 獲取早期引用的lamma 表達式。
AbstractBeanFactory,doGetBean
獲取對應(yīng)的bean,此處只列舉關(guān)鍵代碼
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { /** ** 省略代碼 **/ // 會先檢查一級緩存(singletonObjects),如果在其中找到bean實例,則直接返回, // 此方法調(diào)用DefaultSingletonBeanRegistry 下getSingleton 方法 // 方法(1) 從三級緩存中獲取對象 Object sharedInstance = this.getSingleton(beanName); // 省略 sharedInstance 不為null 直接返回的代碼 /** ** 省略代碼 **/ // 如果沒有找到,則繼續(xù)執(zhí)行bean的創(chuàng)建流程,并將工廠方法存儲在三級緩存(singletonFactories)中 if (mbd.isSingleton()) { // this.getSingleton 通過改bean 的工廠方法創(chuàng)建出來bean 并放入到單例池中 // 方法2:getSingleton(String beanName, ObjectFactory<?> singletonFactory) 創(chuàng)建對象 sharedInstance = this.getSingleton(beanName, () -> { try { // 創(chuàng)建普通對象/代理對象 并將工廠方法存儲在三級緩存(singletonFactories)中 // 此方法調(diào)用 AbstractAutowireCapableBeanFactory 下 createBean 方法然后在調(diào)用 doCreateBean 方法 return this.createBean(beanName, mbd, args); } catch (BeansException var5) { this.destroySingleton(beanName); throw var5; } }); beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } }
方法(1) 從三級緩存中獲取對象 this.getSingleton(beanName) 方法:
Object sharedInstance = this.getSingleton(beanName)
方法,DefaultSingletonBeanRegistry
下getSingleton
, 會先檢查一級緩存(singletonObjects),如果在其中找到bean實例,則直接返回:
@Nullable public Object getSingleton(String beanName) { return this.getSingleton(beanName, true); } @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從一級緩存獲取完整的bean Object singletonObject = this.singletonObjects.get(beanName); // 沒有獲取到 并且 這個bean 正在初始化中 if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { // 從二級緩存獲取這個bean 的早期引用 singletonObject = this.earlySingletonObjects.get(beanName); // 沒有早期引用 并且運行循環(huán)依賴 if (singletonObject == null && allowEarlyReference) { synchronized(this.singletonObjects) { // 加對象鎖 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 從三級緩存 獲取bena 的 工廠類 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 二級緩存中放入 this.earlySingletonObjects.put(beanName, singletonObject); // 三級緩存中移除 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
單例池中沒有改bean 則進入 sharedInstance = this.getSingleton(beanName, () -> {}) return this.createBean(beanName, mbd, args) 方法
通過AbstractAutowireCapableBeanFactory.doCreateBean
創(chuàng)建普通對象/代理對象 并將工廠方法存儲在三級緩存(singletonFactories)中:
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName); if (earlySingletonExposure) { if (this.logger.isTraceEnabled()) { this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // addSingletonFactory 放入到三級緩存 this.addSingletonFactory(beanName, () -> { // 獲取提前暴露出來的bean 對象 return this.getEarlyBeanReference(beanName, mbd, bean); }); } try { // 繼續(xù)對改bean 中的屬性進行初始化 this.populateBean(beanName, mbd, instanceWrapper); exposedObject = this.initializeBean(beanName, exposedObject, mbd); } catch (Throwable var18) { if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) { throw (BeanCreationException)var18; } throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18); }
DefaultSingletonBeanRegistry.addSingletonFactory: bean
的工廠放入到三級緩存:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized(this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 放入3級緩存map this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); // 將改bean 的名稱放入到正在創(chuàng)建的bean 的set 集合 this.registeredSingletons.add(beanName); } } }
方法2:getSingleton(String beanName, ObjectFactory<?> singletonFactory) 創(chuàng)建對象:
此時的singletonFactory 為 this.getSingleton(beanName, () -> { try { // 創(chuàng)建普通對象/代理對象 并將工廠方法存儲在三級緩存(singletonFactories)中 // 此方法調(diào)用 AbstractAutowireCapableBeanFactory 下 createBean 方法然后在調(diào)用 doCreateBean 方法 return this.createBean(beanName, mbd, args); } catch (BeansException var5) { this.destroySingleton(beanName); throw var5; } })
方法中的lamma 表達式,通過 singletonFactory.getObject() 就可以調(diào)用到 this.createBean(beanName, mbd, args) 方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized(this.singletonObjects) { // 獲取對象鎖,然后從單例池中獲取bean Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 省略代碼 try { // 獲取bean 的工廠 singletonObject = singletonFactory.getObject(); newSingleton = true; } // 省略代碼 if (newSingleton) { // bean 完成后放入到單例翅中 this.addSingleton(beanName, singletonObject); } } return singletonObject; } } protected void addSingleton(String beanName, Object singletonObject) { synchronized(this.singletonObjects) { // 單例池中放入改bean this.singletonObjects.put(beanName, singletonObject); // 從三級緩存和二級緩存中移除 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
案例過程:
在A 依賴于B,B 依賴于C,C又依賴A 的場景中,當實例化 A后對A 的bean 對象進行屬性依賴注入時;發(fā)現(xiàn)其依賴了B;此時實例化 B,然后對B的bean 對象進行屬性依賴注入;發(fā)現(xiàn)依賴了C 的bean, 實例化 C,對C 的bean 進行屬性注入時,發(fā)現(xiàn)依賴了A的bean :
此時在對C的bean依賴注入為:先從單例池(一級緩存獲取A的bean),因為A的bean 還在被創(chuàng)建過程中,所以從單例池中的到的是null,在從二級緩存(早期暴露的對象)獲取A的早期暴露bean,如果二級緩存也沒有,則從三級緩存中執(zhí)行l(wèi)amma 表達式,獲取到A的早期暴露bean (將A的bean 放入到二級緩存中,并同時從三級緩存中移除A的bean 的lamma 表達式)進行屬性依賴注入;然后完成C的依賴注入,初始化 將其放入到單例池中,然后完成B的屬性依賴注入,初始化,將其放入到單例池;完成A 的屬性依賴注入,初始化,并放入到單例池中;
過程步驟:
- 開始初始化 A,創(chuàng)建 A 的實例并完成屬性注入;
- 當A屬性注入 過程中發(fā)現(xiàn) A 依賴于 B,Spring 會嘗試從單例池中獲取B的ean,如果獲取不到則進入到創(chuàng)建 B 的實例過程;
- 創(chuàng)建 B 的實例,對B 的bean進行依賴注入,發(fā)現(xiàn) B 還有其他依賴,如: C。此時 Spring 會先從單例池中獲取C的bean,如果獲取不到則進入到C的bean 創(chuàng)建過程;;
- 在C實例化后,對C屬性依賴注入 過程中,發(fā)現(xiàn) C 依賴于 A,從單例池中獲取A 的bean,此時 A 實例還未創(chuàng)建完成,從二級緩存獲取A bean 的早期引用;如果還沒有,則從三級緩存找到A bean的lamma 表達式,獲取到Abean 的早期暴露對象,并將Abean的早期暴露對象,放入到二級緩存同時,將Abean從三級緩存中移除;
- 繼續(xù)完成對 C 屬性注入(如果有),然后進行初始化,最終將C的bean 放入到單例池中,同時刪除其在二級和三級緩存的對象;
- 然后完成對B 其它屬性依賴注入中,發(fā)現(xiàn) B 不再依賴其他對象,完成 B 的初始化,最終將B的bean 放入到單例池中,同時刪除其在二級和三級緩存的對象;
- 繼續(xù) A的依賴注入,在 A的依賴注入 過程中,發(fā)現(xiàn) A 依賴于 B,B 的實例早已創(chuàng)建完成,因此可以直接從一級緩存獲取 B 的實例;
- 完成 A 的依賴注入,初始化 ,最終將A的bean 放入到單例池中,同時刪除其在二級和三級緩存的對象
Spring 使用了緩存機制,確保在遞歸調(diào)用時能夠正確地獲取到已經(jīng)創(chuàng)建的對象實例,避免死循環(huán)。同時,Spring 也會處理代理對象的生成和使用,以確保 A、B、C 的代理對象在正確的時間被創(chuàng)建和使用。
三、Spring 三級緩存無法解決的單例循環(huán)依賴情況
3.1 通過構(gòu)造方法注入的bean ,出現(xiàn)循環(huán)依賴會報錯
在A類中通過構(gòu)造方法的方式注入B的bean,在B類中通過構(gòu)造方法的方式注入A的bean;在此場景中,
- 在調(diào)用A的構(gòu)造方法進行實例化時,發(fā)現(xiàn)依賴的B的bean,需要對B類進行實例化;
- 調(diào)用B類的構(gòu)造方法進行實例化時,發(fā)現(xiàn)依賴的A的bean,此時出現(xiàn)循環(huán)依賴;
- 然后此時需要對A的bean 提前進行引用的暴露;
- 然而在對A的bean 提前進行引用的暴露,需要用到A 的實例化對象,此時A的實例化對象還沒有被創(chuàng)建,則直接報錯;
3.2 早期暴露的非aop代理對象引用,出現(xiàn)循環(huán)依賴會報錯
@Service public class AserviceImpl implements Aservice { @Autowired private Bservice bservice; @Async public void test(){ System.out.println(bservice); } } @Service public class BserviceImpl implements Bservice { @Autowired private Aservice aservice; }
當使用 @Async 注解標注一個bean 中的方法為異步方法時,Bservice 中注入的Aservice aservice 的bean 與最終生成的Aservice 的bean 不相同而導(dǎo)致報錯;
- 對Aservice 進行實例化后,對其bservice 屬性進行初始化;
- 對Bservice 進行實例化,然后對其aservice 屬性進行初始化,此時發(fā)現(xiàn)循環(huán)依賴;
- 暴露Aservice 的bean 到二級緩存中,因為Aservice 非aop 代理對象 ,所以此時二級緩存中放入的是Aservice 的普通對象;
- Bservice 的bean 完成初始化;
- Aservice 對 bservice 屬性初始化完成 ,并將Aservice 的bean 放入到一級緩存,并從二級緩存中刪除;
- 對Aservice 的bean 進行代理對象的包裝,包裝后的bean 與之前放入到一級緩存的bean 兩個不是同一個,程序報錯;
四、Lazy 注解解決循環(huán)依賴問題
延遲加載可以通過將 bean 的依賴關(guān)系運行時進行注入,而不是在初始化階段。這樣,當遇到循環(huán)依賴時,Spring 可以先創(chuàng)建需要的 bean 實例,并將其設(shè)置為代理對象,而不需要立即解決依賴關(guān)系。
@Service public class AserviceImpl implements Aservice { @Autowired @Lazy private Bservice bservice; @Async public void test(){ } }
Lazy 延遲加載打破循環(huán)依賴; 通過其它途徑生成bservice 的lazy 的代理對象,不會去走創(chuàng)建bservice 的代理 對象 然后注入aservice 這套流程。這樣創(chuàng)建aservice 的單例對象并放入到單例池中,Bservice 的bean 在實例化后,注入aservice bean 屬性就可以從單例池中加載到aservice 的真正的bean ,而不會出現(xiàn)bean 對象不一致的問題。
總結(jié)
spring 通過三級緩存解決單例的循環(huán)依賴問題,singletonObjects 用來存放已經(jīng)初始化完成的bean,earlySingletonObjects 用來存放早期暴露出來的半成品bean 的引用,singletonFactories 用來存放獲取早期引用的 Lambda表達式 工廠。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot使用 druid 連接池來優(yōu)化分頁語句
這篇文章主要介紹了SpringBoot使用 druid 連接池來優(yōu)化分頁語句,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11springboot post接口接受json時,轉(zhuǎn)換為對象時,屬性都為null的解決
這篇文章主要介紹了springboot post接口接受json時,轉(zhuǎn)換為對象時,屬性都為null的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10關(guān)于bootstrap.yml和bootstrap.properties的優(yōu)先級問題
這篇文章主要介紹了關(guān)于bootstrap.yml和bootstrap.properties的優(yōu)先級問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Java如何計算兩個時間段內(nèi)的工作日天數(shù)
這篇文章主要介紹了Java如何計算兩個時間段內(nèi)的工作日天數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07java實現(xiàn)Xml與json之間的相互轉(zhuǎn)換操作示例
這篇文章主要介紹了java實現(xiàn)Xml與json之間的相互轉(zhuǎn)換操作,結(jié)合實例形式分析了Java xml與json相互轉(zhuǎn)換工具類的定義與使用相關(guān)操作技巧,需要的朋友可以參考下2019-06-06SpringBoot中使用Cookie實現(xiàn)記住登錄的示例代碼
這篇文章主要介紹了SpringBoot中使用Cookie實現(xiàn)記住登錄的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07java 使用ImageIO.writer從BufferedImage生成jpeg圖像遇到問題總結(jié)及解決
這篇文章主要介紹了java 使用ImageIO.writer從BufferedImage生成jpeg圖像遇到問題總結(jié)及解決的相關(guān)資料,需要的朋友可以參考下2017-03-03