Spring — 循环依赖

简介: 读完这篇文章你将会收获到• Spring 循环依赖可以分为哪两种• Spring 如何解决 setter 循环依赖• Spring 为何是三级缓存 , 二级不行 ?• Spring 为啥不能解决构造器循环依赖

读完这篇文章你将会收获到

  • Spring 循环依赖可以分为哪两种
  • Spring 如何解决 setter 循环依赖
  • Spring 为何是三级缓存 , 二级不行 ?
  • Spring 为啥不能解决构造器循环依赖


概述


循环依赖就是循环引用,两个或以上的 bean 相互持有对方。比如说 beanA 引用 beanB , beanB 引用 beanCbeanC 引用 beanA , 它们之间的引用关系构成一个环。


Spring 如何解决循环依赖


Spring 中的循环依赖包括

  • 构造器循环依赖
  • setter 循环依赖


构造器的依赖

Spring 对于构造器的依赖、无法解决。只会抛出 BeanCurrentlyInCreationException 异常。

protected void beforeSingletonCreation(String beanName) {
  if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
  }
 }
复制代码


setter 的循环依赖

不管是 autowireByName 还是 autowireByType 都是属于这种。Spring 默认是能够解决这种循环依赖的,主要是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤的 bean 来完成的。而且只能解决 singleton 类型的循环依赖、对于 prototype 类型的是不支持的,因为 Spring 没有缓存这种类型的 bean


Spring 是如何解决的

其实很简单、在 Spring 获取单例流程(一) 中我们曾提及过三级缓存

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   // 这个bean 正处于 创建阶段
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // 并发控制
      synchronized (this.singletonObjects) {
         // 单例缓存是否存在
         singletonObject = this.earlySingletonObjects.get(beanName);
         // 是否运行获取 bean factory 创建出的 bean
         if (singletonObject == null && allowEarlyReference) {
            // 获取缓存中的 ObjectFactory
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               // 将对象缓存到 earlySingletonObject中
               this.earlySingletonObjects.put(beanName, singletonObject);
               // 从工厂缓冲中移除
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}
复制代码

Spring 解决 setter 循环依赖的关键点就是在这里,主要是 singletonFactories 这个 Map

我们可以先梳理一下整体的流程

beanA --> beanB --> beanC -->beanA
复制代码


以上面为例子、我们先假设它们是构造器的循环依赖

  1. Spring 初始化完成之后、接收到一个 getBean 的调用请求、请求  beanA
  2. Spring 发现三级缓存中都没有 beanA 的存在、所以开始创建 beanA 的流程
  3. beanA 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanA 正在创建中
  4. 兜兜转转,发现我要 new 一个 beanA 的对象、我要先获得一个 beanB 的对象、好、我们就进行一个 getBean(beanB)
  5. Spring 发现三级缓存中都没有 beanB 的存在、所以开始创建 beanB 的流程
  6. beanB 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanB 正在创建中
  7. 兜兜转转,发现我要 new 一个 beanB 的对象、我要先获得一个 beanC 的对象、好、我们就进行一个 getBean(beanC)
  8. Spring 发现三级缓存中都没有 beanC 的存在、所以开始创建 beanC 的流程
  9. beanC 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanC 正在创建中
  10. 兜兜转转,发现我要 new 一个 beanC 的对象、我要先获得一个 beanA 的对象、好、我们就进行一个 getBean(beanA)
  11. Spring 发现三级缓存中都没有 beanA 的存在、所以开始创建 beanA 的流程
  12. beanA 放入到 singletonsCurrentlyInCreation 集合中去、但是在这个时候、插入到集合中失败、直接抛出异常


而假如我们是一个 setter 的循环依赖

  1. Spring 初始化完成之后、接收到一个 getBean 的调用请求、请求  beanA
  2. 先判断三级缓存中有没有 beanA ,如果没有则往下进行
  3. beanA 放入到 singletonsCurrentlyInCreation 集合中去、代表着 beanA 正在创建中
  4. 兜兜转转,终于创建了一个 beanA , 但是这个时候的 beanA 是一个不完整的状态、因为很多属性没有被赋值、比如说 beanA 中的成员变量 beanB 现在还是一个 null 的状态
  5. 然后判断是否需要将当前创建的不完整的 beanA 加入到第三级缓存中,正常来说都是会被加入到第三级缓存中的
  6. 加入第三级缓存以后、进行一个属性填充,这个时候发现需要填充一个 beanB 对象
  7. 然后如上面那样、先看看三级缓存有没有 beanB ,如果没有则创建一个并不完整的 beanB、然后加入到第三级缓存中、然后发现需要填充一个 beanC 的属性
  8. 然后如上面那样、先看看三级缓存有没有 beanC ,如果没有则创建一个并不完整的 beanC、然后加入到第三级缓存中、然后发现需要填充一个 beanA 的属性
  9. 这个时候,先看看三级缓存中有没有 beanA ,发现在第三级缓冲中有不完整的 beanA、将其从第三级缓存中移除出来、放入到第二级缓存中,然后返回给 beanC 用于填充属性
  10. 然后 beanC 的 属性填充完毕,则将其从 singletonsCurrentlyInCreation 集合中移除掉,代表 beanC 已经真正的创建好了
  11. 然后将 beanC 加入到第一级缓存中,并将其从第三级缓存中移除,并返回给 beanBbeanB 也如 beanC 那样处理
  12. beanA 也如 beanBbeanC 那样处理、加入到第一级缓存中、然后从第二级缓存中移除
  13. 结束


其实上面的屁话又长又臭,但是流程还是非常简单的


为啥是三级缓存,二级不行吗?


/**
 * Cache of singleton objects: bean name to bean instance.
 * 存放的是单例 bean、对应关系是 bean Name --> bean instance
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
 * Cache of early singleton objects: bean name to bean instance.
 * 存放的早期的 bean、对应的关系 也是 beanName --> bean instance
 * 与 singletonObjects 区别在于 earlySingletonObjects 中存放的bean 不一定是完整的、
 * bean 在创建过程中就加入到 earlySingletonObjects 中了、所以在bean创建过程中就可以通过getBean 方法获取、
 * 这个Map 也是解决循环依赖的关键所在
 **/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
 * Cache of singleton factories: bean name to ObjectFactory.
 * 存放的是 ObjectFactory 、可以理解为创建单例bean的factory、对应关系是 bean name --> objectFactory
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码


我们来看看从第三级缓存升级到第二级缓存究竟发生了什么

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
         }
      }
   }
   return exposedObject;
}
// 默认实现
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
  return bean;
}
复制代码


