Spring解决循环依赖的原理源码解析

 更新时间:2025年09月17日 10:39:35   作者:柯南二号  
循环依赖指的是两个或多个Bean互相依赖,导致初始化时出现死循环,本文给大家介绍Spring解决循环依赖的原理 + 源码解读,感兴趣的朋友跟随小编一起看看吧

Spring 如何解决循环依赖:原理 + 源码解读

  1. 开篇引导:什么是循环依赖
  2. Spring 解决循环依赖的思路(三层缓存)
  3. 源码走读(关键方法:getSingletondoCreateBeanaddSingletonFactory
  4. 限制与不能解决的场景
  5. 实践建议

Spring 如何解决循环依赖(原理 + 源码解读)

一、循环依赖是什么

循环依赖指的是 两个或多个 Bean 互相依赖,导致初始化时出现死循环
比如:

@Component
public class A {
    @Autowired
    private B b;
}
@Component
public class B {
    @Autowired
    private A a;
}
  • 构造器注入:直接死锁,无法解决。
  • Setter/Field 注入:Spring 可以通过“提前暴露半成品对象”来打破循环。

二、Spring 的解决思路

Spring 采用 三层缓存机制 + 提前暴露引用 来解决单例 Bean 的循环依赖。

三层缓存:

  1. singletonObjects:一级缓存,存放完全初始化好的单例 Bean。
  2. earlySingletonObjects:二级缓存,存放早期的半成品 Bean。
  3. singletonFactories:三级缓存,存放一个 ObjectFactory,用于生成早期引用(可能是代理对象)。

流程简化:

  1. Bean 创建时,先放入三级缓存(singletonFactories)。
  2. 如果别的 Bean 依赖它,就能通过三级缓存拿到早期引用。
  3. 依赖注入完成后,Bean 初始化成功,放入一级缓存,并清理二、三级缓存。

三、源码走读(三层缓存机制)

1.getSingleton

DefaultSingletonBeanRegistry 中:

public Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先从一级缓存中取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. 一级没有,且 Bean 正在创建中,则尝试二级缓存
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 3. 二级没有,就从三级缓存拿 ObjectFactory 生成早期引用
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                singletonObject = singletonFactory.getObject();
                // 提前曝光的对象放到二级缓存
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

👉 这里是 循环依赖能被打破的关键点

  • A 需要 B,发现 B 还在创建中,去二级/三级缓存里取。
  • 如果三级缓存有工厂,就生成一个“早期引用”,保证注入能继续。

2.doCreateBean

AbstractAutowireCapableBeanFactory 中,Bean 的创建核心流程:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 实例化 Bean(构造方法)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();
    // 2. 是否需要提前暴露?
    boolean earlySingletonExposure = (mbd.isSingleton() && allowCircularReferences
        && isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 加入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // 3. 填充属性(setter/field 注入)
    populateBean(beanName, mbd, instanceWrapper);
    // 4. 初始化(各种回调、AOP 代理)
    bean = initializeBean(beanName, bean, mbd);
    // 5. 放入一级缓存,清理二、三级缓存
    registerSingleton(beanName, bean);
    return bean;
}

👉 关键点:在属性注入之前,就把早期引用工厂放入三级缓存
这样如果别的 Bean 需要它,可以从三级缓存里拿到“半成品”。

3.addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    if (!this.singletonObjects.containsKey(beanName)) {
        this.singletonFactories.put(beanName, singletonFactory);
        this.earlySingletonObjects.remove(beanName);
    }
}

👉 确保只有在 Bean 没有完全初始化前,才会暴露到三级缓存

四、不能解决的场景

  • 构造器注入的循环依赖
    • 因为实例化必须先完成构造器,Spring 没法提前暴露半成品。
  • 原型作用域(prototype)的循环依赖
    • 每次创建都是新对象,不能共享早期引用。
  • 部分 AOP/异步场景
    • 代理对象和早期引用可能不一致,导致注入问题。
  • Spring Boot 2.6+ 默认禁止循环依赖
    • 需显式开启:
spring.main.allow-circular-references=true

五、常见解决办法

  • 重构依赖关系(最佳实践)
  • 拆出第三个 Service,消除循环。
  • 使用 @Lazy 延迟加载
public A(@Lazy B b) { this.b = b; }

使用 ObjectProviderProvider

@Autowired
private ObjectProvider<B> bProvider;

ApplicationContext#getBean 延迟获取(不推荐,耦合度高)。

六、总结

  • Spring 通过 三级缓存(singletonObjects / earlySingletonObjects / singletonFactories)getEarlyBeanReference,解决了大部分 单例 + Setter/Field 注入 的循环依赖。
  • 构造器循环依赖、prototype Bean 无法解决。
  • Spring Boot 2.6+ 默认关闭循环依赖,需要显式开启。
  • 从设计角度,循环依赖往往是架构问题,最佳做法是重构消除循环

到此这篇关于Spring解决循环依赖的原理源码解析的文章就介绍到这了,更多相关Spring循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决springboot URL带有斜杠的转义字符百分之2F导致的400错误

    解决springboot URL带有斜杠的转义字符百分之2F导致的400错误

    这篇文章主要介绍了解决springboot URL带有斜杠的转义字符百分之2F导致的400错误问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 教你Springboot如何实现图片上传

    教你Springboot如何实现图片上传

    这篇文章主要介绍了教你Springboot如何实现图片上传,首先大家明白图片上传,需要在数据库定义一个varchar类型的img字段图片字段,本文结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • 详谈Map的key、value值的数据类型不能为基本类型的原因

    详谈Map的key、value值的数据类型不能为基本类型的原因

    这篇文章主要介绍了详谈Map的key、value值的数据类型不能为基本类型的原因,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Hibernate单表操作实例解析

    Hibernate单表操作实例解析

    这篇文章主要介绍了Hibernate单表操作实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • java生成excel报表文件示例

    java生成excel报表文件示例

    本篇文章主要介绍了java生成excel报表文件示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • 关于Java8新特性Optional类的详细解读

    关于Java8新特性Optional类的详细解读

    Optional类是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在,原来用 null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常,需要的朋友可以参考下
    2023-05-05
  • Java二进制运算符超详细讲解及扩展知识

    Java二进制运算符超详细讲解及扩展知识

    Java源码中涉及到大量的二进制操作,非常的复杂,但非常的快速,下面这篇文章主要介绍了Java二进制运算符超详细讲解及扩展知识的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-09-09
  • Spring通过配置文件管理Bean对象的方法

    Spring通过配置文件管理Bean对象的方法

    这篇文章主要介绍了Spring通过配置文件管理Bean对象的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • mybatis-plus主键id生成、字段自动填充的实现代码

    mybatis-plus主键id生成、字段自动填充的实现代码

    这篇文章主要介绍了mybatis-plus主键id生成、字段自动填充的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • 移除元素Java实现方式

    移除元素Java实现方式

    这篇文章主要介绍了移除元素Java实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04

最新评论