Java Spring处理循环依赖详解

作者:Samson_bu 时间:2022-07-14 05:06:16 

01-前言:什么是循环依赖?

首先,我们先明确下依赖的定义。 如果一个 Bean bar 的属性,引用了容器中的另外一个 Bean foo,那么称 foo 为 bar 的依赖,或称 bar 依赖 foo。 如果用代码表示,可以表示为:

@Component("foo")
public Class Foo {
   @Autowired
   private Bar bar;  // 称 foo 依赖 bar
}

@Component("bar")
public Class Bar {
   @Autowired
   private Baz baz;  // bar 依赖 baz
}

其次,循环引用的概念是指多个 Bean 之间的依赖关系形成了环。 接着上面的例子,如果 baz 依赖 foo 或 bar 都将形成循环依赖。

@Component("baz")
public class Baz {
   @Autowired
   private Foo foo; // 形成了循环依赖,baz -> (依赖) foo -> bar -> baz ...
}

02-Spring 如何处理循环依赖?

在之前的文章中,我跟大家一块学习了 Spring 创建 Bean 过程的源码。 我们知道:在 createBeanInstance 阶段,需要解决构造器、工厂方法参数的依赖; 在 populateBean 阶段,需要解决类属性中对其他 Bean 的依赖。 这其实对应了 Spring 中支持的两种依赖注入方式,基于构造器的依赖注入和基于 setter 方法的依赖注入,分别对应前面的两种情况。

接下来,我会通过上节介绍的示例,来分情况讨论产生循环依赖的场景。 为了使讨论过程更清楚、更简洁,我会让 foo 依赖 bar,而 bar 依赖 foo。 在接下来的描述中,我假设 Spring 会先创建 Bar 的对象,再创建 Foo 的对象。 针对不同的依赖情况,可以分为四种场景:

  • 第一种场景,Bar 在构造器参数中依赖 Foo,Foo 在构造器参数中依赖 Bar。这种场景下,依赖的注入发生在 Bar 和 Foo 的实例化阶段。

  • 第二种场景,Bar 在构造器参数中依赖 Foo,Foo 通过 setter 函数依赖 Bar。这种场景下,Bar 中注入 Foo 发生在实例化阶段,Foo 中注入 Bar 发生在属性填充阶段。

  • 第三种场景,Bar 通过 setter 函数依赖 Foo,Foo 在构造器参数中依赖 Bar。这种场景下,Bar 中输入 Foo 发生在属性填充阶段,而 Foo 中注入 Bar 发生在实例化阶段。

  • 第四种场景,Bar 通过 setter 函数依赖 Foo,Foo 通过 setter 函数依赖 Bar。 这种场景下,依赖注入均发生在属性填充阶段。

在具体分析上述四种场景之前,先说下结论: Spring 可以解决场景三、四中出现循环依赖的情况,而第一、二种场景,Spring 无法解决,需要重构依赖或者延迟延迟依赖注入的时机(例如使用 @Lazy 等)。 细心的读者可能会问第二种、第三种场景有什么不同呢? 其实第二、第三种场景本质上是同一种情况,唯一的不同是实例化的先后顺序。 结合这个信息,可以得出,先创建的类以构造器参数方式依赖其他 Bean,则会发生循环依赖异常。 反过来,如果先创建的类以 setter 方式依赖其他 Bean,则不会发生循环依赖异常。

接下来,我会详细分析每一种场景,并指出抛循环依赖异常的时机。 

首先,所有的单例 Bean 会在容器启动后被创建 ConfigurableListableBeanFactory#preInstantiateSingletons,即所谓的 “eager registration of singletons” 过程。

第一种场景,会先触发 Bar 类的实例 bar 的创建。在 createBeanInstance 阶段,会通过 ConstructorResolver#autowireConstructor 来创建实例。 ConstructorResolver#createArgumentArray 会解析构造器中的参数,并处理对其他 Bean 依赖的引用 ConstructorResolver#resolveAutowiredArgument。 处理依赖的方式就是通过 DefaultListableBeanFactory#resolveDependency 来查找符合条件的 Bean,最终还是通过 AbstractBeanFactory#getBean 来从容器中取。 当通过 getBean("bar") 来触发 Spring 创建 bar 时,在实例化阶段,根据构造器参数来 getBean("foo") 并触发 foo 的创建。 在 foo 的实例化过程与 bar 的是完全一样的,最终 getBean("bar")。这是容器中的 bar 还没有创建好,所以会再次触发创建过程。 在真正创建过程之前,在 DefaultSingletonBeanRegistry#getSingleton 中会有一次检查,DefaultSingletonBeanRegistry#beforeSingletonCreation 如果发现要创建的 bean 正在创建过程中,则抛出异常。

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?

