Spring如何解决循环依赖

Spring 通过三级缓存机制提前暴露未初始化 Bean的方式,自动解决了setter 注入和字段注入场景下的循环依赖问题。

一、三级缓存的核心概念

Spring 容器在创建 Bean 的过程中,使用三个缓存来管理 Bean 的生命周期:

  1. 单例池(一级缓存)singletonObjects
    存储已经完全初始化(属性注入完成)的单例 Bean。
  2. 提前暴露的单例对象(二级缓存)singletonFactories
    存储原始的 Bean 对象(未完成属性注入),但通过工厂可以获取早期引用。
  3. 单例工厂(三级缓存)earlySingletonObjects
    存储 Bean 的工厂对象,用于生成早期暴露的 Bean 引用。

二、自动解决循环依赖的流程

假设存在循环依赖:A 依赖 BB 依赖 A

1. 创建 Bean A
  1. A 进入创建状态,将 A 的工厂对象放入三级缓存

    // 三级缓存:存储A的工厂
    singletonFactories.put("a", () -> getEarlyBeanReference(beanName, mbd, bean));
    
     

    此时 A 尚未完成属性注入,但已通过工厂暴露了早期引用。

  2. 注入 A 的依赖 B
    Spring 发现 B 不存在,开始创建 B。

2. 创建 Bean B
  1. B 进入创建状态,将 B 的工厂对象放入三级缓存
  2. 注入 B 的依赖 A
    Spring 从缓存中查找 A,发现 A 在三级缓存中,通过工厂获取 A 的早期引用,并将 A 从三级缓存移至二级缓存
    // 二级缓存:存储A的早期引用
    earlySingletonObjects.put("a", getEarlyBeanReference(beanName, mbd, bean));
    
  3. B 完成属性注入,并将 B 放入一级缓存(单例池)。
3. 继续完成 A 的创建
  1. A 获取到已完成的 B,完成自身属性注入。
  2. A 放入一级缓存,循环依赖解决。

三、关键机制解析

1. 为什么构造器注入无法解决循环依赖?
  • 构造器注入在实例化阶段就需要完成依赖注入,而此时 Bean 尚未放入缓存(包括三级缓存)。
  • 若 A 的构造器依赖 B,B 的构造器依赖 A,会导致:
    1. 创建 A 时,因需要 B 而暂停。
    2. 创建 B 时,因需要 A 而暂停。
    3. 形成死循环,最终抛出BeanCurrentlyInCreationException
2. 为什么 setter / 字段注入可以解决?
  • setter / 字段注入在实例化后进行,此时 Bean 已通过三级缓存暴露早期引用。
  • 当 B 需要 A 时,可以从三级缓存获取 A 的早期引用(尽管 A 尚未完成初始化),从而打破循环。
3. 三级缓存的作用
  • 三级缓存(singletonFactories)
    存储 Bean 的工厂对象,允许在 Bean 未完全初始化时通过getEarlyBeanReference()方法生成早期引用。这一步可能涉及 AOP 代理的创建(如果 Bean 需要被代理)。
  • 二级缓存(earlySingletonObjects)
    存储早期引用的实际对象,避免重复创建代理或处理。
  • 一级缓存(singletonObjects)
    最终存储完全初始化的 Bean,确保用户获取到的是完整可用的实例。

四、源码验证

Spring 在DefaultSingletonBeanRegistry类中实现了三级缓存逻辑:

// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);

// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

关键方法getSingleton()展示了缓存查询顺序:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    // 2. 如果Bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 3. 从二级缓存获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 4. 二级缓存没有,且允许早期引用,则从三级缓存通过工厂获取
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.singletonObjects.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;
}

五、总结

Spring 解决循环依赖的核心是:通过三级缓存提前暴露未初始化的 Bean,允许其他 Bean 引用这个早期实例。这一机制只适用于:

  1. 单例作用域的 Bean(原型作用域每次创建新实例,无法解决循环依赖)。
  2. setter / 字段注入(构造器注入因实例化阶段依赖未解决而失败)。

若项目中存在复杂的循环依赖,建议优先重构代码设计,而非依赖此机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值