spring詳解-循環依賴的解決

Spring循環依賴

重點提示: 本文都快寫完了,發現“丈夫” 的英文是husband… 在“②有AOP循環依賴” 改過來了,前面用到的位置太多了就沒改。我是說怎么idea的hansband英文下面怎么有波浪線。各位能夠理解意思就行,英文拼寫不要過于在意.

1.案例引入

在這篇文章中,"②容器刷新"這一小節,留下了如下這樣一個疑問。【https://blog.csdn.net/okok__TXF/article/details/147009731】

// DefaultSingletonBeanRegistry.java
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}
————————————————
版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
原文鏈接:https://blog.csdn.net/okok__TXF/article/details/147009731

getSingleton(String beanName, boolean allowEarlyReference) 如果allowEarlyReference是true的話,就用三級緩存來解決循環依賴。下面以Spring5.3.31再次來追蹤一下源碼。

本文章示例代碼見該倉庫:【spring】中的“spring”模塊。

倉庫地址:https://gitee.com/quercus-sp204/sourcecode-and-demos

2.循環依賴分析

①無AOP的循環依賴

下面舉一個例子,一個丈夫和一個妻子的循環依賴。

// 這是Main測試類
@Configuration
@ComponentScan("com.feng.myspring")
public class Main {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);Hansband h = context.getBean(Hansband.class);h.say();}
}//1.丈夫
public class Hansband {@Autowiredprivate Wife wife;public void say() {System.out.println("我是丈夫");wife.eat();}public Hansband( ) {}public void eat() {System.out.println("丈夫吃飯");}
}
//2.妻子
public class Wife {@Autowiredprivate Hansband hansband;public void say(){System.out.println("我是妻子");hansband.eat();}public Wife() {}public void eat() {System.out.println("妻子吃飯");}
}

然后再配置類里面創建了一個丈夫和妻子對象

@Configuration
public class Config01 {@Beanpublic Hansband hansband(){return new Hansband();}@Beanpublic Wife wife(){return new Wife();}
}

然后上面的項目創建好了之后,現在就開始分析如下方法

// DefaultSingletonBeanRegistry.java
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

首先理解一下這其中的變量都是啥。

  • singletonObjects:單例對象的緩存,bean名稱對應一個bean實例。主要存放的是已經完成實例化、屬性填充和初始化所有步驟的單例Bean實例,這樣的Bean能夠直接提供給用戶使用,稱之為終態Bean或叫成熟Bean。【一級緩存】
  • earlySingletonObjects:早期單例對象的緩存,bean名稱對應一個bean實例。主要存放的已經完成實例化,但屬性還沒自動賦值的Bean,這些Bean還不能提供用戶使用,只是用于提前暴露的Bean實例,把這樣的Bean稱之為臨時Bean或早期的Bean(半成品Bean) 【二級緩存】
  • singletonFactories:單例工廠的緩存,bean名稱對應一個對象工廠。存放的是ObjectFactory的匿名內部類實例,調用ObjectFactory.getObject()最終會調用getEarlyBeanReference方法,該方法可以獲取提前暴露的單例bean引用。【三級緩存】
    在這里插入圖片描述

上圖是getSingleton(String beanName, boolean allowEarlyReference)方法的大致流程。然后就沒了?肯定不是的。

回顧容器刷新

現在回顧一下容器刷新階段,里面會調用這樣的方法

// AbstractApplicationContext.java
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {...// Instantiate all remaining (non-lazy-init) singletons.beanFactory.preInstantiateSingletons();
}// DefaultListableBeanFactory.java
@Override
public void preInstantiateSingletons() throws BeansException {...// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {...getBean(beanName); // 會調用這個...}}...
}// AbstractBeanFactory.java
@Override
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {.......... // ===== 【重要】   
}

上面最后是到了AbstractBeanFactory::doGetBean(xxx)方法了。下面就來著重分析一下這個doGetBean. 這里只挑重點,并不是一行一行地來。