其实只要有二级缓存也是可以的,虽然可以达到解决 setter 循环依赖的问题、但是却无法给用户提供一个扩展接口(当存在循环依赖的)。


就好比说、上面的例子、在循环依赖的关系中,当 beanA第三级缓存升级到第二级缓存的时候,我们可以在其升级的时候去设置一些 beanA 的属性或者做一些其他事情,我们只需要在 beanA 的类中实现 SmartInstantiationAwareBeanPostProcessor 接口即可


但是单纯只有二级缓存的话,当我们创建好一个没有完成初始化的 bean 的时候、要么就直接调用 ObjectFactorygetObject 方法获取经过回调的 bean 放入到第二级缓存(不管这个 bean 存不存在一个循环引用的关系链中),要么就直接放刚刚创建好的没有完成初始化的 bean 放入到第二级缓存。无论是哪种情况,都无法达到这样一个需求:当存在循环依赖的时候,我们作为用户需要对其进行一些设置或者一些其他的操作


为啥不能解决构造函数的循环依赖


如果按照解决 setter 循环依赖的流程、是否能够解决?先将一个不完整的 bean 放入到第三级缓存中,然后提供出去给其他 bean 依赖。但是呢,问题是我无法创建出这么一个不完整的 bean 在一个构造函数依赖的关系中,参数不全,再牛皮也不能把



目录
相关文章
|
1月前
|
XML 缓存 Java
Spring源码之 Bean 的循环依赖
循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示: 代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢? 可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。 一、复现循环依赖问题 Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖 1. 基于xml复现循环依赖 定义实体 Bean java复制代码public class A {
|
2月前
|
存储 缓存 Java
明知面试要问spring循环依赖,很多人还是搞不懂!
Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃不掉。如果你回答得好,那么这就是你的必杀技,反正,那就是面试官的必杀技,这也是取这个标题的原因,当然,本文的目的是为了让你在之后的所有面试中能多一个必杀技,专门用来绝杀面试官!
33 0
|
1天前
|
存储 缓存 Java
Spring解决循环依赖
Spring解决循环依赖
|
18天前
|
缓存 算法 Java
开发必懂的Spring循环依赖图解 Spring 循环依赖
开发必懂的Spring循环依赖图解 Spring 循环依赖
18 1
|
21天前
|
缓存 算法 Java
Spring解决循环依赖
Spring解决循环依赖
19 0
|
1月前
|
Java 开发者 Spring
【Java】Spring循环依赖:原因与解决方法
【Java】Spring循环依赖:原因与解决方法
27 0
|
1月前
|
Java 数据库连接 Spring
Spring Boot 3.2.2整合MyBatis-Plus 3.5.5依赖不兼容问题
Spring Boot 3.2.2整合MyBatis-Plus 3.5.5依赖不兼容问题
42 0
|
1月前
|
消息中间件 运维 监控
|
22天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
38 0
|
30天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
106 0