Spring的循环依赖问题

出现原因

一个类初始化的时候需要另一个类的实例,反过来也是

框架怎么解决的

spring默认是支持自动解决单例的循环依赖问题的

关键在于提前暴露:在A实例化完但是还没依赖注入(属性填充)的时候,直接把A暴露出来给B用


三级缓存

IOC容器里面维护了三个Map存储不同阶段的Bean实例

Map名称存储内容作用
一级缓存singletonObjects存储完整、初始化完毕的单例Bean。(bean)正常的Bean容器。
二级缓存earlySingletonObjects存储提前暴露的Bean实例,可能是代理对象或原始对象。(Bean)存放已暴露但尚未完全初始化的Bean。
三级缓存singletonFactories存储一个工厂对象 (逻辑)负责判断要不要代理并且返回给二级缓存实例

  1. A开始创建
  2. (A到3级)暴露A的工厂:实例化A但是在DI之前,把ObjectFactory放到三级缓存里,来负责A的早期引用
  3. A的属性注入: A开始进行属性注入,发现依赖B,于是开始查找B。
  4. B开始创建
  5. B的属性注入: B开始进行属性注入,发现依赖A,于是开始查找A。
  6. 查找A(三级缓存触发): B尝试从一级缓存、二级缓存查找A,都未找到。它会访问三级缓存,发现A对应的 ObjectFactory
  7. (A到2级)工厂执行: B执行这个 ObjectFactory。Factory会判断:A是否需要AOP代理?

    • 如果需要代理,Factory会生成A的代理对象。
    • 如果不需要代理,Factory直接返回A的原始对象。
    • 无论返回什么, 这个对象都会被放入二级缓存 ,并从三级缓存中移除。
  8. (B从2级拿的A)B获取A并初始化: B拿到A的半成品,完成自己的属性注入,并完成初始化。此时B是一个完整的Bean。
  9. A完成初始化: B完成后,Spring回到A的初始化流程。A拿到完整的B,完成自己的属性注入。
  10. (A到1级)A最终入库: A完成所有初始化步骤(包括 init 方法、AOP代理创建等),最终将完整的A放入一级缓存 (singletonObjects),并从二级缓存中移除。

总结: 第三级缓存 (singletonFactories) 是一个缓冲层,它将生成早期Bean实例的决定权延迟到了真正需要解决循环依赖的那一刻,从而确保了AOP代理的正确性和完整性。


情况总结

场景分类AOP 需求注入方式循环依赖最少所需的缓存级别原因
正常 Bean 创建有/无-无问题一级缓存仅需最终存储完整的 Bean 实例。
循环依赖 (简单)均无 AOPSetter/Field能解决二级缓存仅需提前暴露原始实例,无需 AOP 决策逻辑。
循环依赖 (复杂)至少一方有 AOPSetter/Field能解决三级缓存需要 ObjectFactory 延迟 AOP 代理的生成和决策,确保早期引用是 AOP 后的对象。
循环依赖 (无效)有/无Constructor不能解--无法在实例化前提前暴露实例。

框架不能解决的

构造器互相注入的

为什么?因为构造器创建bean的时候DI和实例化的是一个步骤(调用构造器方法)

过程:尝试实例化 A 时发现需要 B,转而实例化 B;实例化 B 时发现需要 A,又转而实例化 A。死锁

解决:a.改为Setter 注入或者@Resource注解 b.在其中一个Bean对象的构造函数参数上使用@Lazy

Prototype作用域的

为什么?三级缓存是为单例模式设计的

过程:同上

解决:a.改为单例 b.手动模拟三级缓存

注入 ObjectProvider<B>Provider<B>,而非 B 实例。在需要 B 时,手动调用 getObject()get() 获取新的原型实例。