// 一、AbstractBeanFactory.java
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {...// 1.檢查單例緩存Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {....beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {//2.原型作用域(prototype) 的 Bean 不支持循環依賴,//因為每次獲取都會創建新實例,Spring 無法通過緩存解決循環依賴。if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}....try {.....// 3.Create bean instance.if (mbd.isSingleton()) {// 4.調用了重載的getSingleton(xx,xx)方法===// 第二個參數是lambda表達式sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {....}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}...}return adaptBeanInstance(name, beanInstance, requiredType);
}// 二、DefaultSingletonBeanRegistry.java ----- 重載的getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {....synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {.....beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {/* getObject()實際執行的是這個。上面的lambda() -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {....}}*/// 實際調用的createBean(beanName, mbd, args);---見下面的三singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {.....}.....finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}}return singletonObject;}
}// 三、AbstractAutowireCapableBeanFactory.java
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {...try {// 創建實例beanObject beanInstance = doCreateBean(beanName, mbdToUse, args);...return beanInstance;}catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {...}...
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// 1,得到bean實例,先從緩存中找,找不到就構造一個BeanWrapper instanceWrapper = null;//調用 createBeanInstance 方法根據 Bean 定義和構造函數參數創建一個新的 BeanWrapper 實例。//從 BeanWrapper 中獲取實際的 Bean 實例和 Bean 的類型,并將類型信息記錄到 Bean 定義中。instanceWrapper = createBeanInstance(beanName, mbd, args);....// 2.low post-processors to modify the merged bean definition.// 應用合并后的 Bean 定義后置處理器synchronized (mbd.postProcessingLock) {...applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);...}//3.提前暴露單例 Bean 以處理循環引用boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {....// 這里是不是就放到了三級緩存中去了,注意一下參數哦。。getEarlyBeanReference// 將一個 ObjectFactory 放入單例工廠緩存中,該工廠會調用 getEarlyBeanReference 方法// 該方法會返回一個早期的 Bean 引用,以便在循環依賴時可以提前獲取到 Bean 的引用。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}Object exposedObject = bean;try {// 4.填充bean屬性populateBean(beanName, mbd, instanceWrapper);// 5.初始化bean【本文開頭的上篇文章】exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {......}if (earlySingletonExposure) {.....}...return exposedObject;
}

doCreateBean 方法是 Spring 框架中用于實際創建 Bean 實例的核心方法。它在 AbstractAutowireCapableBeanFactory 類中實現。該方法承擔了創建 Bean 實例、屬性填充、初始化以及處理循環依賴等重要任務。從最后一個方法我們可以大致總結三個關于spring創建bean的核心步驟,從上到下按順序。

  • 實例化Bean : 可以理解為new一個空的bean對象 【createBeanInstance
  • 填充Bean屬性 : 對bean的依賴屬性進行填充,對@Value @Autowired @Resource注解標注的屬性注入對象引用。【populateBean
  • 調用Bean初始化方法 : @PostConstruct、init-metohd、等等【initializeBean

這一節我們從最開始的getBean(beanName);,一直往下,發現getBean好像就是創建對象嚯。 【這句話后面會用到的。。。】

示例分析

那么案例中,我們是字段注入的,肯定就是發生在“填充Bean屬性” 這個階段才會產生的循環依賴問題!!接下來就以上面的“丈夫、妻子”為例子。

在這里插入圖片描述

上圖是正在創建Hansband的bean對象、到了屬性填充這個步驟了。進入這個populateBean方法里面,

在這里插入圖片描述

上圖是populateBean里面,調用這個AutowiredAnnotationBeanPostProcessor :: postProcessProperties(xx)方法。

在這里插入圖片描述

上圖中,在這個方法里面,調用 findAutowiringMetadata 方法,根據 Bean 的名稱、類型以及屬性值集合來查找自動注入的元數據。自動注入元數據包含了 Bean 中需要自動注入的字段、方法等信息,這些信息是通過 @Autowired@Resource 等注解標記的。可以發現綠色箭頭指向的是wife了。隨后,調用metadata.inject(bean, beanName, pvs);來執行執行依賴注入操作。

在這里插入圖片描述

上圖是metadata.inject里面的內容,對目標對象執行依賴注入操作。for循環遍歷注入元素并執行注入操作。 element.inject(target, beanName, pvs);

在這里插入圖片描述

上圖中,最后如果value不是null,field.set(bean, value):將解析好的依賴值注入到目標 Bean 的字段中。所以重點應該是上面的**resolveFieldValue 核心邏輯**

在這里插入圖片描述

上圖是解析字段的核心邏輯。

首先, 創建依賴描述符(DependencyDescriptor),可以看到將field傳了進去,【field很明顯就是wife嘛,上圖中也可以看出來】。

然后,準備依賴解析環境…這個就不說了。其次,調用了這一行代碼 beanFactory.resolveDependency(xxx);,這個是Spring 依賴解析的核心方法,根據 DependencyDescriptor 查找匹配的依賴值【可以很明顯感覺到,這個又是核心邏輯了】。 找到依賴值后,會緩存起來。

最后會返回這個value嘛。

在這里插入圖片描述

上圖是beanFactory.resolveDependency,先會檢查是否需要延遲解析代理(處理 @Lazy 等場景),若無需延遲解析,返回 null,進入下一步。然后,調用核心解析方法 doResolveDependency

// ContextAnnotationAutowireCandidateResolver.java
// 檢查是否需要延遲解析
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}

