Spring如何巧妙解决循环依赖问题

什么是循环依赖?

循环依赖是指两个或多个Bean之间相互依赖,形成闭环的情况。例如:AService依赖BService,而BService又依赖AService。这种场景下,传统的创建顺序无法满足依赖注入的要求。

Spring的三级缓存机制

Spring通过三级缓存机制优雅地解决了循环依赖问题:

1. 第一级缓存:singletonObjects

  • 存储完全初始化完成的单例Bean

  • 这里的Bean是经过完整生命周期处理的(包括AOP代理)

2. 第二级缓存:earlySingletonObjects

  • 存储提前暴露的Bean引用(早期引用)

  • 这些Bean尚未完成完整初始化,但可以用于解决循环依赖

3. 第三级缓存:singletonFactories

  • 存储Bean的ObjectFactory(工厂对象)

  • 通过工厂可以获取Bean的早期引用,必要时创建代理对象

解决循环依赖的详细流程

场景:AService ←→ BService 相互依赖

java

// AService 依赖 BService
@Service
public class AService {
    @Autowired
    private BService bService;
}

// BService 依赖 AService
@Service
public class BService {
    @Autowired
    private AService aService;
}

解决步骤:

  1. 开始创建AService

    • creatingSet.add('AService') - 标记AService正在创建

    • 实例化AService普通对象

  2. AService需要注入BService

    • 从单例池获取BService,不存在则开始创建BService

  3. 创建BService

    • creatingSet.add('BService') - 标记BService正在创建

    • 实例化BService普通对象

    • BService需要注入AService

  4. BService获取AService

    • 从单例池获取AService,不存在

    • 检查creatingSet发现AService正在创建(循环依赖 detected!)

    • 从三级缓存获取AService的ObjectFactory

    • 执行工厂方法:getEarlyBeanReference() → 必要时创建AService代理对象

    • 将结果存入二级缓存earlySingletonObjects,并从三级缓存移除工厂

  5. BService继续初始化

    • 注入AService的早期引用(可能是代理对象)

    • 完成其他属性填充

    • 执行AOP(如果需要)

    • 将完整BService存入单例池

  6. AService继续初始化

    • 注入已创建的BService

    • 完成其他属性填充

    • 执行AOP(但发现已提前AOP,直接使用早期引用)

    • 将完整AService存入单例池

关键源码分析

1. 添加单例工厂

java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三级缓存
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

doCreateBean()方法中,Spring会提前暴露Bean的工厂:

java

// 为了解决循环依赖提前缓存单例创建工厂
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2. 获取单例Bean的逻辑

java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存查找
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. 从二级缓存查找
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // 3. 从三级缓存查找并创建早期引用
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 移动到二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

3. 提前AOP处理

java

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    // 如果需要AOP,则创建代理对象
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        return proxy;
    }
    return bean;
}

为什么需要三级缓存?

  1. 一级缓存:存储完整的单例Bean,保证单例性质

  2. 二级缓存:存储早期引用,避免多次执行AOP产生多个代理对象

  3. 三级缓存:存储工厂对象,延迟决策是否需要进行AOP

如果只有二级缓存,每次获取早期引用都需要执行AOP,可能导致创建多个不同的代理对象,违反单例原则。

使用注意事项

