Spring的循环依赖还不会?

Spring循环依赖解析
本文详细解释了Spring框架中循环依赖的概念及其解决方案。通过三级缓存机制,Spring能够有效地解决循环依赖问题,确保Bean实例化过程的顺利进行。

Spring的循环依赖还不会?

一、什么是循环依赖?

循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleA,则它们最终反映为一个环。例如:

public class A{
    private B b; 
}

public class B{
    private A a;
}

//A和B两个对象,A对象中引用B,B对象中引用A,这样就形成了循环依赖

二、如何解决循环依赖问题?

注入方式是setter且singleton, 就不会有循环依赖问题,解决循环依赖主要使用了三级缓存的概念,下面将详细介绍三级缓存以及三级缓存是如何解决循环依赖问题的。

构造器注入没有办法解决循环依赖, 想让构造器注入支持循环依赖,是不存在的

三、什么是三级缓存

所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个Map。

	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • 第一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象

  • 第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)

  • 第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂

四、三级缓存是如何解决循环依赖问题的?

1.对象产生的过程

创建对象的时候,主要会经历四个方法,分别是:getSingleton,doCreateBean,populateBean,addSingleton

  • getSingleton:希望从容器里面获得单例的bean,没有的话执行方法2
  • doCreateBean: 没有就创建bean
  • populateBean: 创建完了以后,要填充属性
  • addSingleton: 填充完了以后,再添加到容器进行使用

2.循环依赖产生的示意图

在这里插入图片描述

如上图所示,当新创建A和B两个对象的时候,就会形成闭环,造成循环依赖的现象,三级缓存就是用来打破闭环,解决循环依赖。

引入三级缓存之后,效果如下:有专门存放半成品的缓存,就不会陷入闭环的尴尬。

在这里插入图片描述

3.对象的迁移过程

  • 示意图

在这里插入图片描述

  • 文字描述

    1. getBean在容器(包括一二三级缓存)中获得A对象,没有则创建A对象;
    2. 初始化A对象,将A对象的ObjectFactory的lamda放入三级缓存;
    3. populateBean,填充属性B对象;
    4. getBean在容器(包括一二三级缓存)中获得B对象,没有则创建B对象;
    5. 初始化B对象,将B对象的ObjectFactory的lambda放入三级缓存;
    6. populateBean,填充属性A对象,在三级缓存中找到A对象(正在创建中的A对象);
    7. 调用A的ObjectFactory,创建半成品,将创建好的半成品放到二级缓存,并将三级缓存中的A移除;
    8. 取到A对象之后,给B对象中的A属性赋值,那么此时B对象则创建完成,放到一级缓存,三级缓存删除B;
    9. 此时再给A对象中的B对象赋值,将成品A放到一级缓存,将二级缓存中的A删除;
    10. 此时A对象创建完成,再创建B对象,但是在容器中直接拿到了B对象,则无需再次创建,完工。
