spring如何解决循环依赖问题?

文章详细阐述了Spring框架如何处理循环依赖问题,主要涉及三级缓存的作用。在bean创建过程中,一级缓存存放完整bean,二级缓存存放半成品bean,三级缓存存放工厂对象(lambda表达式)。在bean实例化、属性赋值和初始化完成后,分别在不同阶段利用和更新这三级缓存,确保正确处理循环依赖。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是循环依赖?

循环依赖就是两个或者多个bean之间相互依赖,形成依赖闭环. 例子: A对象包含属性B,B对象包含属性A,如下图:

spring在处理循环依赖的问题上采用了三级缓存 

三级缓存在bean创建的那个时机会用到?

1.bean实例化之前会查询缓存,判断bean是否已经存在

2.bean属性赋值之前会先向三级缓存中放入一个lambda表达式,该表达式执行则会生成一个半成品Bean放入二级缓存

3.Bean初始化完成后将完整的Bean放入一级缓存,同时清空二、三级缓存

bean创建的过程:

以上三个时机的源码分析 

1. bean实例化之前 

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //从singletonObjects(一级缓存)中获取,获取不到继续往下走
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            //从earlySingletonObjects(二级缓存)中获取,获取不到继续往下走
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //为了线程安全加锁
                synchronized(this.singletonObjects) {
                       //从一级缓存中获取,获取不到往下走
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        //从二级缓存中获取,获取不到往下走
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            //从三级缓存中获取lambada表达式
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                //根据lambada表达式获取bean
                                singletonObject = singletonFactory.getObject();
                                //将获取到的bean放入二级缓存中
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                //从三级缓存中移除
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

整体逻辑总结为:

  1. 从一级缓存获取,获取到了,则返回
  2. 从二级缓存获取,获取到了,则返回
  3. 从三级缓存获取,获取到了,则执行三级缓存中的lambda表达式,将结果放入二级缓存,清除三级缓存

 2. bean属性赋值之前

AbstractAutowireCapableBeanFactory#doCreateBean

 

 addSingletonFactory

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized(this.singletonObjects) {
            //如果一级缓存中不存在的话,往下走
            if (!this.singletonObjects.containsKey(beanName)) {
                //放入三级缓存
                this.singletonFactories.put(beanName, singletonFactory);
                //删除二级缓存中对应的bean
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }

        }
    }

 AbstractAutowireCapableBeanFactory#getEarlyBeanReference

该方法逻辑:判断该Bean是否需要被动态代理

  • 不需要代理,返回未属性注入、未初始化的半成品Bean
  • 需要代理,返回未属性注入、未初始化的半成品Bean的代理对象

 

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
        Iterator var5 = this.getBeanPostProcessors().iterator();
        // 遍历后置处理器
        while(var5.hasNext()) {
            BeanPostProcessor bp = (BeanPostProcessor)var5.next();
            // 找到实现SmartInstantiationAwareBeanPostProcessor接口的
            // 该接口getEarlyBeanReference方法什么时候会执行?
            // AOP动态代理的时候 该方法执行就是判断该Bean是否需要被代理
            // 需要代理则会创建代理对象返回
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    // 这个Object有两种情况,一是实例化后的半成品Bean,二是半成品Bean动态代理后的代理对象
    return exposedObject;
}

注意:这里只是把lambda表达式放入了三级缓存,如果不从三级缓存中获取,这个表达式是不执行的,一旦执行了,就会把半成品Bean或者半成品Bean的代理对象放入二级缓存中了

3. Bean初始化完成后

AbstractBeanFactor#doGetBean 

这里注意啊,这个getSingleton方法传参传了个lambda表达式,这个表达式内部就是Bean的实例化过程,初始化完成后,是要需要执行这个getSingleton方法的 

 

DefaultSingletonBeanRegistry.getSingleton(beanName, singletonFactory) 