接著進入doResolveDependency方法里面看看。

在這里插入圖片描述

// DefaultListableBeanFactory.java
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {...// 一陣噼里啪啦if (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);}
}// DependencyDescriptor.java
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)throws BeansException {return beanFactory.getBean(beanName); //
}

看到這里,明白了沒,一切都回來了!!! 前面不是說過嗎getBean好像就是創建對象嚯。我們在創建Hansband對象的時候,這個時候并沒有創建Wife對象啊,現在兜兜轉轉又回來了。doGetBean() 現在里面是wife了。!!!!【遞歸進去,Hansband對象的創建還是卡在populateBean方法的】

在這里插入圖片描述

然后Wife又會走一遍上面的流程。我們在最上面創建好Hansband之后,在填充Hansband屬性之前,有這樣一段代碼。

//3.提前暴露單例 Bean 以處理循環引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {....// 這里是不是就放到了三級緩存中去了,注意一下參數哦。。getEarlyBeanReference// 將一個 ObjectFactory 放入單例工廠緩存中,該工廠會調用 getEarlyBeanReference 方法// 該方法會返回一個早期的 Bean 引用,以便在循環依賴時可以提前獲取到 Bean 的引用。// 在后續再緩存中查找Bean時會觸發匿名內部類getEarlyBeanReference()方法回調addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}//4. 填充屬性
..... 
populateBean(beanName, mbd, instanceWrapper);

創建Wife的時候同理啊,又會調用一遍addSingletonFactory,此時是把wife放到singletonFactories里面了。此時singletonFactories里面就會有兩個鍵值對。

hansband -- getEarlyBeanReference("hansband", mbd, bean)
wife -- getEarlyBeanReference("wife", mbd, bean)

然后就輪到執行Wife的屬性填充了,發現需要Hansband的類型的bean,順著上面梳理的流程,最后又會回到下面這里,只不過此時的參數:descriptor就是hansband,beanName是Wife了。

// DefaultListableBeanFactory.java
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {...// 一陣噼里啪啦if (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);// beanFactory.getBean("hansband");  遞歸進去}
}

在Wife的屬性填充的時候,遞歸進去拿Hansband對象,會有如下顯示【這個時候,三級緩存里面就可以看到有兩個鍵值對了,在這里將Hansband的早期對象拿到,并把它從三級緩存移動到二級緩存中去】

在這里插入圖片描述

這樣在Wife屬性填充的時候,實現了提前將Hansband曝光給Wife完成屬性依賴注入。緊接著,Wife就可以繼續完成后面的初始化邏輯,產生一個成熟的Wife。

創建好之后,會addSingleton("wife", xxxx)

protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject); // 放入一級緩存this.singletonFactories.remove(beanName); // 移除二三級緩存this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}

請看下面的流程圖解釋。

在這里插入圖片描述

上圖流程中,創建wife的時候,提前注入了一個hansband還沒有初始化好的對象【注入了一個早期bean】,這樣不會有什么問題嗎?

wife創建完全之后,會返回到hansband的“bean初始化階段”,然后hansband就會初始化ok并放入單例池。由于wife中的早期bean和 創建hansband中的bean是同一個引用,故沒有啥問題的。

嘶,看了一下,要三級緩存有這個必要嗎?圖中二級緩存干啥的,getEarlyBeanReference經過調試,發現就是返回了一個bean。看來還是疑點重重,請看下一節。

②有AOP的循環依賴

在上面普通bean的循環依賴場景下,可以看出三級緩存貌似并沒有什么卵用。【實際上確實是的,在普通的循環依賴的情況下,三級緩存沒有任何作用。

】經過反復參考求證,發現三級緩存是和spring 的 AOP掛鉤的!

