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

  • 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 在一个构造函数依赖的关系中,参数不全,再牛皮也不能把

Spring — 循环依赖的更多相关文章

  1. spring循环依赖问题分析

    新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...

  2. Spring 循环依赖

    循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...

  3. Springboot源码分析之Spring循环依赖揭秘

    摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效.或许刚说到这,有的小伙伴就会大惊失色了.Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我 ...

  4. Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)

    本篇文章解决以下问题: [1] . Spring循环依赖指的是什么? [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况? [3] . Spring能解决的循环依赖原理(三级缓存) 一 ...

  5. Spring循环依赖的解决

    ## Spring循环依赖的解决 ### 什么是循环依赖 循环依赖,是依赖关系形成了一个圆环.比如:A对象有一个属性B,那么这时候我们称之为A依赖B,如果这时候B对象里面有一个属性A.那么这时候A和B ...

  6. 这个 Spring 循环依赖的坑,90% 以上的人都不知道

    1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...

  7. 帮助你更好的理解Spring循环依赖

    网上关于Spring循环依赖的博客太多了,有很多都分析的很深入,写的很用心,甚至还画了时序图.流程图帮助读者理解,我看了后,感觉自己是懂了,但是闭上眼睛,总觉得还没有完全理解,总觉得还有一两个坎过不去 ...

  8. spring 循环依赖的一次 理解

    前言: 在看spring 循环依赖的问题中,知道原理,网上一堆的资料有讲原理. 但今天在看代码过程中,又产生了疑问. 疑问点如下: // 疑问点: 先进行 dependon 判断String[] de ...

  9. 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖

    本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...

随机推荐

  1. Java实现 蓝桥杯 算法训练 多阶乘计算

    试题 算法训练 多阶乘计算 问题描述 我们知道,阶乘n!表示n*(n-1)(n-2)-21, 类似的,可以定义多阶乘计算,例如:5!!=531,依次可以有n!..!(k个'!',可以简单表示为n(k) ...

  2. Java实现 蓝桥杯VIP 算法提高 传染病控制

    算法提高 传染病控制 时间限制:1.0s 内存限制:512.0MB 问题描述 近来,一种新的传染病肆虐全球.蓬莱国也发现了零星感染者,为防止该病在蓬莱国大范围流行,该国政府决定不惜一切代价控制传染病的 ...

  3. java实现第六届蓝桥杯显示二叉树

    显示二叉树 题目描述 排序二叉树的特征是: 某个节点的左子树的所有节点值都不大于本节点值. 某个节点的右子树的所有节点值都不小于本节点值. 为了能形象地观察二叉树的建立过程,小明写了一段程序来显示出二 ...

  4. 两种方法设置MMDVM静态组

    方法一.进入BM页面设置静态组 1.仪表盘配置页面点击下图所示进入BM 2.或是点击链接进入https://brandmeister.network 3..进入页面后点击My hotspots,显示你 ...

  5. RocketMQ系列(二)环境搭建

    RocketMQ的基本概念在上一篇中给大家介绍了,这一节将给大家介绍环境搭建.RocketMQ中最基础的就是NameServer,我们先来看看它是怎么搭建的. NameServer RocketMQ要 ...

  6. swagger--Failed to load API definition.

    打开 http://localhost:5000/swagger/v1/swagger.json 提示错误 An unhandled exception occurred while processi ...

  7. STL中常用算法

    一.排序 sort sort(first_pointer,first_pointer+n,cmp) 默认为升序 若要使用降序,自行写cmp 函数 bool cmp(int a,int b){ retu ...

  8. tensorflow2.0学习笔记第一章第一节

    一.简单的神经网络实现过程 1.1张量的生成 # 创建一个张量 #tf.constant(张量内容,dtpye=数据类型(可选)) import tensorflow as tf import num ...

  9. 温故知新-多线程-Cache Line存在验证

    文章目录 简述 缓存行Cache Line 验证CacehLine存在? 参考 你的鼓励也是我创作的动力 Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持 ...

  10. 源码分析(1)-HashMap(JDK1.8)

    UML类图 java.util.Map<K, V>接口,有4个实现类:HashMap.Hashtable.LinkedHashMap和TreeMap. 1.说明 (1)HashMap除允许 ...