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測試類看看結果【結果如下】
【Aop】Wife 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:A 和 B,它們互相依賴。Spring 的解決流程如下:
- 創建 A:實例化 A(調用構造方法),此時 A 還未完成屬性填充和初始化。
- 暴露早期對象:將 A 的原始對象包裝成
ObjectFactory
,存入 三級緩存(singletonFactories
)。 - 填充 A 的屬性:發現 A 依賴 B,開始創建 B。
- 創建 B:實例化 B,填充 B 的屬性時發現依賴 A。
- 從三級緩存獲取 A:通過
ObjectFactory.getObject()
獲取 A 的早期引用(原始對象),注入到 B 中。 - 完成 B 的初始化:B 初始化完成后,存入一級緩存(
singletonObjects
)。 - 完成 A 的初始化:將 A 的最終對象存入一級緩存,替換三級緩存中的臨時對象。
若沒有代理需求,二級緩存(earlySingletonObjects
)似乎可以直接存儲原始對象,無需三級緩存。但問題在于:當 Bean 需要被代理時,必須確保注入的是代理對象而非原始對象。
循環依賴 + 代理的復雜場景
假設 A 和 B 都需要被 AOP 代理(例如被 @Transactional
標記),此時若僅用二級緩存,會引發以下問題:
- A 的創建流程:
- 實例化 A(原始對象)。
- 將 A 的原始對象存入二級緩存(
earlySingletonObjects
)。 - 填充屬性時發現依賴 B,開始創建 B。
- B 的創建流程:
- 實例化 B(原始對象)。
- 填充 B 的屬性時,從二級緩存獲取 A 的原始對象(未代理)。
- 完成 B 的初始化后,生成 B 的代理對象,存入一級緩存。
- 完成 A 的初始化:
- 在 A 的初始化后階段(
postProcessAfterInitialization
),生成 A 的代理對象。 - 最終緩存中的 A 是代理對象,但 B 中注入的 A 是原始對象,導致不一致!
- 在 A 的初始化后階段(
說實話,我自己都不能說服我自己。寫的好勉強。。。這些疑問還是看這篇文章吧。【參考里面的第二篇文章】
end.參考
-
https://mp.weixin.qq.com/s/dSRQBSG42MYNa992PvtnJA 【阿里云開發者 – 一文詳解Spring Bean循環依賴】
-
https://www.cnblogs.com/daimzh/p/13256413.html 【博客園 面試必殺技,講一講Spring中的循環依賴 】質量很高
-
原文鏈接:https://blog.csdn.net/chaitoudaren/article/details/105060882 【CSDN Spring源碼最難問題】