AOP CSDN地址:https://blog.csdn.net/okok__TXF/article/details/147397816

AOP 博客園地址:https://www.cnblogs.com/jackjavacpp/p/18838920

看一下上一小節的getEarlyBeanReference(beanName, mbd, bean) 到底做了什么

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;
}

在未啟動AOP代理之前,這個方法中的SmartInstantiationAwareBeanPostProcessor如下所示【AutowiredAnnotationBeanPostProcessor::getEarlyBeanReference()方法就是單純的返回bean】

在這里插入圖片描述

打開AOP之后,變成了AnnotationAwareAspectJAutoProxyCreator~ 那么它的getEarlyBeanReference()方法有變化嗎?

在這里插入圖片描述

// AbstractAutoProxyCreator.java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);/*========== earlyProxyReferences【重要】跟蹤哪些 Bean 的代理對象已在 提前暴露階段 生成,他的主要作用大概如下1. 防止重復代理:避免在 Bean 初始化階段重復創建代理對象,如果有循環依賴,那么該代理對象在屬性填充階段被創建過了2. 保證代理對象一致性:確保循環依賴注入的代理對象與最終暴露的代理對象是同一實例*/this.earlyProxyReferences.put(cacheKey, bean); // ======return wrapIfNecessary(bean, beanName, cacheKey); // 下一層
}
// 到這里來了
// 決定是否為給定的 Bean 創建代理對象
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {....// Create proxy if we have advice.// getAdvicesAndAdvisorsForBean獲取適用于該 Bean 的通知和增強器// 該方法會根據 Bean 的類型和名稱,從 Spring 容器中查找所有匹配的通知和增強器,并返回一個數組Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// DO_NOT_PROXY 就是 nullif (specificInterceptors != DO_NOT_PROXY) { // 不為nullthis.advisedBeans.put(cacheKey, Boolean.TRUE);//調用 createProxy 方法創建代理對象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

可以大致了解到這個是返回了一個代理對象,可見開啟AOP之前和開啟AOP之后,果然有不一樣。現在我們將案例變成如下有AOP的循環依賴。

// 接口
public interface AopMan {void aopManSay();
}
@Component("husband")
public class AopHusband implements AopMan{@Autowiredprivate AopWoman wife;@Overridepublic void aopManSay() {System.out.println("【AOP】Husband say 哦吼");}
}// 接口
public interface AopWoman { void aopWomanSay();
}
@Component("wife")
public class AopWife implements AopWoman{@Autowiredprivate AopMan husband;@Overridepublic void aopWomanSay() {System.out.println("【Aop】Wife say 哈哈");husband.aopManSay();}
}// 創建切面
@Component
@Aspect
public class ManAdvice {private static final String manExpression = "execution(* com.feng.myspring.aopobj.*.aopManSay*(..))";//環繞通知@Around(manExpression)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【環繞通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【環繞通知中的后置通知】##########");return returnVale;}
}// 主啟動測試類
@Configuration
@ComponentScan("com.feng.myspring")
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class Main {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);// ②有Aop的循環依賴AopMan husband = context.getBean("husband", AopMan.class);husband.aopManSay();System.out.println(husband.getClass()); // 打印一下類型AopWoman wife = context.getBean("wife", AopWoman.class);System.out.println(wife.getClass()); // 打印一下類型}
}

上述例子中,wife的aopWomanSay()方法里面會調用husband的aopManSay方法,但是此時我是對husband的aopManSay配置了環繞通知的,那么請各位想一下此時wife里面注入的husband是原來的對象嗎?肯定不是的,是代理對象,注入的是husband的代理對象,可以運行一下Main測試類看看結果【結果如下】

AopWife say 哈哈
##########【環繞通知中的前置通知】##########
【AOP】Husband say 哦吼
##########【環繞通知中的后置通知】##########
class com.sun.proxy.$Proxy18 // husband是一個代理對象
class com.feng.myspring.aopobj.AopWife// wife仍然是原來

我們都知道springbean的創建過程,是 1.緩存查詢;2.創建對象;3.屬性填充、注入依賴;4.執行初始化操作;5.這個bean創建好了。大致這樣五個過程,在沒有循環依賴的前提下,開啟aop會生成bean的代理對象,這個生成代理對象的時機是在 “4.執行初始化操作” 這一步的,但是本文討論的都是在第三步哦~~【AOP見下面文章】。所以,三級緩存的存在就是為了提前用對象工廠獲取代理對象,并賦值給wife的husband屬性【代理對象】依賴注入。

