信息发布→ 登录 注册 退出

Java Spring 循环依赖解析

发布时间:2026-01-11

点击量:
目录
  • 1、常见问题
  • 2、什么是循环依赖?
  • 3、循环依赖说明
  • 4、BeanCurrentlyInCreationException
  • 5、依赖注入的两种方式
    • 方式一:构造器方式注入依赖
  • 方式二:以 set 方式注入依赖
    • 6、Spring 三级缓存介绍和循环依赖解决过程
      • 三级缓存介绍
      • 实例化/初始化定义
      • 三级缓存使用过程
      • A/B 两对象在三级缓冲的迁移说明
      • ObjectFactory 接口
      • DEBUG 断点调试
      • 循环依赖解决
    • 7、Spring 循环依赖总结

      1、常见问题

      • 你解释一下 spring 中的三级缓存?
      • 三级缓存分别是什么?三个 Map 有什么异同?
      • 什么是循环依赖?请谈谈?你看过 spring 的源码吗?一般我们说的是 spring  容器是什么?
      • 多例的情况下,循环依赖问题为什么无法解决?

      2、什么是循环依赖?

      多个 bean 之间相互依赖,形成闭环。比如:A 依赖于 B, B 依赖于 C , C 依赖于 A

      示例代码:

      public class T1 {
      
        class A {
          B b;
        }
        class B {
          C c;
        }
        class C {
          A a; 
        }
      }

      比如:A 依赖于 B, B 依赖于 C , C 依赖于 A
      通常来说,如果问 Spring 容器内部如何解决循环依赖,一定是指默认的单例 Bean 中, 属性相互引用的场景。

      @Component
      public class A {
          @Autowired
          private B b;
      }
      
      @Component
      public class B {
          @Autowired
          private C c;
      }
       
      @Component
      public class C {
          @Autowired
          private A a;
      }

      3、循环依赖说明

      官方说明:

      参考官方说

      构造方法注入:不支持循环依赖。
      我们 AB 循环依赖问题。只要 A 的方式是 setter 且 singleton
      就不会有循环依赖问题。

      4、BeanCurrentlyInCreationException

      循环依赖异常的定义如下所示,如果出现循环依赖,我们在启动/运行过程中会报这个错误。

      5、依赖注入的两种方式

      方式一:构造器方式注入依赖

      @Component
      public class ServiceA{
      
          private ServiceB serviceB;
              
          public ServiceA(ServiceB serverB) {
             this.serivceB = serviceB;
          }     
      }
      
      @Component
      public class ServiceB{
      
          private ServiceA serviceA;
              
          public ServiceB(ServiceA serviceA) {
             this.serviceA = serviceA;
          }     
      }

      构造器循环依赖是无法解决的,你想让构造器注入支持循环依赖,是不可能的。

      方式二:以 set 方式注入依赖

      @Component
      public class ServiceA{
      
          private ServiceB serviceB;
              
          public setServiceB(ServiceB serverB) {
             this.serivceB = serviceB;
          }     
      }
      
      @Component
      public class ServiceB{
      
          private ServiceA serviceA;
              
          public setServiceB(ServiceA serviceA) {
             this.serviceA = serviceA;
          }     
      }

      案例演示(基于 Spring 容器的循环依赖)

      普通的 Java 基础编码A  类、B 类

      @Data
      public class A{
        private B b;
        
        public A() {
           System.out.println("----- A create success");
        }
      }
      
      @Data
      public class B{
        private a a;
      
        public B() {
           System.out.println("------ B create success");
        }    
      }

      循环依赖解决

      A a = new A();
      B b = new B();
      
      a.setB(b);
      b.setA(a);

      基于 Spring 容器的循环依赖

      • 默认的单例(singleton)的场景是支持循环依赖的,不报错
      • 原型(prototype)的场景是不支持循环依赖的, 会报错

      代码演示:

      循环依赖代码:

      @Component
      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
      public class A {
      
          @Autowired
          private B b;
      }
      
      @Component
      public class B {
      
          @Autowired
          private A a;
      }

      默认单例,修改为原型

      // 增加注解
      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

      一段测试程序

      class BTest {
      
      
          @Configuration
          @Import({A.class, B.class})
          public static class TestConfig {
      
          }
      
          @Test
          public void currentlyincreation() {
              AnnotationConfigApplicationContext applicationContext =
                      new AnnotationConfigApplicationContext(TestConfig.class);
              // 这里要获取 bean 一下,如果不去主动获取,可能是由于惰性加载没有执行,不会报错
              A a = applicationContext.getBean(A.class);
              System.out.println(a);
          }
      }

      循环依赖异常

      6、Spring 三级缓存介绍和循环依赖解决过程

      三级缓存介绍

      核心类: DefaultSingletonBeanRegistry

      第一级缓存:(也叫单例池)singletonObjects: 存放已经经历完整生命周期的 Bean 对象
      第二级缓存:earlySingletonObjects: 存放早起暴露出来的 Bean 对象, Bean 的生命周期未结束(属性还未填充完成)
      第三级缓存:Map<String, ObjectFactory<?>> singletonFactories可以存放 Bean 工厂。
      只有单例 Bean 会通过三级缓存提前暴露出来解决循环依赖问题,而非单例的 bean, 每次从容器获取都是新的对象,都会重新创建,所以非单例的 bean 是没有缓存的,不会放到三级缓存中。

      实例化/初始化定义

      实例化/初始化

      1、实例化:内存中申请一块内存空间;(比如:租赁好好房子,自己的家具沙发,床还没有搬进去。)
      2、初始化属性填充:完成各种属性的赋值;(比如:装修,家电家具进场)

      三级缓存使用过程

      3 个 map 的四大方法,总体相关对象

      第一层:(也叫单例池)singletonObjects: 存放已经初始化好的 Bean。
      第二层:earlySingletonObjects: 存放的是实例化的了, 但是未初始化的 Bean。
      第三层:Map<String, ObjectFactory<?>> singletonFactories
      存放的是 FactroyBean。假如 A 类实现了 FactoryBean, 那么依赖注入的时候不是 A 类,而是 A类的 FAC天FactoryBean。

      /**
       * 单例对象的缓存:bean 名称--Bean 实例, 即:所有的单例池
       * 表示经历了完整生命周期的 Bean 对象
       * <b>第一级缓存</b>
       */
      private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
      
      /**
       * 单例工厂的高速缓存:bean 名称--ObjectFacotry
       * 表示存放生成的 bean 工厂
       * <b>第三级缓存</b>
       */
      private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
      
      /**
       * 早起的单例对象的高速缓存:bean 名称--Bean 实例
       * 表示 Bean 的生命周期还没有走完(Bean 的属性还未填充)就把这个 Bean 存入该缓存中
       * 也就是实例化的 bean 放入了该缓存中
       * <b>第二级缓存</b>
       */
      private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

      A/B 两对象在三级缓冲的迁移说明

      1、创建 A 过程中需要 B, 于是 A 将自己放入到三级缓存里面,去实例化 B
      2、B 实例化的时候发现需要 A ,于是 B 先查一级缓存,没有,再查二级缓存,还是没有再查三级缓存,找到了 A 然后把三级缓存里面的这个 A 放入到二级缓存里面,并且删除三级缓存里面的 A。
      3、 B 顺利初始化完毕后,将自己放入到一级缓存里面(此时 B 里面的 A 依然是创建中状态)后来接着创建 A, 此时 B 已经创建结束,直接从一级缓存里面拿到 B,然后完成创建,并且将 A 自己放到一级缓存中

      ObjectFactory 接口

      @FunctionalInterface
      public interface ObjectFactory<T> {
      
          /**
           * Return an instance (possibly shared or independent)
           * of the object managed by this factory.
           * @return the resulting instance
           * @throws BeansException in case of creation errors
           */
          T getObject() throws BeansException;
      
      }

      DEBUG 断点调试

      1、Bean 创建

      2、放入三级缓存

      3、属性填充(就是做属性注入)

      循环依赖解决

      整理流程梳理:

      1、调用 doGetBean() 方法 , 想要获取 beanA , 于是调用 getSingletion() 方法从缓存中查询 beanA
      2、在 getSingletion() 方法中,从一级缓存中查找,没有,返回 null
      3、doGetBean() 犯法中获取到 beanA 为 null, 于是就走对应的处理逻辑,调用 getSIngletion() 的重载方法(参数为 ObjecFactory  的)
      4、在 getSingletion() 方法中,先将 beanB_name 添加到一个集合中,用于标记该 bean 正在创建中,然后回调匿名内部类的 createBean  方法
      5、进入 AbstractAutowireCapableBenaFactory#doCreateBean , 先反射调用构造器创建出 beanA 的实例,然后判断,是否为单例、是否允许提前暴露引用(对于单例一般为 true)、是否正在创建中(即是在第四步的集合中)。判断为 true 则将 beanA 添加到【三级缓存】中。
      6、对 beanA 进行属性填充,此时检测到 beanA 依赖 beanB , 于是开始查找 beanB
      7、调用 doGetBean 方法,和上面的 beanA 过程一样,到缓存中查找 beanB, 没有则创建,然后给 beanB 填充属性。
      8、此时 beanB  依赖于 beanA , 调用 getSingleton() 获取 beanA , 依次从一级、二级、三级缓存中找,此时三级缓存中获取到 beanA 的创建工厂,通过创建工厂获取到 singletonObject, 此时这个 singletonObject 指向的就是上面的 doCreateBean() 方法实例化的 beanA 。
      9、这样 beanB 就获取到了 beanA 的依赖,于是 beanB 顺利完成实例话,并将 beanA 从三级缓存移动到二级缓存中。
      10、随后 beanA  继续他的属性填充工作,此时也获取到了beanB , beanA 也随之完成了创建,回调 getSingleton() 方法中继续向下执行,将 beanA 从二级缓存中移动到一级缓存中。

      7、Spring 循环依赖总结

      Spring 创建 Bean 主要分为两个步骤,创建原始 Bean 对象,接着去填充属性和初始化。
      每次创建 Bean 之前,我们都会从缓存中查一下有没有该 bean , 因为是单例, 只能有一个
      当我们创建 beanA  的原始对象之后,并且把它放入到三级缓存中,接下来就该填充属性了,这个时候发现依赖了 beanB , 就直接去创建 beanB, 同样的流程,创建完 beanB 统筹国内属性时又发现了它依赖了 beanA  又是同样的流程。

      不同的是:

      这个时候可以在三级缓存只能够查询到刚才放进去的原始对象 beanA , 所以不需要继续创建,用它注入 beanB , 完成 beanB 的创建
      既然 beanB 创建好了,所以 beanA 就可以弯沉属性填充的步骤了,接下来执行剩下的逻辑完,完成闭环

      @Nullable
      protected Object getSingleton(String beanName, boolean allowEarlyReference) {
          // 从 singletonObject 获取实例,singletonObject 中的实例都是准备好的 bean 实例
          Object singletonObject = this.singletonObjects.get(beanName);
          // isSingletonCurrentlyInCreation() 判断当前单例 bean 是否正在创建中
          if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
              
              singletonObject = this.earlySingletonObjects.get(beanName);
              if (singletonObject == null && allowEarlyReference) {
                  synchronized (this.singletonObjects) {
                      // Consistent creation of early reference within full singleton lock
                      singletonObject = this.singletonObjects.get(beanName);
                      if (singletonObject == null) {
                          // 一级缓存中没有就去二级缓存中找            
                          singletonObject = this.earlySingletonObjects.get(beanName);
                          if (singletonObject == null) {
                              // 二级缓存也没有,就去三级缓存查找
                              ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                              if (singletonFactory != null) {
                                  // 三级缓存存在的化,就把它移动到二级缓存
                                  singletonObject = singletonFactory.getObject();
                                  this.earlySingletonObjects.put(beanName, singletonObject);
                                  this.singletonFactories.remove(beanName);
                              }
                          }
                      }
                  }
              }
          }
          return singletonObject;
      }

      Spring 解决循环依赖靠的是 Bean 的“中间态” 这个概念。而这个中间态指的是 已经实例化还没有初始化的状态 ---> 半成品,实例话的过程有是通过构造器创建的,如果 A 还没有创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

      Spring 为了解决单例的循环依赖问题,使用了三级缓存:

      • 其中一级缓存为单例池(singletonObjects)
      • 二级缓存为提前曝光对象(earlySingletonObjects)
      • 三级缓存为提前曝光对象工厂(singletonFactories)

      假设 A,B 循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了 B ,同样的流程也是实例化后放入三级缓存,接着去填充属性时发现自己依赖 A,这个时候从缓存中查找到早起暴露的 A,没有 AOP 代理的化,直接将 A 原始对象注入 B,完成 B的初始化后,进行属性填充和初始化,这个时候 B完成后,就去完成剩下 A的步骤,如果有 AOP 代理,就会进行 AOP 处理获取代理后的  A,注入 B, 走剩下的流程。

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!