第二种场景,同样先构建 bar 实例。与第一种场景不同之处在于 foo 创建时,它的 createBeanInstance 阶段能够执行完毕。 原因是 foo 只有一个无参构造器(即默认构造器),不需要注入其他依赖。 foo 的 createBeanInstance 阶段执行完毕后,会进入 populateBean 阶段。 在这个阶段中,AutowiredAnnotationBeanPostProcessor#postProcessProperties 会处理 setter 函数依赖的 Bean。 大致处理过程为:AutowiredAnnotationBeanPostProcessor 识别到 foo 中包含需要注入依赖的 setter 函数,将其映射为 AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement 对象。 然后调用 AutowiredMethodElement#inject 方法注入依赖。 在 inject 方法中,会调用 DefaultListableBeanFactory#resolveDependency 来查找对应的依赖。 到这里为止,后续的过程与第一种场景完全一致了。 从容器中尝试获取 bar,发现不存在,会出发 bar 的再次创建,最终在 DefaultSingletonBeanRegistry#beforeSingletonCreation 中抛出异常。

第三种场景,同样先构建 bar 实例。由于它只包含一个默认构造器,所以它的 createBeanInstance 阶段会顺利完成,然后进入 populateBean 阶段。 当你仔细回看一下 Spring 创建 Bean 过程的源码,你会发现下面这段代码:

if (earlySingletonExposure) {
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

这段逻辑发生在 createBeanInstance 之后,尚未进入 populateBean 之前。 这里其实就是 Spring 解决循环依赖机制的核心点之一,这里我暂且不深入介绍,后面会有详细的分析。

继续前面的分析。bar 实例创建进入到 populateBean 阶段后,会检查其自身依赖情况,然后注入对应的依赖 Bean。 这里的处理逻辑依然是 AutowiredAnnotationBeanPostProcessor#postProcessProperties 来处理。 当尝试注入 foo 时,会出发 foo 实例的创建过程。foo 通过构造器依赖 bar,因此在其 createBeanInstance 阶段,会通过 ConstructorResolver#autowireConstructor 完成依赖注入。 此时通过 getBean("bar") 从容器中尝试获取 bar 时,能够“获取到”。 注:这里为什么不会出发 bar 的创建,反而能够直接得到 bar 对象呢?上面的获取到我加了引号,它其实获得的并不是一个完整、可用的 bar。 它获得的是通过 earlySingletonExposure 提前暴露出的对象。 这个过程在后面介绍 * 缓存时会详细介绍。

篇幅原因,第四种场景我不在这里继续分析,感兴趣的读者可以自己尝试分析下。 简单提示下,它的过程有点像第三种场景前半段、第二种场景的后半段结合起来。

在上述四种场景下,第一种情况,依赖双方都是通过构造器依赖对方,这种情况下 Spring 是无法处理的。 而且,我认为出现这一情况,属于是设计上的缺陷,应当通过重新设计依赖关系来解决,例如可以将基于构造器的注入修改为基于 setter 的注入,或者通过 @Lazy 将依赖的初始化延迟到使用时。 通过 Foo、Bar 类来举例说明。

@Component
public class Foo {
   private Bar bar;
   @Autowired
   public Foo(@Lazy Bar bar) {
       this.bar = bar;
   }
}

@Component
public class Bar {
   private Foo foo;
   @Autowired
   public Bar(Foo foo) {  // 或者将对 foo 的依赖,注解为 @Lazy 表示使用时才初始化
       this.foo = foo;
   }
}

另一种修改方式就是,将第一种情况,修改为第二种情况,即:

@Component
public class Bar {
   private Foo foo;
   @Autowired
   public void setFoo(Foo foo) {
       this.foo = foo;
   }
}

03-Spring 中解决循环依赖的 * 缓存

Spring 中设计了一个 * 缓存用来解决前面介绍的循环依赖问题的处理。 * 缓存包括:

  • singletonObjects,为一级缓存,保存了 beanName -> bean instance 的映射关系。存放的是完全可用的单例 Bean 对象。

  • earlySingletonObjects,为二级缓存,保存了 beanName -> bean instance 的映射关系。 在一级、二级缓存都没有发现目标对象,但 * 缓存中存在 ObjectFactory 对象时,调用 ObjectFactory#getObject 创建实例,放入二级缓存,删除 * 缓存中的 ObjectFactory 对象。

  • singletonFactories,为 * 缓存,保存了 beanName -> ObjectFactory 的映射关系。 在 doCreateBean 时,会向这个 map 中添加 beanName: () -> getEarlyBeanReference(beanName, mbd, bean) 的映射关系,value 是一个函数式接口 ObjectFactory。

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);
       }
   }
}