AOP CSDN地址:https://blog.csdn.net/okok__TXF/article/details/147397816

AOP 博客園地址:https://www.cnblogs.com/jackjavacpp/p/18838920

回到本小節開始的時候,開啟aop之后,AnnotationAwareAspectJAutoProxyCreator通過getEarlyBeanReference里面的wrapIfNecessary拿到的代理實例,我們對husband進行了AOP代理的話,那么此時getEarlyBeanReference將返回一個代理后的對象,而不是實例化階段創建的對象,這樣就意味著wife中注入的husband將是一個代理對象而不是husband的實例化階段創建后的對象。

Spring是在何處將husband的代理對象放進去的呢?

在這里插入圖片描述

在完成husband初始化后,Spring又調用了一次getSingleton方法,允許早期引用為false。在前面分析的時候在為wife中注入husband時已經將三級緩存中的工廠取出,并從工廠中獲取到了一個husband代理對象放入到了二級緩存中,并且從三級緩存中移除掉,所以這里的這個getSingleton方法做的時間就是從二級緩存中獲取到這個代理后的husband對象。

3.疑問

1.為什么三級緩存要弄一個對象工廠添加進去,我直接往三級緩存放入對象的代理對象不行嗎?

這個工廠的目的在于延遲對實例化階段生成的對象的代理,只有真正發生循環依賴的時候,才去提前生成代理對象,否則只會創建一個工廠并將其放入到三級緩存中,但是不會去通過這個工廠去真正創建對象。

讀者在此處思考一下,如果看過AOP,那么肯定就會知道,代理對象的生成是在bean的初始化操作中的后置處理器的postProcessAfterInitialization這一步的,而我們循環依賴發生在屬性填充這一步。發生循環依賴的時候,需要注入代理對象,但是還沒到代理對象生成那一步

try {// 4.填充bean屬性populateBean(beanName, mbd, instanceWrapper);// 5.初始化bean【本文開頭的上篇文章】exposedObject = initializeBean(beanName, exposedObject, mbd);
} .....

在 Spring 框架中,三級緩存(singletonFactories)存儲的是 ObjectFactory,而非直接存儲代理對象,這一設計是為了解決循環依賴中代理對象生成的 時機問題

2.用二級緩存不行嗎?

循環依賴的簡單場景(無代理) : 是沒問題的

假設有兩個 Bean:AB,它們互相依賴。Spring 的解決流程如下:

  1. 創建 A:實例化 A(調用構造方法),此時 A 還未完成屬性填充和初始化。
  2. 暴露早期對象:將 A 的原始對象包裝成 ObjectFactory,存入 三級緩存singletonFactories)。
  3. 填充 A 的屬性:發現 A 依賴 B,開始創建 B。
  4. 創建 B:實例化 B,填充 B 的屬性時發現依賴 A。
  5. 從三級緩存獲取 A:通過 ObjectFactory.getObject() 獲取 A 的早期引用(原始對象),注入到 B 中。
  6. 完成 B 的初始化:B 初始化完成后,存入一級緩存(singletonObjects)。
  7. 完成 A 的初始化:將 A 的最終對象存入一級緩存,替換三級緩存中的臨時對象。

若沒有代理需求,二級緩存(earlySingletonObjects)似乎可以直接存儲原始對象,無需三級緩存。但問題在于:當 Bean 需要被代理時,必須確保注入的是代理對象而非原始對象


循環依賴 + 代理的復雜場景

假設 A 和 B 都需要被 AOP 代理(例如被 @Transactional 標記),此時若僅用二級緩存,會引發以下問題:

  1. A 的創建流程
    • 實例化 A(原始對象)。
    • 將 A 的原始對象存入二級緩存(earlySingletonObjects)。
    • 填充屬性時發現依賴 B,開始創建 B。
  2. B 的創建流程
    • 實例化 B(原始對象)。
    • 填充 B 的屬性時,從二級緩存獲取 A 的原始對象(未代理)。
    • 完成 B 的初始化后,生成 B 的代理對象,存入一級緩存。
  3. 完成 A 的初始化
    • 在 A 的初始化后階段(postProcessAfterInitialization),生成 A 的代理對象。
    • 最終緩存中的 A 是代理對象,但 B 中注入的 A 是原始對象,導致不一致

