Spring 怎么解决循环依赖的呢?

Spring解决循环依赖机制与三级缓存解析,
本文详细解释了Spring框架如何通过三级缓存机制处理循环依赖,特别关注了setter注入和构造器注入的区别,以及为何需要三级缓存而非二级。

什么是循环依赖

Spring循环依赖

Spring 循环依赖:简单说就是自己依赖自己,或者和别的 Bean 相互依赖。

鸡和蛋

只有单例的 Bean 才存在循环依赖的情况,原型(Prototype)情况下,Spring 会直接抛出异常。原因很简单,AB 循环依赖,A 实例化的时候,发现依赖 B,创建 B 实例,创建 B 的时候发现需要 A,创建 A1 实例……无限套娃,直接把系统干垮。

Spring 可以解决哪些情况的循环依赖?

Spring 不支持基于构造器注入的循环依赖,但是假如 AB 循环依赖,如果一个是构造器注入,一个是 setter 注入呢?

看看几种情形:

循环依赖的几种情形

第四种可以而第五种不可以的原因是 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。

所以简单总结,当循环依赖的实例都采用 setter 方法注入的时候,Spring 可以支持,都采用构造器注入的时候,不支持,构造器注入和 setter 注入同时存在的时候,看天。

那 Spring 怎么解决循环依赖的呢?

我们都知道,单例 Bean 初始化完成,要经历三步:

Bean初始化步骤

注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:

  1. 一级缓存 : Map singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
  2. 二级缓存 : Map earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例
  3. 三级缓存 : Map> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。

三级缓存

我们来看一下三级缓存解决循环依赖的过程:

当 A、B 两个类发生循环依赖时: 循环依赖

A 实例的初始化过程:

  1. 创建 A 实例,实例化的时候把 A 对象⼯⼚放⼊三级缓存,表示 A 开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道

1

  1. A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B
  2. 同样,B 注⼊属性时发现依赖 A,它就会从缓存里找 A 对象。依次从⼀级到三级缓存查询 A,从三级缓存通过对象⼯⼚拿到 A,发现 A 虽然不太完善,但是存在,把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成,把 B 放入⼀级缓存。

2

  1. 接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存
  2. 最后,⼀级缓存中保存着实例化、初始化都完成的 A、B 对象

5

所以,我们就知道为什么 Spring 能解决 setter 注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的化,那么都得在实例化这一步完成注入,所以自然是无法支持了。

为什么要三级缓存?⼆级不⾏吗?

不行,主要是为了⽣成代理对象。如果是没有代理的情况下,使用二级缓存解决循环依赖也是 OK 的。但是如果存在代理,三级没有问题,二级就不行了。

因为三级缓存中放的是⽣成具体对象的匿名内部类,获取 Object 的时候,它可以⽣成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象。

假设只有⼆级缓存的情况,往⼆级缓存中放的显示⼀个普通的 Bean 对象,Bean 初始化过程中,通过 BeanPostProcessor 去⽣成代理对象之后,覆盖掉⼆级缓存中的普通 Bean 对象,那么可能就导致取到的 Bean 对象不一致了。

二级缓存不行的原因

