Spring循环依赖问题及解决办法

Spring 框架通过三级缓存机制优雅地解决了单例 bean 的循环依赖问题。下面我将详细解释这个过程。

一、循环依赖的本质问题

当两个或多个 bean 相互依赖时,可能形成循环依赖:

java

class A {
    @Autowired private B b;
}

class B {
    @Autowired private A a;
}

若按常规创建流程:

  1. 创建 A 时发现需要 B,暂停 A 的创建去创建 B;
  2. 创建 B 时发现需要 A,但 A 尚未完成初始化,导致死锁。

二、Spring 的三级缓存机制

Spring 通过三个 Map 实现缓存(位于DefaultSingletonBeanRegistry类):

java

// 一级缓存:存储完全初始化好的单例bean(已创建、已注入、已初始化)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存储提前暴露的单例bean(已创建,但未完成属性注入和初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 三级缓存:存储ObjectFactory(用于生成早期暴露对象的工厂)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

三、三级缓存如何解决循环依赖?

核心流程(以 A→B→A 循环为例):
  1. 创建 A
    • A 开始创建,首先将 A 的ObjectFactory放入三级缓存(此时 A 刚实例化,未注入属性)。
  1. java

  1. A 需要 B
    • A 在属性注入阶段发现依赖 B,暂停 A 的创建,开始创建 B。
  1. 创建 B
    • B 实例化后,将 B 的ObjectFactory放入三级缓存。
    • B 在属性注入阶段发现依赖 A,从缓存中获取 A。
  1. B 获取 A
    • B 首先从一级缓存查 A,未找到;
    • 再从二级缓存查 A,未找到;
    • 最后从三级缓存获取 A 的ObjectFactory,通过工厂获取 A 的早期引用(未完成初始化的 A)。
    • 将 A 的早期引用从三级缓存移至二级缓存。
  1. B 完成创建
    • B 成功注入 A 的早期引用,完成自身初始化,放入一级缓存。
  1. A 继续创建
    • A 获取已完成创建的 B,完成自身初始化,放入一级缓存。
关键代码(从缓存获取 bean):

java

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) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 5. 从三级缓存获取ObjectFactory
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 6. 通过工厂获取早期引用
                            singletonObject = singletonFactory.getObject();
                            // 7. 将早期引用放入二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 8. 从三级缓存移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

四、为什么需要三级缓存?二级不够吗?

必须使用三级缓存的核心原因AOP 代理

若 A 是代理对象(如使用 @Transactional):

  • 二级缓存直接存储 A 的原始实例,B 注入的将是 A 的原始对象,而非代理对象。
  • 三级缓存通过ObjectFactory在获取早期引用时动态生成代理对象(若有 AOP),保证 B 注入的是 A 的代理对象。

示例代码(添加 AOP 代理逻辑):

java