說實話,我自己都不能說服我自己。寫的好勉強。。。這些疑問還是看這篇文章吧。【參考里面的第二篇文章】

end.參考

  1. https://mp.weixin.qq.com/s/dSRQBSG42MYNa992PvtnJA 【阿里云開發者 – 一文詳解Spring Bean循環依賴】

  2. https://www.cnblogs.com/daimzh/p/13256413.html 【博客園 面試必殺技,講一講Spring中的循環依賴 】質量很高

  3. 原文鏈接:https://blog.csdn.net/chaitoudaren/article/details/105060882 【CSDN Spring源碼最難問題】

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/904366.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/904366.shtml
英文地址,請注明出處:http://en.pswp.cn/news/904366.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

隨機快速排序算法

一、隨機化原理 經典快速排序 選取固定的“樞軸”&#xff08;通常取第一個或最后一個元素&#xff09;&#xff0c;在最壞情況下&#xff08;如已經有序&#xff09;會退化為 。 隨機快速排序 在每次分區前隨機地從當前區間 [p..r] 中等概率選取一個樞軸&#xff0c;將它與末…

數據可視化與分析

數據可視化的目的是為了數據分析&#xff0c;而非僅僅是數據的圖形化展示。 項目介紹 項目案例為電商雙11美妝數據分析&#xff0c;分析品牌銷售量、性價比等。 數據集包括更新日期、ID、title、品牌名、克數容量、價格、銷售數量、評論數量、店名等信息。 1、數據初步了解…

美團Java高級配送員面經分享|玩梗版

美團Java高級配送員面經分享&#xff01;純玩梗&#xff01;

在windows中卸載mysql

一、停止服務 winR快捷鍵 -->> 輸入services.msc -->> 進入服務窗口關閉Mysql服務 二、卸載程序&#xff08;可選&#xff09; 如果是通過解壓壓縮包安裝的則跳過這一步&#xff0c;如果是使用.msi文件驅動安裝則需要卸載 控制面板 -->> 程序和功能 -->…

https://juejin.cn/editor/drafts/7262346366541070395

.Net Core從零學習搭建權限管理系統教程 推薦一組WPF自定義控件開源項目。 項目簡介 這是基于WPF開發的&#xff0c;為開發人員提供了一組方便使用自定義組件&#xff0c;并提供了各種常用的示例。 包含組件&#xff1a;數據表格、屬性列表、樹形列表、選色器、單選框列表、…

三、網絡管理

網絡管理 一、IP地址 原理&#xff1a; 定義與作用&#xff1a;IP 地址是互聯網協議地址&#xff0c;用于在網絡中唯一標識一臺設備。它如同現實生活中的家庭住址&#xff0c;確保數據能準確無誤地從源設備傳輸到目標設備。地址分類&#xff1a;IP 地址分為 IPv4 和 IPv6 兩種…

Auto.js 腳本:清理手機數據但保留賬號

Auto.js 腳本&#xff1a;清理手機數據但保留賬號 以下是一個使用 Auto.js 實現的腳本&#xff0c;它可以幫你清理手機數據&#xff08;類似恢復出廠設置&#xff09;&#xff0c;同時盡可能保留已登錄的賬號狀態。請注意&#xff0c;這個腳本不能完全等同于真正的恢復出廠設置…

LeetCode 熱題 100 279. 完全平方數

LeetCode 熱題 100 | 279. 完全平方數 大家好&#xff0c;今天我們來解決一道經典的動態規劃問題——完全平方數。這道題在 LeetCode 上被標記為中等難度&#xff0c;要求找到和為給定整數 n 的完全平方數的最少數量。 問題描述 給定一個整數 n&#xff0c;返回和為 n 的完全…

【coze】手冊小助手(提示詞、知識庫、交互、發布)

【coze】手冊小助手&#xff08;提示詞、知識庫、交互、發布&#xff09; 1、創建智能體2、添加提示詞3、創建知識庫4、測試智能體5、添加交互功能6、發布智能體 1、創建智能體 2、添加提示詞 # 角色 你是幫助用戶搜索手冊資料的AI助手 ## 工作流程 ### 步驟一:查詢知識庫 1.每…

一個基于Asp.Net Core + Angular + Bootstrap開源CMS系統