<think>好的,我现在要解决用户的问题:“Spring 怎么解决循环依赖呢?”首先,我需要回忆一下Spring框架处理循环依赖的机制,确保自己理解正确,然后以清晰的中文结构逐步解释给用户。 循环依赖指的是两个或多个Bean相互依赖,比如Bean A依赖Bean B,而Bean B又依赖Bean A。这种情况下,如果不处理,可能会导致无限递归或创建失败。Spring如何解决这个问题呢? 首先,我应该想到Spring的三级缓存机制。三级缓存是解决循环依赖的关键。但需要具体说明每一级缓存的作用。一级缓存存放完全初始化好的Bean,二级缓存存放早期暴露的对象(尚未填充属性),三级缓存存放Bean的工厂对象,用于生成早期对象。 接下来,需要详细描述创建Bean的过程。当创建Bean A时,首先通过构造函数实例化,然后将其工厂对象放入三级缓存。接着,在填充属性时发现需要Bean B,这时会去创建Bean B。Bean B同样实例化后放入三级缓存,填充属性时又需要Bean A。此时,Bean A虽然未完全初始化,但已经存在于三级缓存中,因此可以通过工厂获取早期引用,放入二级缓存。Bean B完成初始化后移入一级缓存,Bean A接着完成属性填充和初始化,最终也被移到一级缓存。这样循环依赖就被解决了。 需要注意的是,构造函数注入的循环依赖无法解决,因为此时Bean还未实例化,无法提前暴露引用。只能通过setter方法或字段注入来处理。这部分需要特别指出,避免用户误用构造函数导致问题。 此外,可以提到Spring的默认作用域是单例,而原型作用域的Bean无法解决循环依赖,因为每次都会新建实例,无法通过缓存获取。这也是需要注意的地方。 最后,总结Spring解决方式,并给出实际开发中的建议,比如避免循环依赖的设计,优先通过重构代码消除依赖环,或者在无法避免时使用setter注入等。确保用户不仅了解机制,还能正确应用。</think>Spring通过三级缓存机制解决循环依赖问题,以下是具体实现原理和步骤拆解: --- ### 一、循环依赖的三种类型 1. **构造器循环依赖**(无法解决) 2. **Setter方法/字段注入循环依赖**(可解决) 3. **原型(prototype)作用域循环依赖**(无法解决) > Spring只能解决**单例作用域**且**使用Setter/字段注入**的循环依赖 --- ### 二、三级缓存机制 Spring容器内部维护三个缓存Map: ```java // 一级缓存:完整Bean(完成初始化) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:早期Bean(已实例化但未填充属性) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:对象工厂(用于生成早期引用) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); ``` --- ### 三、解决流程(以A<->B循环依赖为例) 1. **创建Bean A** - 调用`getBean(A)`,在一级缓存未找到 - 执行`createBean()`实例化A(此时A对象尚未初始化) - 将A的**对象工厂**存入三级缓存 ```java addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean)); ``` 2. **填充属性** - 发现A依赖B,触发`getBean(B)` 3. **创建Bean B** - 同样流程实例化B,将B的工厂存入三级缓存 - 填充B属性时发现依赖A,触发`getBean(A)` 4. **解决A的依赖** - 从三级缓存获取A的工厂,生成**早期引用** - 将A早期引用放入二级缓存,同时删除三级缓存中的A ```java ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } ``` 5. **完成B的初始化** - B获得A的早期引用后,完成属性注入和初始化 - B被放入一级缓存 6. **完成A的初始化** - A获得完全初始化的B实例 - A完成后续初始化流程,移入一级缓存 --- ### 四、关键设计点 1. **提前暴露机制**:在对象实例化后(构造函数执行完)立即暴露引用 2. **对象状态分离**: - 一级缓存:完整可用对象 - 二级缓存:半成品对象(防止多次创建代理对象) - 三级缓存:延迟生成早期引用的工厂 --- ### 五、使用注意事项 1. **避免构造器注入循环依赖** ```java // 这种场景Spring会直接抛出BeanCurrentlyInCreationException @Service public class A { private final B b; public A(B b) { this.b = b; } } @Service public class B { private final A a; public B(A a) { this.a = a; } } ``` 2. **推荐解决方案**: - 使用`@Autowired`字段注入或Setter方法注入 - 重构代码结构,使用`@Lazy`延迟加载 - 引入中间层(如事件机制、抽象接口) --- ### 六、实现原理验证 可以通过调试模式观察缓存变化: 1. 在`DefaultSingletonBeanRegistry`类设置断点 2. 观察三个Map的内容变化 3. 查看`getSingleton()`方法的执行路径 --- 建议在实际开发中尽量避免循环依赖,优先通过代码重构解决问题。当确实需要时,确保使用正确的注入方式并理解Spring解决机制。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每周都想吃火锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值