<think>嗯,用户现在想了解Spring框架中循环依赖的解决方案。之前我已经回答过Spring Bean的生命周期,并且用户提到了相关的站内引用,不过他们现在更关注循环依赖的处理。首先,我需要回忆Spring是如何处理循环依赖的,尤其是三级缓存机制。 用户提供的引用里提到,Spring通过三级缓存解决循环依赖,特别是singleton作用域的bean。三级缓存包括singletonObjects、earlySingletonObjects和singletonFactories。可能需要在回答中详细解释这三个缓存的作用,以及它们如何协作。比如,当创建Bean A时,它会被放入三级缓存中,如果它依赖Bean B,而Bean B又依赖Bean A,这时三级缓存允许提前暴露半成品的Bean A,供Bean B完成初始化。 另外,用户提到的Spring Boot 2.6.0之后对循环依赖的处理更严格,可能需要说明版本变化带来的影响,比如默认禁止循环依赖,开发者需要显式配置才能允许。这时候应该建议用户优先考虑重构代码,而不是依赖技术手段解决,因为循环依赖可能暗示设计问题。 还要注意用户之前的问题中提到了Bean的生命周期,所以在解释循环依赖解决方案时,可能需要结合Bean的实例化和属性注入阶段,说明在哪个阶段三级缓存被使用。比如,在实例化后、属性注入前,将对象工厂放入三级缓存,这样当发生循环依赖时,可以提前获取到未完全初始化的bean实例。 用户可能还会关心不同作用域的bean是否支持循环依赖,比如原型作用域的bean,Spring无法解决,这时候需要说明原因,并建议避免这种情况。此外,@Lazy注解的使用场景和限制也需要提到,它如何延迟依赖的解析,从而打破循环,但可能带来的潜在问题。 需要确保回答结构清晰,分点说明解决方案、注意事项和常见方法,同时结合代码示例,如使用@Lazy或setter注入的例子。最后,相关问题部分可以引导用户深入理解三级缓存、设计模式的应用以及版本差异的影响。</think>以下是Spring框架解决循环依赖的核心方案和关键细节分析: --- ### Spring循环依赖解决方案 #### 核心机制:三级缓存 Spring通过三级缓存解决**singleton作用域**的循环依赖问题: $$ \begin{aligned} 1. &\text{一级缓存(完全初始化): singletonObjects} \\ 2. &\text{二级缓存(半成品对象): earlySingletonObjects} \\ 3. &\text{三级缓存(对象工厂): singletonFactories} \end{aligned} $$ #### 解决流程(以A↔B循环依赖为例) ```mermaid flowchart TD A[创建Bean A] --> B[实例化A对象] B --> C[将A对象工厂放入三级缓存] C --> D[注入B属性] D --> E{检查B是否在缓存中} E --> |未找到| F[开始创建Bean B] F --> G[实例化B对象] G --> H[将B对象工厂放入三级缓存] H --> I[注入A属性] I --> J{从三级缓存获取A对象工厂} J --> K[通过工厂获取A的早期引用] K --> L[将A升级到二级缓存] L --> M[完成B的初始化] M --> N[将B放入一级缓存] N --> O[返回B到A的注入点] O --> P[完成A的初始化] P --> Q[将A放入一级缓存] ``` #### 关键特性 1. **仅支持单例模式** 原型(prototype)作用域的Bean无法解决循环依赖[^1] 2. **构造器注入限制** 若循环依赖通过构造器注入发生,Spring会直接抛出`BeanCurrentlyInCreationException`[^2] 3. **自动代理冲突规避** 通过`SmartInstantiationAwareBeanPostProcessor`接口处理AOP代理对象的循环依赖 --- ### 常见解决方案对比 | 方法 | 实现方式 | 适用场景 | |---------------------|----------------------------------|----------------------------| | @Lazy延迟加载 | 在依赖项添加@Lazy注解 | 简单循环且无需立即初始化的场景 | | Setter/Field注入 | 替换构造器注入为属性注入 | 大多数单例Bean循环依赖场景 | | 接口分离重构 | 提取公共接口解耦依赖关系 | 设计优化型解决方案 | | 上下文分离 | 使用@DependsOn明确初始化顺序 | 复杂依赖链场景 | --- ### Spring Boot 2.6+新特性 1. **严格模式默认开启** 启动时检测到循环依赖会直接报错,需通过配置开启兼容模式: ```properties spring.main.allow-circular-references=true ``` 2. **设计优先原则** 官方建议重构代码而不是依赖技术方案解决循环依赖 --- #### 代码示例 ```java // 使用@Lazy解决构造器循环依赖 @Service public class ServiceA { private final ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { this.serviceB = serviceB; } } // Setter注入方案 @Service public class ServiceB { private ServiceA serviceA; @Autowired public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; } } ``` --- ### 相关问题 1. **三级缓存如何避免并发问题?** 2. **为什么原型作用域的Bean不能解决循环依赖?** 3. **Spring如何检测循环依赖的存在?** 4. **循环依赖对事务管理有什么影响?** [^1]: Spring通过提前暴露对象引用的方式解决单例Bean的循环依赖问题 [^2]: Spring Boot 2.6+版本对循环依赖采取更严格的设计规范
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值