從零學習構建一個完整的系統 推薦一個功能強大、易于擴展、安全可靠的開源內容管理系統&#xff0c;適用于各種類型和規模的網站。 項目簡介 MixCoreCMS是一個基于.NET Core框架的開源內容管理系統&#xff08;CMS&#xff09;&#xff0c;提供了豐富的的基礎功能和插件&…

【Python】常用命令提示符

Python常用的命令提示符 一、Python環境基礎命令【Windows】 于Windows環境下&#xff0c;針對Python&#xff0c;在CMD&#xff08;命令提示符&#xff09;常用的命令以及具體用法&#xff0c;怎么用&#xff1b; ??主要包含&#xff1a;運行腳本、包管理、虛擬環境、調試與…

提示詞優化:檢索歷史提示確定方向→生成候選提示并控制修改幅度→基于準確率迭代優化

提示詞優化器 Unleashing the Potential of Large Language Models as Prompt Optimizers: Analogical Analysis with Gradient - based Model Optimizers 《Unleashing the Potential of Large Language Models as Prompt Optimizers: Analogical Analysis with Gradient - …

如何設計一個網頁計算器?—— 從需求分析到測試的全流程

1. 需求分析與功能設計 核心功能 基礎運算:+ - * / 高級運算:% (取模)、^ (冪運算)、√ (開平方) 記憶功能:M+ (累加)、M- (累減)、MR (讀取)、MC (清除) 交互優化: 支持鍵盤輸入(0-9、Enter、Backspace) 實時計算(類似 Google 計算器,輸入 2+3= 自動顯示 5) 錯誤處理…

基于RT-Thread的STM32F4開發第二講第一篇——ADC

文章目錄 前言一、RT-Thread工程創建二、ADC工程創建三、ADC功能實現1.ADC.c2.ADC.h3.mian.c 四、效果展示和工程分享總結 前言 ADC是什么不多講了&#xff0c;前面裸機操作部分有很多講述。我要說的是RT-Thread對STM32的ADC外設的適配極其不好&#xff0c;特別是STM32G4系類&…

FoMo 數據集是一個專注于機器人在季節性積雪變化環境中的導航數據集,記錄了不同季節(無雪、淺雪、深雪)下的傳感器數據和軌跡信息。

2025-05-02&#xff0c;由加拿大拉瓦爾大學北方機器人實驗室和多倫多大學機器人研究所聯合創建的 FoMo 數據集&#xff0c;目的是研究機器人在季節性積雪變化環境中的導航能力。該數據集的意義在于填補了機器人在極端季節變化&#xff08;如積雪深度變化&#xff09;下的導航研…

vue3+ts繼續學習

我們再寫點東西&#xff0c;這里面都是vue2的語法&#xff0c;應該都能看明白&#xff01;我們寫完直接去運行一下代碼&#xff01; 發現什么都沒有發生&#xff01;為什么呢&#xff1f;因為我們在App.vue中沒有引入&#xff01;哈哈哈哈&#xff01;這樣就好了&#xff01;注…

LIO-Livox

用單臺Livox Horizon (含內置IMU) 實現高魯棒性的激光-慣性里程計&#xff0c;可在各類極端場景下魯棒運行&#xff0c;并達到高精度的定位和建圖效果。(城區擁堵、高速公路、幽暗隧道) 注&#xff1a;該系統主要面向大型室外環境中的汽車平臺設計。用戶可以使用 Livox Horizo…

day18-API(常見API,對象克隆)

課程目標 能夠熟練使用Math類中的常見方法 能夠熟練使用System類中的常見方法 能夠理解Object類的常見方法作用 能夠熟練使用Objects類的常見方法 能夠熟練使用BigInteger類的常見方法 能夠熟練使用BigDecimal類的常見方法 1 Math類 1.1 概述 tips&#xff1a;了解內容…

用OMS從MySQL遷移到OceanBase,字符集utf8與utf8mb4的差異

一、問題背景 在一次從MySQL數據庫遷移到OceanBase的MySQL租戶過程中&#xff0c;出現了一個轉換提示&#xff1a; [WARN][CONVER] he table charset:utf8->utf8mb4&#xff0c; 你可能會擔心這種轉換可能導致字符集不兼容的問題。但通過查閱相關資料可知&#xff0c;utf8m…

MATLAB中tabulate函數——先驗概率的簡單估計

load fisheriris X meas(:,1:2); Y species; labels unique(Y); tabulate(Y)ValueCountPercentsetosa5033.33%versicolor5033.33%virginica5033.33%