这个方法与上面那个不一样,重载了 

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     
        synchronized(this.singletonObjects) {
            // 第一次进来这里获取肯定为null
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               // 省略................
                try {
                    // 注意啊,这个就是执行外面那个传参的lambda表达式
                    // 所以这里才会跳到createBean方法那里去执行
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                } 
                // 省略................
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    this.afterSingletonCreation(beanName);
                }
                // 到了这说明Bean创建完了
                if (newSingleton) {
                    // 这里就会把Bean放入一级缓存中了 同时清除二、三级缓存
                    this.addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

 DefaultSingletonBeanRegistry#addSingleton

protected void addSingleton(String beanName, Object singletonObject) {
  synchronized(this.singletonObjects) {
     // 放入一级缓存  
      this.singletonObjects.put(beanName, singletonObject);
      // 清除二、三级缓存
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
  }
}

 总结:

整个过程就三个地方跟缓存有关,我们假设现在要实例化A这个Bean,看看缓存是怎么变化的:

实例化前,获取缓存判断(三个缓存中肯定没有A,获取为null,进入实例化流程)
实例化完成,属性注入前(往三级缓存中放入了一个lambda表达式,一、二级为null)
初始化完成(将A这个Bean放入一级缓存,清除二、三级缓存)

 

一级缓存,存放完整bean:

Map<String, Object> singletonObjects

二级缓存,存放半成品bean:

Map<String, Object> earlySingletonObjects

三级缓存,存放工厂对象(lambda表达式):

Map<String, ObjectFactory<?>> singletonFactories

<think>嗯,用户现在想了解Spring框架中循环依赖解决方案。之前我已经回答过Spring Bean的生命周期,并且用户提到了相关的站内引用,不过他们现在更关注循环依赖的处理。首先,我需要回忆Spring是如何处理循环依赖的,尤其是三级缓存机制。 用户提供的引用里提到,Spring通过三级缓存解决循环依赖,特别是singleton作用域的bean。三级缓存包括singletonObjects、earlySingletonObjects和singletonFactories。可能需要在回答中详细解释这三个缓存的作用,以及它们如何协作。比如,当创建Bean A,它会被放入三级缓存中,如果它依赖Bean B,而Bean B又依赖Bean A,这三级缓存允许提前暴露半成品的Bean A,供Bean B完成初始化。 另外,用户提到的Spring Boot 2.6.0之后对循环依赖的处理更严格,可能需要说明版本变化带来的影响,比如默认禁止循环依赖,开发者需要显式配置才能允许。这候应该建议用户优先考虑重构代码,而不是依赖技术手段解决,因为循环依赖可能暗示设计问题。 还要注意用户之前的问题中提到了Bean的生命周期,所以在解释循环依赖解决方案,可能需要结合Bean的实例化和属性注入阶段,说明在哪个阶段三级缓存被使用。比如,在实例化后、属性注入前,将对象工厂放入三级缓存,这样当发生循环依赖,可以提前获取到未完全初始化的bean实例。 用户可能还会关心不同作用域的bean是否支持循环依赖,比如原型作用域的bean,Spring无法解决,这候需要说明原因,并建议避免这种情况。此外,@Lazy注解的使用场景和限制也需要提到,它如何延迟依赖的解析,从而打破循环,但可能带来的潜在问题。 需要确保回答结构清晰,分点说明解决方案、注意事项和常见方法,同结合代码示例,如使用@Lazy或setter注入的例子。最后,相关问题部分可以引导用户深入理解三级缓存、设计模式的应用以及版本差异的影响。</think>以下是Spring框架解决循环依赖的核心方案和关键细节分析: --- ### Spring循环依赖解决方案 #### 核心机制:三级缓存 Spring通过三级缓存解决**singleton作用域**的循环依赖问题: $$ \begin{aligned} 1. &\text{一级缓存(完全初始化): singletonObjects} \\ 2. &\text{二级缓存(半成品对象): earlySingletonObjects} \\ 3. &\text{三级缓存(对象工厂): singletonFactories} \end{aligned} $$ #### 解决流程(以A↔B循环依赖为例) ```mermaid flowchart TD A[创建Bean A] --> B[实例化A对象] B --> C[将A对象工厂放入三级缓存] C --> D[注入B属性] D --> E{检查B是否在缓存中} E --> |未找到| F[开始创建Bean B] F --> G[实例化B对象] G --> H[将B对象工厂放入三级缓存] H --> I[注入A属性] I --> J{从三级缓存获取A对象工厂} J --> K[通过工厂获取A的早期引用] K --> L[将A升级到二级缓存] L --> M[完成B的初始化] M --> N[将B放入一级缓存] N --> O[返回B到A的注入点] O --> P[完成A的初始化] P --> Q[将A放入一级缓存] ``` #### 关键特性 1. **仅支持单例模式** 原型(prototype)作用域的Bean无法解决循环依赖[^1] 2. **构造器注入限制** 若循环依赖通过构造器注入发生,Spring会直接抛出`BeanCurrentlyInCreationException`[^2] 3. **自动代理冲突规避** 通过`SmartInstantiationAwareBeanPostProcessor`接口处理AOP代理对象的循环依赖 --- ### 常见解决方案对比 | 方法 | 实现方式 | 适用场景 | |---------------------|----------------------------------|----------------------------| | @Lazy延迟加载 | 在依赖项添加@Lazy注解 | 简单循环且无需立即初始化的场景 | | Setter/Field注入 | 替换构造器注入为属性注入 | 大多数单例Bean循环依赖场景 | | 接口分离重构 | 提取公共接口解耦依赖关系 | 设计优化型解决方案 | | 上下文分离 | 使用@DependsOn明确初始化顺序 | 复杂依赖链场景 | --- ### Spring Boot 2.6+新特性 1. **严格模式默认开启** 启动检测到循环依赖会直接报错,需通过配置开启兼容模式: ```properties spring.main.allow-circular-references=true ``` 2. **设计优先原则** 官方建议重构代码而不是依赖技术方案解决循环依赖 --- #### 代码示例 ```java // 使用@Lazy解决构造器循环依赖 @Service public class ServiceA { private final ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { this.serviceB = serviceB; } } // Setter注入方案 @Service public class ServiceB { private ServiceA serviceA; @Autowired public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; } } ``` --- ### 相关问题 1. **三级缓存如何避免并发问题?** 2. **为什么原型作用域的Bean不能解决循环依赖?** 3. **Spring如何检测循环依赖的存在?** 4. **循环依赖对事务管理有什么影响?** [^1]: Spring通过提前暴露对象引用的方式解决单例Bean的循环依赖问题 [^2]: Spring Boot 2.6+版本对循环依赖采取更严格的设计规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值