【Spring】Spring是如何解决循环依赖问题的

06-01 1299阅读

Spring 通过 三级缓存 和 提前暴露对象引用 的机制解决单例 Bean 的循环依赖问题。以下是详细原理和实现流程:


一、循环依赖的场景

假设存在以下相互依赖的 Bean:

@Component
public class A {
    @Autowired
    private B b;
}
@Component
public class B {
    @Autowired
    private A a;
}

此时 Spring 需要处理 A → B → A 的循环依赖。


二、Spring 的解决机制

1. 三级缓存

Spring 使用三级缓存存储不同状态的 Bean:

缓存名称作用
singletonObjects存储完全初始化后的单例 Bean(一级缓存)
earlySingletonObjects存储早期暴露的 Bean(未完成属性填充和初始化,二级缓存)
singletonFactories存储 Bean 的 ObjectFactory(用于生成早期引用,三级缓存)
2. Bean 创建流程(以 A → B → A 为例)

• 步骤 1:创建 Bean A

  1. 实例化 A(调用构造函数,但属性未填充)。
  2. 将 A 的 ObjectFactory 放入 三级缓存(singletonFactories)。
  3. 开始填充 A 的属性,发现依赖 B。

• 步骤 2:创建 Bean B

  1. 实例化 B(调用构造函数,属性未填充)。
  2. 将 B 的 ObjectFactory 放入 三级缓存。
  3. 开始填充 B 的属性,发现依赖 A。

• 步骤 3:解决依赖 A

  1. 从 三级缓存 中获取 A 的 ObjectFactory,生成 A 的早期引用。
  2. 将 A 的早期引用放入 二级缓存(earlySingletonObjects),并从三级缓存中移除。
  3. 将 A 的早期引用注入到 B 中,完成 B 的初始化。
  4. 将 B 的完整实例放入 一级缓存(singletonObjects)。

• 步骤 4:完成 Bean A 的初始化

  1. 将已注入 B 的 A 实例继续初始化。
  2. 将 A 的完整实例从 二级缓存 移到 一级缓存。

三、核心源码解析

• 关键类:DefaultSingletonBeanRegistry

• 缓存操作:

// 从缓存获取 Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                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;
}

四、限制条件

1. 仅支持单例(Singleton)作用域的 Bean

• 原型(Prototype)作用域的 Bean 无法解决循环依赖,Spring 直接抛出 BeanCurrentlyInCreationException。

2. 不支持构造器注入的循环依赖

• 构造器注入的循环依赖无法解决(实例化阶段就需要依赖对象,但此时依赖的 Bean 未创建)。

• 错误示例:

@Component
public class A {
    private B b;
    public A(B b) { this.b = b; } // 构造器注入导致循环依赖无法解决
}
3. 需要启用代理

• 若 Bean 被 AOP 代理(如通过 @Async、@Transactional),需确保代理对象能正确暴露到缓存中。


五、如何避免循环依赖?

  1. 代码设计优化:

    【Spring】Spring是如何解决循环依赖问题的
    (图片来源网络,侵删)

    • 避免双向依赖,改为单向依赖。

    • 使用 @Lazy 延迟加载(将依赖标记为惰性初始化)。

    【Spring】Spring是如何解决循环依赖问题的
    (图片来源网络,侵删)

    • 通过事件驱动(ApplicationEvent)解耦 Bean。

  2. 依赖注入方式:

    【Spring】Spring是如何解决循环依赖问题的
    (图片来源网络,侵删)

    • 优先使用 Setter 注入 而非构造器注入。

  3. 示例:使用 @Lazy:

    @Component
    public class A {
        @Autowired
        @Lazy // 延迟注入 B 的代理对象
        private B b;
    }
    

六、总结

机制说明
三级缓存通过缓存不同状态的 Bean 解决循环依赖
提前暴露引用在 Bean 未完成初始化前,提前将引用暴露给其他 Bean 使用
单例限制仅支持单例 Bean,原型 Bean 和构造器注入的循环依赖无法解决

最佳实践:优先通过代码设计避免循环依赖,仅在必要场景下依赖 Spring 的自动解决机制。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码