找回密码
 立即注册
首页 业界区 业界 详细解析Spring如何解决循环依赖问题

详细解析Spring如何解决循环依赖问题

旌磅箱 3 小时前
在日常的Spring开发中,循环依赖是一个高频出现的问题,也是面试中的核心考点。本文将从概念定义、问题表现、核心原理到源码层面,全方位解析Spring是如何通过三级缓存机制优雅地解决单例Bean的循环依赖问题。
一、什么是循环依赖?

循环依赖,指的是两个或多个Bean之间互相持有对方的引用,形成闭环依赖关系。最典型的场景是Bean A依赖Bean B,同时Bean B又依赖Bean A。
代码示例:
  1. @Component
  2. class A {
  3.     // A依赖B
  4.     @Resource
  5.     private B b;
  6. }
  7. @Component
  8. class B {
  9.     // B依赖A,形成循环
  10.     @Resource
  11.     private A a;
  12. }
复制代码
在默认情况下,如果Spring不做特殊处理,项目启动时会抛出BeanCurrentlyInCreationException异常,提示存在循环依赖无法解决:
1.png

二、Spring解决循环依赖的核心:三级缓存

为了解决单例Bean的循环依赖问题,Spring设计了三级缓存机制,通过提前暴露半成品Bean的方式打破依赖闭环。
三级缓存的定义

缓存级别缓存名称作用一级缓存singletonObjects存放完全初始化完成的单例Bean(成品对象),供业务直接使用二级缓存earlySingletonObjects存放提前暴露的半成品Bean(已实例化但未完成属性填充和初始化)三级缓存singletonFactories存放ObjectFactory(对象工厂),这是一个函数式接口,仅在调用getObject()时才会创建Bean实例三、核心源码解析(基于 Spring 5.3.x)

Spring处理Bean创建和循环依赖的核心逻辑集中在DefaultSingletonBeanRegistry类中,以下是关键源码及解析:
  1. public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
  2.     // 一级缓存:存放完全初始化好的单例Bean (成品)
  3.     private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  4.     // 三级缓存:存放Bean的工厂对象,用于创建提前暴露的Bean (半成品工厂)
  5.     private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  6.     // 二级缓存:存放提前暴露的Bean实例 (半成品,未完成属性填充和初始化)
  7.     private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  8.     // 记录当前正在创建的Bean名称,解决循环依赖的关键判断
  9.     private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
  10.     /**
  11.      * 核心方法:获取单例Bean(解决循环依赖的入口)
  12.      * @param beanName Bean名称
  13.      * @param allowEarlyReference 是否允许提前引用半成品Bean
  14.      * @return 单例Bean实例
  15.      */
  16.     @Nullable
  17.     public Object getSingleton(String beanName, boolean allowEarlyReference) {
  18.         // 第一步:优先从一级缓存获取成品Bean
  19.         Object singletonObject = this.singletonObjects.get(beanName);
  20.         
  21.         // 如果一级缓存没有,且当前Bean正在创建中(循环依赖的核心判断条件)
  22.         if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  23.             // 第二步:从二级缓存获取提前暴露的半成品Bean
  24.             singletonObject = this.earlySingletonObjects.get(beanName);
  25.             
  26.             // 如果二级缓存也没有,且允许提前引用
  27.             if (singletonObject == null && allowEarlyReference) {
  28.                 // 加锁保证并发安全
  29.                 synchronized (this.singletonObjects) {
  30.                     // 双重检查(防止多线程下重复创建)
  31.                     singletonObject = this.singletonObjects.get(beanName);
  32.                     if (singletonObject == null) {
  33.                         singletonObject = this.earlySingletonObjects.get(beanName);
  34.                         if (singletonObject == null) {
  35.                             // 第三步:从三级缓存获取ObjectFactory
  36.                             ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  37.                             if (singletonFactory != null) {
  38.                                 // 通过工厂创建半成品Bean(提前暴露的核心操作)
  39.                                 singletonObject = singletonFactory.getObject();
  40.                                 // 将半成品Bean放入二级缓存,同时移除三级缓存(避免重复创建)
  41.                                 this.earlySingletonObjects.put(beanName, singletonObject);
  42.                                 this.singletonFactories.remove(beanName);
  43.                             }
  44.                         }
  45.                     }
  46.                 }
  47.             }
  48.         }
  49.         return singletonObject;
  50.     }
  51.     /**
  52.      * 将Bean工厂放入三级缓存(提前暴露Bean的关键步骤)
  53.      * 在Bean实例化后、属性填充前调用
  54.      */
  55.     protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  56.         Assert.notNull(singletonFactory, "Singleton factory must not be null");
  57.         synchronized (this.singletonObjects) {
  58.             if (!this.singletonObjects.containsKey(beanName)) {
  59.                 this.singletonFactories.put(beanName, singletonFactory);
  60.                 this.earlySingletonObjects.remove(beanName);
  61.                 this.registeredSingletons.add(beanName);
  62.             }
  63.         }
  64.     }
  65.     /**
  66.      * 将完全初始化的Bean放入一级缓存,清理二、三级缓存
  67.      * 在Bean初始化完成后调用
  68.      */
  69.     protected void addSingleton(String beanName, Object singletonObject) {
  70.         synchronized (this.singletonObjects) {
  71.             this.singletonObjects.put(beanName, singletonObject);
  72.             this.singletonFactories.remove(beanName);
  73.             this.earlySingletonObjects.remove(beanName);
  74.             this.registeredSingletons.add(beanName);
  75.             this.singletonsCurrentlyInCreation.remove(beanName);
  76.         }
  77.     }
  78. }