<think> 我们正在讨论Spring解决循环依赖问题。根据引用内容,Spring主要通过三级缓存机制解决单例模式下setter注入的循环依赖问题。同时,构造器注入和多例模式下的循环依赖无法解决。此外,使用@Lazy注解也可以帮助解决循环依赖。 我们将按照以下步骤回答: 1. 解释循环依赖的三种情况(构造器注入、setter多例、setter单例)及Spring解决的情况(仅setter单例)。 2. 重点介绍Spring解决单例setter循环依赖的三级缓存机制。 3. 介绍使用@Lazy注解解决循环依赖的方法。 4. 总结并给出建议。 注意:引用内容已经给出,我们将在回答中适当引用。 </think>下面详细介绍 Spring解决循环依赖问题的方法,重点分析其核心机制——**三级缓存**: --- ### 一、Spring 循环依赖解决范围 根据引用内容[^4],Spring 仅能解决特定类型的循环依赖: 1. ✅ **可解决**:Setter 注入 + 单例模式(最常见情况) 2. ❌ **不可解决**: - 构造器注入("先有鸡还是先有蛋"问题) - Setter 注入 + 多例模式(导致无限创建 Bean 引发 OOM) --- ### 二、三级缓存机制(核心解决方案) Spring 通过三级缓存解决单例 Setter 循环依赖[^5],缓存结构如下: | 缓存级别 | 名称 | 存储内容 | 数学表示 | |----------|-----------------------|---------------------------------------------|------------------------| | 一级缓存 | `singletonObjects` | 完整的 Bean(已完成初始化) | $$ S_{\text{complete}} $$ | | 二级缓存 | `earlySingletonObjects` | 早期对象(已实例化但未完成属性注入) | $$ S_{\text{early}} $$ | | 三级缓存 | `singletonFactories` | 对象工厂(用于生成早期对象的 Lambda 表达式) | $$ \lambda_{\text{factory}} $$ | #### 解决流程(以 A→B→A 循环依赖为例): ```mermaid graph TD A[创建A实例] --> B[将A工厂放入三级缓存] B --> C[注入A的依赖B] C --> D[创建B实例] D --> E[将B工厂放入三级缓存] E --> F[注入B的依赖A] F --> G{检查缓存} G -->|三级缓存存在A工厂| H[通过工厂获取A早期对象] H --> I[将A早期对象放入二级缓存] I --> J[完成B的属性注入] J --> K[将B放入一级缓存] K --> L[完成A的属性注入] L --> M[将A放入一级缓存] ``` #### 关键步骤: 1. **提前暴露引用**: 实例化 A 后立即将其工厂(`() -> getEarlyBeanReference()`)放入三级缓存 $$ \lambda_{\text{factory}} $$[^5] 2. **依赖解析**: 当 B 需要注入 A 时,通过三级缓存工厂获取 A 的**早期引用**(未完成初始化的对象) 3. **缓存升级**: 从三级缓存获取对象后,将其升级到二级缓存 $$ S_{\text{early}} $$[^5] 4. **完成初始化**: 所有依赖满足后,最终将完整 Bean 放入一级缓存 $$ S_{\text{complete}} $$ --- ### 三、其他辅助解决方案 #### 1. `@Lazy` 延迟注入[^3] ```java @Component public class ServiceA { @Lazy // 延迟初始化 @Autowired private ServiceB serviceB; } ``` - **原理**:创建代理对象替代真实依赖,打破初始化死锁 - **适用场景**:构造器循环依赖的替代方案 #### 2. 代码重构(根本方案) - 提取公共逻辑到第三方类 - 使用事件监听(`ApplicationEvent`)解耦 - 避免双向依赖(改为单向依赖) --- ### 四、不同场景下的行为对比 | 依赖类型 | 作用域 | 能否解决 | 异常类型 | |----------------|----------|----------|------------------------| | 构造器注入 | 单例/多例 | ❌ | `BeanCurrentlyInCreationException` | | Setter/Field注入 | 单例 | ✅ | - | | Setter/Field注入 | 多例 | ❌ | `BeanCreationException` | --- ### 五、最佳实践建议 1. **优先使用 Setter/Field 注入**:避免构造器注入导致的不可解循环依赖[^4] 2. **保持 Bean 无状态**:减少组件间强耦合 3. **谨慎使用多例作用域**:多例 Bean 的循环依赖无法解决 4. **监控三级缓存**:通过 `AbstractBeanFactory` 的缓存变量调试 > 示例:通过断点查看三级缓存内容 > ```java > // 调试代码(需在Spring源码环境中) > DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) context.getAutowireCapableBeanFactory(); > System.out.println("三级缓存: " + registry.getSingletonFactories()); > System.out.println("二级缓存: " + registry.getEarlySingletonObjects()); > ``` Spring 的三级缓存设计巧妙解决了 $$ O(n^2) $$ 级别的依赖复杂度,其核心思想是 **空间换时间** 和 **提前暴露引用**,该方案的时间复杂度为 $$ O(1) $$ 缓存查询操作[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值