- 文档
- 教程
- 后端
【免费下载链接】CodeGuide
:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!
导读:本文以 CodeGuide 仓库中《面经手册 · 第31篇》为核心骨架,系统讲解 Spring 中循环依赖的定义、成因与解法。你将掌握:循环依赖的三种形态、手写一套可运行的循环依赖解决方案、Spring 从getBean到doCreateBean再到三级缓存getSingleton的核心调用链,以及一级、二级、三级缓存各自解决什么问题。文中同时结合仓库内《手撸 Spring》专栏第 13、17 章源码,给出可直接对照调试的落地实现与测试案例。
一、什么是循环依赖:先看清问题的本质
了解问题的本质再分析问题,往往更利于对问题有更深入的了解和研究。所以在分析 Spring 关于循环依赖的源码之前,先要弄清楚什么是循环依赖。
1. 循环依赖的三种形态
循环依赖主要分为三种情况:
- 自身依赖于自身:A 的完整创建依赖于 A 自身。
- 互相循环依赖:A 依赖 B,B 又依赖 A。
- 多组循环依赖:A→B→C→A 这类更长链条的环形依赖。
但无论循环依赖的数量有多少、链条有多长,它的本质都是一样的:你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
所以 Spring 提供了除构造函数注入和原型(prototype)注入之外的setter 循环依赖注入解决方案。
2. 问题体现:最原始的循环依赖代码
public class ABTest { public static void main(String[] args) { new ClazzA(); } } class ClazzA { private ClazzB b = new ClazzB(); } class ClazzB { private ClazzA a = new ClazzA(); }这段代码就是循环依赖最初的模样:你中有我,我中有你,运行就会报错java.lang.StackOverflowError。
这样的循环依赖代码是没法解决的。当你看到 Spring 中提供 get/set 或者注解之所以能解决循环依赖,首先是进行了一定的解耦:把"类的创建"和"属性的填充"分离。先创建出半成品 Bean,再处理属性的填充,最终完成成品 Bean 的提供。这一点正是整个循环依赖解决方案的设计基石。
3. 问题处理:自己动手实现一个循环依赖解决方案
在这部分代码中只有一个核心目的:自己来解决循环依赖。方案如下:
public class CircleTest { private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); public static void main(String[] args) throws Exception { System.out.println(getBean(B.class).getA()); System.out.println(getBean(A.class).getB()); } private static <T> T getBean(Class<T> beanClass) throws Exception { String beanName = beanClass.getSimpleName().toLowerCase(); if (singletonObjects.containsKey(beanName)) { return (T) singletonObjects.get(beanName); } // 实例化对象入缓存 Object obj = beanClass.newInstance(); singletonObjects.put(beanName, obj); // 属性填充补全对象 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Class<?> fieldClass = field.getType(); String fieldBeanName = fieldClass.getSimpleName().toLowerCase(); field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass)); field.setAccessible(false); } return (T) obj; } } class A { private B b; // ...get/set } class B { private A a; // ...get/set }这段代码的要点如下:
- 提供了 A、B 两个类,互相有依赖。但两个类中的依赖关系使用的是setter 方式进行填充,也就是只有这样才能避免两个类在创建之初不非得强依赖于另外一个对象。
getBean是整个解决循环依赖的核心内容:A 创建后填充属性时依赖 B,那么就去创建 B;在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存singletonObjects中了,所以 B 可以正常创建,再通过递归把 A 也创建完整。
这套自研方案与仓库《手撸 Spring》专栏 第17章:通过三级缓存解决循环依赖 中"仅用一级缓存解决循环依赖"的演示实现完全一致——先newInstance创建对象立即入缓存,再通过递归填充属性。该章节的测试运行结果也印证了这一点:
public static void main(String[] args) throws Exception { System.out.println(getBean(B.class).getA()); System.out.println(getBean(A.class).getB()); } cn.bugstack.springframework.test.A@49476842 cn.bugstack.springframework.test.B@78308db1 Process finished with exit code 0即:一级缓存也能解决简单场景的循环依赖问题,这也为理解 Spring 三级缓存的必要性做了铺垫。
二、Spring 的解决入口:getBean → doGetBean
有了上面的例子,我们已经大概了解到:A 和 B 互相依赖时,A 创建完后填充属性 B,继续创建 B,再填充属性 A 时就可以从缓存中获取了。
那么把解决循环依赖这件事放到 Spring 中是什么样?展开细节!
虽然解决循环依赖的核心原理一样,但要放到支撑起整个 Spring 中 IOC、AOP 特性时,就会变得复杂一些。整个处理 Spring 循环依赖的过程基本是:入口 getBean → doGetBean → getSingleton(查缓存)→ createBean → doCreateBean(提前暴露)→ populateBean(填充属性触发递归)→ getSingleton(三级缓存获取)→ registerSingleton(注册成品)。
1. 从单元测试进入源码
以下是对 A、B 依赖获取 Bean 的操作,重点在于进入getBean的源码跟进:
@Test public void test_alias() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml"); Bean_A bean_a = beanFactory.getBean("bean_a", Bean_A.class); logger.info("获取 Bean 通过别名:{}", bean_a.getBean_b()); }关于 getBean 入口更多的分支细节(别名处理、&工厂 Bean 前缀、depends-on、父工厂查找、原型抛异常等),可结合本仓库《面经手册 · 第30篇》《关于 Spring 中 getBean 的全流程源码解析》 对照学习。
2. AbstractBeanFactory#getBean 与 doGetBean
org.springframework.beans.factory.support.AbstractBeanFactory.java
@Override public <T> T getBean(String name, Class<T> requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); }- 从 getBean 进入后,获取 bean 的操作会进入到 doGetBean。
- 之所以这样包装一层,是因为 doGetBean 有很多不同入参的重载方法,方便外部操作。
doGetBean 方法(核心片段)
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // 从缓存中获取 bean 实例 Object sharedInstance = getSingleton(beanName); // mbd.isSingleton() 用于判断 bean 是否是单例模式 if (mbd.isSingleton()) { // 获取 bean 实例 sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { // 创建 bean 实例,createBean 返回的 bean 实例化好的 return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); // 后续的处理操作 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // ... // 返回 bean 实例 return (T) bean; }- 这一部分是从
getSingleton先判断是否有实例对象,对于第一次进入是肯定没有对象的,要继续往下走。 - 在判断
mbd.isSingleton()单例以后,开始使用基于ObjectFactory包装的方式创建createBean,进入后核心逻辑是开始执行doCreateBean操作。
三、doCreateBean:实例化、提前暴露与属性填充
doCreateBean 方法(核心片段)
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { // 创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回 instanceWrapper = createBeanInstance(beanName, mbd, args); // 添加 bean 工厂对象到 singletonFactories 缓存中 addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { // 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 bean。 return getEarlyBeanReference(beanName, mbd, bean); } }); try { // 填充属性,解析依赖关系 populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { exposedObject = initializeBean(beanName, exposedObject, mbd); } } // 返回 bean 实例 return exposedObject; }在 doCreateBean 方法中包括的内容较多,但核心主要是创建实例、加入缓存以及最终进行属性填充,属性填充就是把一个 bean 的各个属性字段涉及到的类填充进去:
createBeanInstance:创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回。addSingletonFactory:添加 bean 工厂对象到 singletonFactories 缓存中。这一步是解决循环依赖的关键——把刚创建出来的"半成品"以工厂对象的形式提前暴露出去。getEarlyBeanReference:获取原始对象的早期引用。在这个方法中会执行 AOP 相关逻辑,若 bean 未被 AOP 拦截,则原样返回 bean。populateBean:填充属性,解析依赖关系。也就是从这开始去找寻 A 实例中属性 B,紧接着去创建 B 实例,最后再返回回来,形成递归。
四、核心源码:getSingleton 与三级缓存
getSingleton 三级缓存(核心片段)
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从 singletonObjects 获取实例,singletonObjects 是成品 bean Object singletonObject = this.singletonObjects.get(beanName); // 判断 beanName ,isSingletonCurrentlyInCreation 对应的 bean 是否正在创建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 从 earlySingletonObjects 中获取提前曝光未成品的 bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 获取相应的 bean 工厂 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 提前曝光 bean 实例,主要用于解决AOP循环依赖 singletonObject = singletonFactory.getObject(); // 将 singletonObject 放入缓存中,并将 singletonFactory 从缓存中移除 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }关键点逐条拆解:
singletonObjects.get(beanName):从一级缓存获取实例,singletonObjects存放的是成品 bean。isSingletonCurrentlyInCreation:判断对应 bean 是否正在创建中。只有"正在创建中"的 bean 才会进入二级、三级缓存查找流程。allowEarlyReference:是否允许提前引用,为 true 时从二级缓存earlySingletonObjects获取提前曝光未成品的 bean。singletonFactory.getObject():从三级缓存取出工厂对象并执行,提前曝光 bean 实例,主要用于解决 AOP 循环依赖。拿到真实对象后会放入二级缓存earlySingletonObjects,并移除三级缓存中的工厂对象——后续再获取就直接命中二级缓存,保证返回的是同一个引用。
综上,这是一个处理循环依赖的完整代码流程。这部分提取出来的内容主要是核心内容,并没有把源码长篇大论的全部拆取出来,大家在调试的时候涉及到的代码会比较多,建议根据上面的流程多调试几遍,逐步建立"缓存命中"的直觉。
五、依赖解析:一级、二级、三级缓存分别解决什么
1. 一级缓存能解决吗?
其实只有一级缓存并不是不能解决循环依赖,就像我们在前面自己实现的那个例子一样。但是在 Spring 中如果像例子里那么处理,就会变得非常麻烦,而且也可能出现NPE 问题。
按照 Spring 中代码处理的流程去分析:一级缓存只存放成品 Bean,是不能解决循环依赖问题的。因为 A 的成品创建依赖于 B,B 的成品创建又依赖于 A,当需要补全 B 的属性时,A 还是没有创建完,所以会出现死循环。
2. 二级缓存能解决吗?
有了二级缓存,这个事处理起来就容易了:一个缓存用于存放成品对象,另外一个缓存用于存放半成品对象。
- A 在创建半成品对象后存放到缓存中,接下来补充 A 对象中依赖 B 的属性。
- B 继续创建,创建的半成品同样放到缓存中,在补充 B 对象的 A 属性时,可以从半成品缓存中获取。现在 B 就是一个完整对象了,接下来像是递归操作一样,A 也变成了完整对象。
3. 三级缓存解决什么?
有了二级缓存都能解决循环依赖了,怎么还要三级缓存呢?
其实在前面分析源码时已经提到过:三级缓存主要是解决 Spring AOP 的特性。AOP 本身就是对方法的增强,三级缓存中存放的是ObjectFactory<?>类型的 lambda 表达式(工厂对象),而 Spring 的原则又不希望将此类类型的 Bean 前置创建,所以要存放到三级缓存中延迟处理。
整体处理过程类似,唯独是:B 在填充属性 A 时,先查询成品缓存(一级)、再查半成品缓存(二级),最后再看看有没有单例工厂类在三级缓存中。最终获取到以后调用getObject方法返回代理引用或者原始引用。
至此也就解决了 Spring AOP 所带来的三级缓存问题。
关于"AOP 代理对象的创建提前到二级缓存",仓库《手撸 Spring》专栏 第17章 中有一段清晰的总结:"如果没有如切面和工厂中的代理对象,那么二级缓存也就可以解决了,哪怕是只有一级缓存。但为了设计上的合理和可扩展性,所以创建了三级缓存来放置不同时期的对象。"也就是说三级缓存并非数学上的"必须",而是在满足 Spring 自身创建原则(普通 Bean 全部初始化完成后再处理代理对象)前提下的工程最优解。
六、仓库落地实现:三级缓存在《手撸 Spring》中的源码对应
为了让读者对上述 Spring 源码有可对照的完整实现,这里直接给出仓库《手撸 Spring》专栏第 17 章 《通过三级缓存解决循环依赖》 中的落地源码。它的处理循环依赖核心流程的类关系包括:
- DefaultSingletonBeanRegistry提供三级缓存:
singletonObjects(成品对象)、earlySingletonObjects(半成品对象)、singletonFactories(工厂对象),并包装三个缓存提供方法:getSingleton、registerSingleton、addSingletonFactory,使用方可以分别在不同时间段存放和获取对应的对象。 - AbstractAutowireCapableBeanFactory#doCreateBean提供提前暴露对象的操作
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean)),以及后续获取与注册操作exposedObject = getSingleton(beanName);、registerSingleton(beanName, exposedObject);。 - DefaultAdvisorAutoProxyCreator提供的切面服务中,实现接口
InstantiationAwareBeanPostProcessor新增的getEarlyBeanReference方法,便于把依赖的切面对象也能存放到三级缓存中,处理对应的 AOP 循环依赖。
1. 设置三级缓存
cn.bugstack.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { // 一级缓存,普通对象 private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 二级缓存,提前暴漏对象,没有完全实例化的对象 protected final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(); // 三级缓存,存放代理对象 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(); private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>(); @Override public Object getSingleton(String beanName) { Object singletonObject = singletonObjects.get(beanName); if (null == singletonObject) { singletonObject = earlySingletonObjects.get(beanName); // 判断二级缓存中是否有对象,这个对象就是代理对象,因为只有代理对象才会放到三级缓存中 if (null == singletonObject) { ObjectFactory<?> singletonFactory = singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中 earlySingletonObjects.put(beanName, singletonObject); singletonFactories.remove(beanName); } } } return singletonObject; } public void registerSingleton(String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); } protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){ if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); } } public void registerDisposableBean(String beanName, DisposableBean bean) { disposableBeans.put(beanName, bean); } }要点说明:
- 在用于提供单例对象注册操作的
DefaultSingletonBeanRegistry类中,共有三个缓存对象的属性:singletonObjects、earlySingletonObjects、singletonFactories,如它们的名字一样,用于存放不同类型的对象(单例对象、早期的半成品单例对象、单例工厂对象)。 - 在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:
getSingleton、registerSingleton、addSingletonFactory。后面两个方法都比较简单,主要是getSingleton的操作,它是一层层处理不同时期的单例对象,直至拿到有效的对象。这与 Spring 官方getSingleton(String beanName, boolean allowEarlyReference)的处理逻辑一一对应。
2. 提前暴露对象
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) { Object bean = null; try { // 实例化 Bean bean = createBeanInstance(beanDefinition, beanName, args); // 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来 if (beanDefinition.isSingleton()) { Object finalBean = bean; addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean)); } // 实例化后判断 boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean); if (!continueWithPropertyPopulation) { return bean; } // 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值 applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); // 给 Bean 填充属性 applyPropertyValues(beanName, bean, beanDefinition); // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed", e); } // 注册实现了 DisposableBean 接口的 Bean 对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE Object exposedObject = bean; if (beanDefinition.isSingleton()) { // 获取代理对象 exposedObject = getSingleton(beanName); registerSingleton(beanName, exposedObject); } return exposedObject; } protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) { Object exposedObject = bean; for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { exposedObject = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(exposedObject, beanName); if (null == exposedObject) return exposedObject; } } return exposedObject; } // ... }要点说明:
- 在
AbstractAutowireCapableBeanFactory#doCreateBean方法中,主要是扩展了对象的提前暴露addSingletonFactory、单例对象的获取getSingleton以及注册registerSingleton三个操作。 - 这里提到的
getEarlyBeanReference就是定义在如 AOP 切面中的代理对象处理逻辑,可以参考源码中接口InstantiationAwareBeanPostProcessor#getEarlyBeanReference方法的实现。关于 AOP 代理如何通过BeanPostProcessor融入 Bean 生命周期(即DefaultAdvisorAutoProxyCreator的职责),可对照仓库《手撸 Spring》专栏 第13章《把 AOP 动态代理融入到 Bean 的生命周期》 学习。
3. 三层缓存的迁移轨迹
综合以上源码,一个被循环依赖涉及到的 Bean 在三个缓存中的迁移轨迹可以概括为:
| 缓存 | 存放内容 | 何时写入 | 何时取出 |
|---|---|---|---|
singletonFactories(三级) | ObjectFactory<?>工厂对象(lambda) | doCreateBean实例化后立即addSingletonFactory | 依赖方查找时执行getObject()提前曝光 |
earlySingletonObjects(二级) | 提前曝光的半成品/早期代理对象 | 从三级缓存getObject()后移入 | 依赖方二次查找直接命中 |
singletonObjects(一级) | 填充属性并初始化完成的成品对象 | registerSingleton | 所有后续getBean直接命中 |
依赖方查找顺序永远是:一级 → 二级 → 三级;而对象在缓存间的流转方向则是:三级(工厂)→ 二级(早期对象)→ 一级(成品)。
七、实战验证:老公、媳妇、婆婆与切面的循环依赖
为了验证三级缓存在普通对象依赖、FactoryBean 代理对象、AOP 切面对象三种场景下都能解决循环依赖,仓库第 17 章设计了一个贴近生活的案例:老公和媳妇互相依赖,婆婆是一个模拟成代理妈妈的职责,再配合一个切面来关心家庭生活。
1. 测试类与配置
老公类(Husband):依赖媳妇,提供查询方法。
public class Husband { private Wife wife; public String queryWife(){ return "Husband.wife"; } }媳妇类(Wife):依赖老公和婆婆(代理妈妈),提供查询方法。
public class Wife { private Husband husband; private IMother mother; // 婆婆 public String queryHusband() { return "Wife.husband、Mother.callMother:" + mother.callMother(); } }婆婆类(HusbandMother):实现FactoryBean<IMother>,通过 JDK 动态代理模拟"婚后媳妇妈妈的职责被婆婆代理了"。
public class HusbandMother implements FactoryBean<IMother> { @Override public IMother getObject() throws Exception { return (IMother) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IMother.class}, (proxy, method, args) -> "婚后媳妇妈妈的职责被婆婆代理了!" + method.getName()); } }切面类(SpouseAdvice):实现MethodBeforeAdvice,在方法执行前输出关怀信息。
public class SpouseAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("关怀小两口(切面):" + method); } }spring.xml 属性配置
<bean id="husband" class="cn.bugstack.springframework.test.bean.Husband"> <property name="wife" ref="wife"/> </bean> <bean id="wife" class="cn.bugstack.springframework.test.bean.Wife"> <property name="husband" ref="husband"/> <property name="mother" ref="husbandMother"/> </bean> <bean id="husbandMother" class="cn.bugstack.springframework.test.bean.HusbandMother"/> <!-- AOP 配置,验证三级缓存 --> <bean class="cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean id="beforeAdvice" class="cn.bugstack.springframework.test.bean.SpouseAdvice"/> <bean id="methodInterceptor" class="cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor"> <property name="advice" ref="beforeAdvice"/> </bean> <bean id="pointcutAdvisor" class="cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <property name="expression" value="execution(* cn.bugstack.springframework.test.bean.Wife.*(..))"/> <property name="advice" ref="methodInterceptor"/> </bean>这里的配置:husband依赖wife,wife依赖husband和mother(工厂代理对象),最后是关于 AOP 切面的依赖使用——恰好覆盖了"普通对象 + FactoryBean 代理 + AOP 切面"三类需要放入三级缓存的场景。
2. 单元测试与运行结果
@Test public void test_circular() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml"); Husband husband = applicationContext.getBean("husband", Husband.class); Wife wife = applicationContext.getBean("wife", Wife.class); System.out.println("老公的媳妇:" + husband.queryWife()); System.out.println("媳妇的老公:" + wife.queryHusband()); }运行结果:
老公的媳妇:Husband.wife 关怀小两口(切面):public java.lang.String cn.bugstack.springframework.test.bean.Wife.queryHusband() 媳妇的老公:Wife.husband、Mother.callMother:婚后媳妇妈妈的职责被婆婆代理了!callMother Process finished with exit code 0从测试结果可以看到:无论是简单对象依赖(老公依赖媳妇、媳妇依赖老公),还是代理工厂对象,或者是 AOP 切面对象,都可以在三级缓存下解决循环依赖问题。此外,从运行截图DefaultSingletonBeanRegistry#getSingleton中也可以看到,凡是需要三级缓存存放工厂对象的类,都会使用到getObject获取真实对象,并随后存入半成品对象earlySingletonObjects中以及移除工厂对象。
八、总结与边界
- 回顾全文:先从实际操作的例子开始,引导对循环依赖有一个整体的认识,也给出可以上手的解决示例,这样对后续 Spring 解决循环依赖的方案也就不会那么陌生了。
- 三级缓存并非绝对必须:只是在满足 Spring 自身创建原则(普通 Bean 全部初始化完成后,再处理代理对象的初始化)的前提下,它是必须的。如果提前创建 AOP 对象并保存到缓存中,二级缓存甚至一级缓存也一样可以解决简单场景的循环依赖。三级缓存的本质是"延迟 AOP 代理对象的创建时机",用空间与一层间接性换取了设计上的优雅、简单与可扩展。
- Spring 只能解决 setter 注入的循环依赖:对于构造函数注入和原型(prototype)作用域的循环依赖,Spring 无法解决——前者在
createBeanInstance阶段就形成死锁,后者因为每次请求都新建实例,不存在"提前暴露"的复用对象。这也是 《面经手册 · 第30篇》 中强调"Spring 只能解决 setter 注入"的原因。 - 工程实践建议:循环依赖可能并不是一个好的编码方式。如果是在自己的程序中,还是要尽可能使用更合理的设计模式规避循环依赖(如通过中间层解耦、事件驱动、或者调整依赖方向)。这些方式可能会增加代码量,但在维护上会更加方便。
延伸阅读:本仓库中关于 Spring 主题的完整内容还包括《面经手册 · 第29篇》《Spring IOC 特性有哪些,不会读不懂源码!》 与《手撸 Spring》专栏的 第13章、第17章,读者可以按章节顺序从零手写一个支持循环依赖与 AOP 的 Spring 框架,获得比阅读本文更深的源码级理解。
- 文档
- 教程
- 后端
【免费下载链接】CodeGuide
:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!
相关推荐
Spring IoC 循环依赖源码解析:三级缓存与"提前暴露"机制
Spring IoC 循环依赖源码解析:三级缓存与"提前暴露"机制 导读 本文以 Spring Framework 5.3.18 源码为基线,完整剖析 IoC
文档教程知识库从零手写 Spring:CodeGuide 仓库中的渐进式源码实践,吃透 IOC、AOP 与三级缓存循环依赖
从零手写 Spring:CodeGuide 仓库中的渐进式源码实践,吃透 IOC、AOP 与三级缓存循环依赖 《手写Spring:渐进式源码实践》是 CodeG
文档教程后端逐行精讲聚宝盆(Cornucopia)的tuning_train.py:损失掩码、梯度累积与DDP训练细节
逐行精讲聚宝盆 Cornucopia 的tuning_train.py:损失掩码、梯度累积与DDP训练细节 聚宝盆(Cornucopia)是一个基于 LLaMA
人工智能大模型微调LoRANLP金融科技
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考