大概了解了 Spring 中的 * 缓存后,我们再回过头来看一下 AbstractBeanFactory#getBean 过程。 它的实际工作是在 AbstractBeanFactory#doGetBean 中完成的。 doGetBean 方法的具体实现可以简化、抽象为:

protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
       // 缓存中存在
       /**
       * 如果 beanName 是一个 FactoryBean,则获取对应的 Bean
       * 如果 beanName 是一个普通的 Bean,则返回这个 Bean 本身
       */
       beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   } else {
       // 缓存中不存在
       // 创建对象

if (mbd.isSingleton()) {
           /**
           * 这里的 getSingleton 是 getSingleton(beanName) 的重载版本
           * 它接受一个 beanName 和 一个 ObjectFactory 作为参数
           * 调用 ObjectFactory#getObject 产生一个实例
           * 并通过 addSingleton(beanName, singletonObject); 将实例添加到 singletonObjects 中
           * 这里 createBean 的代码就是前面提到的 Spring 创建 Bean 实例的过程 doCreateBean
           */
           sharedInstance = getSingleton(beanName, () -> {
               try {
                   return createBean(beanName, mbd, args);
               }
           });
           /**
           * 同前面分支中的作用一样
           */
           beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
       }
   }
   /**
   * 判断 Bean 类型与 requiredType 类型是否一直,一致则直接返回,不一致则需要进行转换
   */
   return adaptBeanInstance(name, beanInstance, requiredType);
}

从上面的源码可以知道,当 DefaultSingletonBeanRegistry#getSingleton(beanName) 时,会先从多级缓存中取对象(可能是 bean instance,也可能是对应的 ObjectFactory)。 从多级缓存中取对象的源码如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   Object singletonObject = this.singletonObjects.get(beanName);
   /**
   * 判断第一级缓存中是否有完全可以可用的 Bean 实例,若有则返回;
   * 若没有,则根据情况判断
   * isSingletonCurrentlyInCreation(beanName) 检查的是在 `Set<String> singletonsCurrentlyInCreation` 集合中是否包含要获取的 Bean 实例
   * beanName 只在调用 beforeSingletonCreation(String beanName)  时被添加到 singletonsCurrentlyInCreation 集合中
   * beforeSingletonCreation 在创建 bean,即 doCreateBean 之前调用,在创建过程完成以后,调用 afterSingletonCreation 从集合中移除 beanName
   */
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       /**
       * 执行到这个分支,其实说明 Bean 已经在创建过程中了,只不过是尚未完全可用(即一级缓存中没有)
       * 检查二级缓存,是否包含指定的 Bean
       *
       * 二级缓存里的内容何时被添加或设置进来的呢?
       * 我们可以检查下 earlySingletonObjects.put 方法都在哪里调用。
       * 检查后发现,其实 earlySingletonObjects 就是在当前方法中设置的,我们接着往下看。
       */
       singletonObject = this.earlySingletonObjects.get(beanName);
       if (singletonObject == null && allowEarlyReference) {
           /**
           * 这里的 allowEarlyReference 的意思就是指是否允许在二级缓存中创建一个对象,即是否允许暴露未完全可用的对象
           * 如果 allowEarlyReference 为假,则不会操作二级、 * 缓存,而仅检查一级缓存中是否有完全可用的 Bean 实例
           * 这也意味着,不允许返回未完全可用状态的 Bean
           *
           * 当发现二级缓存中没有对象,同时又允许提前引用(即 allowEarlyReference 值为真)
           * 则检查 * 缓存中是否有对应的 ObjectFactory 对象,若有,则调用它的 getObject 方法产生对象,然后将其放置到二级缓存中,同时删除 * 缓存中的对象工厂实例
           * 若 * 缓存中也没有对象工厂实例,则说面 bean 还未创建
           */
           synchronized (this.singletonObjects) {
               /**
               * 这里会进行一个 double-check,避免多线程间的线程安全问题
               */
               // 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) {
                           /**
                           * * 缓存中存在对象工厂实例,则通过它产生一个 Bean 实例
                           * 加入到二级缓存中,同时删除 * 缓存中的对象工厂实例
                           */
                           singletonObject = singletonFactory.getObject();
                           this.earlySingletonObjects.put(beanName, singletonObject);
                           this.singletonFactories.remove(beanName);
                       }
                   }
               }
           }
       }
   }
   return singletonObject;
}