// 创建bean实例后,添加ObjectFactory到三级缓存
addSingletonFactory(beanName, () -> {
    // 若需要代理,在此生成代理对象
    return getEarlyBeanReference(beanName, mbd, bean);
});

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            // SmartInstantiationAwareBeanPostProcessor会在此生成代理对象
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = 
                    (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

五、循环依赖的限制与注意事项

  1. 仅支持单例模式:原型(prototype)作用域的 bean 不支持循环依赖,因为每次请求都会创建新实例,无法缓存。
  2. 构造器注入无法解决:若依赖通过构造器注入,实例化时就需要依赖对象,此时缓存尚未建立。
  3. 依赖必须是提前暴露的:若 bean 的生命周期中存在某些步骤阻止其提前暴露(如 @PostConstruct 中抛出异常),循环依赖可能失败。

六、总结

Spring 的三级缓存机制通过提前暴露未完成初始化的 bean,结合动态生成代理对象的能力,巧妙解决了单例 bean 的循环依赖问题。核心流程可概括为:

  1. 三级缓存分工
    • 一级缓存:存储完整的 bean。
    • 二级缓存:存储早期暴露的原始 bean 或代理 bean。
    • 三级缓存:存储生成早期 bean 的工厂,支持 AOP 动态代理。
  1. 执行流程
    • 创建 bean 时先将工厂放入三级缓存。
    • 发生循环依赖时,通过工厂从三级缓存获取早期引用,放入二级缓存。
    • bean 完全初始化后,从二级缓存移除,放入一级缓存。

理解三级缓存机制,对掌握 Spring 的 bean 生命周期和依赖注入原理至关重要。‘

是的,你理解得非常准确!Spring 的三级缓存机制核心就是解决循环依赖场景下,如何让 A 和 B 获取到对方的 “早期引用”(未完全初始化的 bean),从而打破死锁。

关键逻辑再梳理

  1. 传统方式的问题
    • 若 A 依赖 B,B 依赖 A,按常规流程:
      • A 创建到一半等待 B → B 创建到一半等待 A → 死循环。
  1. 三级缓存的解决方案
    • 提前暴露机制:当 A 刚实例化(未注入属性)时,就将其 “早期引用” 通过ObjectFactory放入三级缓存。
    • 动态代理支持:若 A 需要 AOP 代理,三级缓存的ObjectFactory会在被调用时动态生成代理对象(而非原始实例)。
    • 依赖注入顺序
      • B 在创建时发现依赖 A,直接从三级缓存获取 A 的早期引用(可能是代理对象)。
      • B 完成创建后,A 再注入已完成的 B,最终 A 也完成创建。

防止 “依赖已初始化完毕的 bean” 的关键点

  • 允许 “半成品” bean:Spring 打破了 “必须完全初始化后才能被引用” 的限制,允许 bean 在未完成属性注入和初始化时就被其他 bean 引用。
  • 保证最终一致性:虽然引用的是早期 bean,但 Spring 通过后续步骤确保所有属性最终正确注入,所有初始化方法(如@PostConstruct)最终被调用。

为什么需要三级缓存?

若只有二级缓存(直接存早期 bean):

  • 当 A 需要代理时,B 会注入 A 的原始实例(而非代理对象),导致 AOP 失效。
  • 三级缓存通过ObjectFactory延迟生成代理对象,确保 B 注入的是经过 AOP 增强的代理 A。

总结

三级缓存机制的本质是:在不破坏单例模式和 AOP 的前提下,通过 “提前暴露未完成初始化的 bean” 和 “动态生成代理对象”,让循环依赖的 bean 能够互相引用对方的早期状态,最终完成完整初始化

三、三级缓存如何解决?

三级缓存通过ObjectFactory延迟生成代理对象,关键在 **“按需生成代理”**:

java

// 三级缓存的正确实现(简化伪代码)
// 1. 创建A时,放入三级缓存的是工厂,而非原始实例
singletonFactories.put(beanName, () -> {
    // 关键点:在这里判断是否需要代理,若需要则生成代理对象
    return getEarlyBeanReference(beanName, mbd, bean);
});

// 2. 当B需要A时,从三级缓存获取工厂并调用
Object a = singletonFactory.getObject(); // 此时才触发代理生成逻辑

核心流程详解

  1. A 实例化后:将ObjectFactory放入三级缓存,工厂内部逻辑包含 “是否需要代理” 的判断。
  2. B 创建时依赖 A
    • 从三级缓存获取ObjectFactory
    • 调用工厂的getObject()方法,触发getEarlyBeanReference()
    • 在此方法中,Spring 通过SmartInstantiationAwareBeanPostProcessor检查 A 是否需要代理:
      • 若需要,则生成代理对象并返回;
      • 若不需要,则返回原始实例。
  1. 代理对象存入二级缓存:生成的代理对象被放入二级缓存,后续其他 bean 再获取 A 时,直接从二级缓存拿到正确的代理对象。

四、对比示意图

二级缓存方案(失败)

plaintext

A实例化 → 原始A存入二级缓存 → B创建时获取原始A → B完成 → A继续初始化 → A被代理增强
↑                                           |
└───────────────────────────────────────────┘
B依赖的是原始A,而非代理A!
三级缓存方案(成功)

plaintext

A实例化 → A的ObjectFactory存入三级缓存 → B创建时获取A的工厂 → 工厂生成代理A → 代理A存入二级缓存
↑                                                      |
↓                                                      ↓
A继续初始化 → 最终A(代理)存入一级缓存 ←─── B完成(依赖的是代理A)

五、关键区别总结

缓存级别

存储内容

解决的问题

二级缓存

原始 bean 实例(提前暴露)

无法处理 AOP 代理,导致循环依赖中注入原始对象

三级缓存

ObjectFactory(延迟生成代理)

通过延迟代理生成,确保循环依赖中注入正确的代理对象

六、为什么代理必须延迟生成?

  1. 代理生成条件:需要等 bean 实例化完成,且所有BeanPostProcessor处理后,才能确定是否需要代理。
  2. 循环依赖的矛盾:在 A 还未完成初始化时,B 就需要引用 A,此时代理可能尚未生成。
  3. 三级缓存的巧妙之处:通过ObjectFactory将代理生成逻辑延迟到 “真正被引用时”,而非 “实例化后立即生成”。

七、验证代码示例

假设 A 是被 @Transactional 注解的 Service:

java

@Service
public class A {
    @Autowired private B b;
    
    @Transactional
    public void doSomething() {
        // 事务方法
    }
}

@Service
public class B {
    @Autowired private A a;
    
    public void callA() {
        a.doSomething(); // 此处必须调用代理A,才能触发事务
    }
}

若使用二级缓存:

  • B 中的 a 会是原始实例 → 调用doSomething()时无事务管理。

使用三级缓存:

  • B 中的 a 是代理实例 → 调用doSomething()时触发事务拦截器。

总结

三级缓存的核心价值在于将 “代理生成” 和 “实例化” 解耦,通过延迟执行代理逻辑,确保在循环依赖场景下,早期暴露的 bean 依然是经过 AOP 增强的代理对象,而非原始实例。这是 Spring 解决循环依赖与 AOP 冲突的关键设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值