复制代码
调试关键方法(建议收藏)

在实际调试Spring源码时,建议重点关注以下核心方法的调用链路:

  • getBean() - Bean获取的入口方法
  • doGetBean() - 获取Bean的核心实现
  • createBean() - 创建Bean的顶层方法
  • doCreateBean() - 创建Bean的核心逻辑
  • createBeanInstance() - Bean实例化(创建空对象)
  • populateBean() - Bean属性填充(依赖注入的核心)
四、循环依赖解决完整流程(以A和B为例)

结合上文A依赖B、B依赖A的场景,我们拆解Spring解决循环依赖的完整执行流程(基于单例Bean + 字段注入):
步骤 1:Spring启动,开始创建Bean A


  • 调用getBean(A),首先将A标记为「正在创建中」(singletonsCurrentlyInCreation.add("A"));
  • 通过反射创建A的空实例(ctro.newInstance()),此时A的属性b为null(实例化阶段);
  • 关键操作:调用addSingletonFactory()将A的ObjectFactory放入三级缓存
  • 开始为A填充属性,发现依赖B,触发getBean(B)。
步骤 2:创建Bean B(触发循环依赖)


  • 调用getBean(B),将B标记为「正在创建中」;
  • 通过反射创建B的空实例(ctro.newInstance()),此时B的属性a为null;
  • 调用addSingletonFactory()将B的ObjectFactory放入三级缓存;
  • 开始为B填充属性,发现依赖A,再次触发getBean(A)。
步骤 3:解决循环依赖(从缓存获取A)


  • 执行getBean(A),检查一级缓存:A未完成初始化,无成品;
  • 检查标记:A处于「正在创建中」,符合循环依赖条件;
  • 检查二级缓存:无A的半成品实例;
  • 检查三级缓存:存在A的ObjectFactory,调用getObject()创建A的半成品实例;
  • 将A的半成品实例从三级缓存移至二级缓存
  • 将半成品A返回给B,完成B的属性a填充。
步骤 4:B完成初始化,反馈给A


  • B完成属性填充,执行初始化方法(init-method/@PostConstruct);
  • 调用addSingleton(B),将B放入一级缓存,并清理其二、三级缓存;
  • 将成品B返回给A,完成A的属性b填充。
步骤 5:A完成初始化,最终入池


  • A完成属性填充,执行初始化方法;
  • 调用addSingleton(A),将A放入一级缓存,清理其二、三级缓存;
  • 移除A的「正在创建中」标记,循环依赖问题解决。
补充说明:
加入三级缓存后的Bean创建流程可参考下图:
2.png

五、关键细节:为什么需要三级缓存?

核心原因是为了支持AOP动态代理

  • 延迟创建代理对象:ObjectFactory的getObject()方法中会调用getEarlyBeanReference(),该方法会判断当前Bean是否需要生成AOP代理。只有发生循环依赖时,才会提前创建代理对象;
  • 保证代理对象的唯一性:如果没有三级缓存,所有Bean都需要提前创建代理,破坏了Spring「初始化完成后再创建代理」的设计原则;
  • 避免重复代理:三级缓存的工厂模式确保代理对象只会被创建一次,放入二级缓存后就移除三级缓存,避免重复生成。
如果仅使用二级缓存,所有Bean都必须在实例化阶段就创建代理,这会导致:

  • 代理对象创建时机提前,不符合Spring的初始化生命周期
  • 无循环依赖的Bean也会被提前代理,增加不必要的性能开销
总结


  • Spring通过三级缓存机制解决单例Bean的循环依赖问题,核心是提前暴露半成品Bean打破依赖闭环;
  • 三级缓存各司其职:一级缓存存成品、二级缓存存半成品、三级缓存存工厂(支持AOP延迟代理);
  • 解决循环依赖的核心流程是:实例化Bean → 放入三级缓存 → 填充属性触发循环 → 从缓存获取半成品 → 完成初始化放入一级缓存。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册