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

  • 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. PAT 换个格式输出整数

    让我们用字母 B 来表示“百”.字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n,换个格式来输出任一个不超过 3 位的正整数.例如 234 应该被输出为BBSSS1234,因为它有 ...

  2. Vue好书推荐

    1.Vue.js实战 从基础知识到ui组件封装和剖析,层层推进,最后两个案例实战.适合零基础入门,学完可就业.(推荐看这本) 交流地址(pdf原件):链接(点击跳转):提取码:7IsG 2.vue.j ...

  3. 滴滴HBase大版本滚动升级之旅

    桔妹导读:滴滴HBase团队日前完成了0.98版本 -> 1.4.8版本滚动升级,用户无感知.新版本为我们带来了丰富的新特性,在性能.稳定性与易用性方便也均有很大提升.我们将整个升级过程中面临的 ...

  4. 64位手机无法加载x5(libmttwebview.so is 32-bit instead of 64-bit)

    x5内核暂时不提供64位的so文件,在64位手机上需要让AP以32位模式运行. 具体操作如下: 1.如果使用是Eclipse则需要将所有的.so文件都放置在so加载目录:lib/armeabi文件夹下 ...

  5. 【代理】内网穿透工具 frp&frps

    frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发. ### frp 的作 ...

  6. [NOI Online #3]魔法值

    题目   点这里看题目. 分析   我们不难想到,对于系数进行一下的拆分: \[\begin{aligned} f(u,j)&=\bigoplus_{(u,v)\in E} f(v,j-1)\ ...

  7. 操作-读取excel

    xlrd 该模块主要用来读取excel 注:sheet表示的是excel的表,就是底下的工作栏 (1) 打开excel文件并获取所有sheet import xlrd # 打开Excel文件读取数据 ...

  8. 这一次搞懂Spring事务注解的解析

    前言 事务我们都知道是什么,而Spring事务就是在数据库之上利用AOP提供声明式事务和编程式事务帮助我们简化开发,解耦业务逻辑和系统逻辑.但是Spring事务原理是怎样?事务在方法间是如何传播的?为 ...

  9. MySQL的使用方法和视图、索引、以及存储过程的一些简单方法

    一,基本概念 1, 常用的两种引擎:         (1) InnoDB        a,支持ACID,简单地说就是支持事务完整性.一致性:         b,支持行锁,以及类似ORACLE的一 ...

  10. html/css 滚动到元素位置,显示加载动画

    每次滚动到元素时,都显示加载动画,如何添加? 元素添加初始参数 以上图中的动画为例,添加俩个左右容器,将内容放置在容器内部. 添加初始数据,默认透明度0.左右分别移动100px. //左侧容器 .it ...