注:

  • isSingletonCurrentlyInCreation(beanName) 意味着什么呢?意味着对应的 Bean 在 doCreateBean 过程中,可能在 createBeanInstance \ populateBean \ initializeBean 阶段中。

  • 在前面提到,createBeanInstance 后,Bean 会被添加到上述多级缓存中的第 * 缓存中,存入对象是 beanName -> objectFactory 映射关系。 当其他的 Bean 依赖当前 Bean 时,而且允许引用提前暴露的 Bean(即未完全可用的 Bean),会检查第二级缓存,如果没有还会检查第 * 缓存,并在得到对应 objectFactory 时,获得对象并将其从第 * 移动到第二级。

有些读者看到这里可能会有个疑问,那二级缓存中的对象什么时候删除呢? 我们再来回头看下 doGetBean 中的代码片段:

sharedInstance = getSingleton(beanName, () -> {
   try {
       return createBean(beanName, mbd, args);
   }
});

这里的 singleton

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   synchronized (this.singletonObjects) {
       Object singletonObject = this.singletonObjects.get(beanName);
       if (singletonObject == null) {
           /**
           * 检查 Bean 是否在创建过程中,避免重复创建,
           * 不能解决的循环依赖也是在这里抛出异常
           */
           beforeSingletonCreation(beanName);
           boolean newSingleton = false;
           try {
               /**
               * 这里调用的其实就是  AbstractAutowireCapableBeanFactory#createBean
               * 然后会执行 doCreateBean(三个阶段)
               */
               singletonObject = singletonFactory.getObject();
               newSingleton = true;
           }
           catch (IllegalStateException ex) {
               // Has the singleton object implicitly appeared in the meantime ->
               // if yes, proceed with it since the exception indicates that state.
               singletonObject = this.singletonObjects.get(beanName);
               if (singletonObject == null) {
                   throw ex;
               }
           }
           catch (BeanCreationException ex) {
               throw ex;
           }
           finally {
               afterSingletonCreation(beanName);
           }
           if (newSingleton) {
               /**
               * 这里说明一个 bean 创建过程的三个阶段都执行完毕了
               */
               addSingleton(beanName, singletonObject);
           }
       }
       return singletonObject;
   }
}
/**
* 将 Bean 实例添加到第一级缓存
* 将第二级、第 * 缓存中的对象删除
*/
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);
}
}

到这里为止,相信你对 Spring 创建 Bean 的过程以及处理循环依赖的机制能够有个大致的了解。 如果想了解的更深或跟多细节,可以自己单步调试下。 希望今天的内容能够对你有所帮助。如果有写得不对的地方,也请大家多批评指正。

来源:https://juejin.cn/post/7224055763543064631

标签:Java,Spring,循环,依赖
0
投稿

猜你喜欢

  • Android中将View的内容保存为图像的简单实例

    2023-11-01 12:45:41
  • C#获取系统版本信息方法

    2022-12-13 04:45:20
  • Android App页面滑动标题栏颜色渐变详解

    2023-10-14 20:08:17
  • @NonNull导致无法序列化的问题及解决

    2021-07-21 23:07:33
  • Android中Matrix用法实例分析

    2023-07-07 18:40:22
  • c#中的扩展方法学习笔记

    2023-04-11 10:29:15
  • springBoot @Scheduled实现多个任务同时开始执行

    2023-06-19 23:16:41
  • 浅谈Maven的安装及修改为阿里云下载依赖

    2023-08-05 08:30:37
  • Java构造方法和方法重载详解

    2021-06-06 13:23:00
  • Android 安全加密:非对称加密详解

    2021-08-03 18:39:43
  • Android仿Keep运动休息倒计时圆形控件

    2022-08-02 07:54:36
  • Android手势识别功能

    2022-07-23 22:04:26
  • SpringBoot+JSON+AJAX+ECharts+Fiddler实现前后端分离开发可视化

    2021-11-12 14:49:17
  • Unity通过脚本创建网格Mesh的方法

    2023-02-26 23:38:00
  • 解决persistence.xml配置文件修改存放路径的问题

    2023-07-16 09:53:54
  • springmvc实现自定义类型转换器示例

    2021-09-29 23:46:53
  • Android Studio提示inotify大小不足的解决办法

    2022-01-06 23:48:07
  • Android Studio实现简单计算器功能

    2023-10-17 03:54:42
  • android在连拍菜单中增加连拍张数选项功能实现代码

    2023-05-27 19:08:38
  • 使用jsoup解析html的table中的文本信息实例

    2021-06-19 23:07:59
  • asp之家 软件编程 m.aspxhome.com