浅入浅出的讲解Spring循环依赖问题

目录
  • 前言
  • 概念
    • 什么是循环依赖?
    • 报错信息
  • 通俗版理解
    • 两人对峙
    • 必须有一人妥协
  • Spring版理解
    • 实例化和初始化什么区别?
  • 三级缓存
  • 创建过程(简易版)
  • 创建过程(源码版)
  • 最后

前言

最近有粉丝问到了循环依赖问题,以后再有人问你,拿这篇“吊打”他。

概念

什么是循环依赖?

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

通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,基于set方法构造注入的属性互相引用的场景。

循环依赖的种类及能否解决如下:

名称 是否可解决循环依赖
构造器循环依赖
Setter循环依赖
Prototype作用域的循环依赖

报错信息

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘myDao': Requested bean is currently in creation: Is there an unresolvable circular reference?

翻译一下

通过构造函数参数 0 表示的依赖关系未得到满足;嵌套的异常是 创建名称为'myDao'的bean时出错。请求的Bean目前正在创建中。是否存在一个无法解决的循环引用?

异常信息:bean当前创建异常org.springframework.beans.factory.BeanCurrentlyInCreationException。

通俗版理解

两人对峙

现在甲乙两个人,互相对峙,甲说乙先放,乙说甲先放。就是不开枪。

哎,就是玩!

相信这个场景大家在电视剧里都见过吧,最后一般是“反派死于话多”。

但是回到我们 spring里,我们是不希望有人死亡的,也就是必须两个bean都创建出来,怎么办?

必须有一人妥协

解决方案就是:必须有一个人先妥协。

甲说:我退一步,我先把弹夹卸了,你把枪放下。

乙一听就感动了,满含热泪的拿枪放下了。

甲一看乙没有打自己,也热泪盈眶,两人紧紧相拥。

从此过上了幸福美满的生活……

Spring版理解

回到我们spring里,先回顾一下bean的生命周期:

  • 实例化
  • 属性赋值
  • 初始化
  • 销毁

简单理解一下的上面的过程

实例化和初始化什么区别?

是不是只差了中间赋值的过程,那只实例化的bean可以使用吗?

当然不可以!

也就是说只实例化的bean是一个半成品,初始化之后才是成品,才可以使用。

现在A依赖B,B依赖A。

A对B说:我要完整的你

b也对a:我要完整的你

ok,两人打起来了,拿枪对峙。怎么解决?是不是得一个人妥协。

a说:算了吧,你给我个你的半成品,我将就一下。

b心里寻思,他用我的半成品创建一个完整的a,然后我就可以创建了。

心里这么想,嘴上就爽快答应着:行,没问题。

如此,a创建了完整的自己,b拿着a也完成了创建。

问题解决。

真的解决了吗?成品和半成品都存在哪里呢?

这就不得不提到大名鼎鼎的三级缓存。

三级缓存

spring提供了三级缓存来存放成品和半成品及工厂。位于DefaultSingletonBeanRegistry类中。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	/**
	*一级缓存:单例池
	*存放已经初始化的bean——成品
	*/
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    /**
	*三级缓存:单例工厂的高速缓存
	*存放生成bean的工厂
	*/
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    /**
	*二级缓存:早期单例对象的高速缓存
	*存放已经实例化但未初始化(未填充属性)的的bean——半成品
	*/
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
}

创建过程(简易版)

如果你是面试突击,建议把简易版被下来就可以应付面试了
等有时间再看源码版

假如A依赖B,B依赖A,那么这两个类之间形成了一个循环依赖

  • A先开始创建,通过其无参构造方法创建bean的实例,并将其实例放入到「二级缓存」提前暴露出来。A停止。
  • B开始创建,先去「一级缓存」找A的成品,找不到,再去「二级缓存」里找,还找不到,再去「三级缓存」里找,找到了A的创建工厂,通过工厂,拿到A的半成品,并将A放到「二级缓存」。
  • 拿到A后,B完成创建,将自己放入「一级缓存」。
  • 此时A继续创建,同样从「一级缓存」开始找,拿到B后完成创建,将自己放入「一级缓存」。

创建过程(源码版)

源码版建议配合spring源码边debug边食用。

1、当我们在调用getBean()获取bean时,实际调用的是doGetBean() 方法。doGetBean() 想要获取 beanA ,于是调用 getSingleton() 方法从缓存中查找 beanA

2、在 getSingleton() 方法中,从「一级缓存」中查找,没有,返回 null

3、doGetBean() 方法中获取到 beanA 为 null ,于是走对应的处理逻辑,调用 getSingleton() 的重载方法(参数为 ObjectFactory 的)

4、在 getSingleton()方法中,先将 beanA_name 添加到一个集合中,用于标记该 bean 正在创建中,然后回调匿名内部类的 createBean 方法

5、进入 AbstractAutowireCapableBeanFactory#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 从「二级缓存」移动到「一级缓存」中

最后

到此这篇关于Spring循环依赖问题的文章就介绍到这了,更多相关Spring循环依赖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring循环依赖正确性及Bean注入的顺序关系详解

    一.前言 我们知道 Spring 可以是懒加载的,就是当真正使用到 Bean 的时候才实例化 Bean.当然也不全是这样,例如配置 Bean 的 lazy-init 属性,可以控制 Spring 的加载时机.现在机器的性能.内存等都比较高,基本上也不使用懒加载,在容器启动时候来加载bean,启动时间稍微长一点儿,这样在实际获取 bean 供业务使用时,就可以减轻不少负担,这个后面再做分析. 我们使用到 Bean 的时候,最直接的方式就是从 Factroy 中获取,这个就是加载 Bean 实例的源

  • 浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种:构造器参数循环依赖 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn CreationException异常表示循环依赖. 如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建Test

  • 浅谈Spring 解决循环依赖必须要三级缓存吗

    我们都知道 Spring 是通过三级缓存来解决循环依赖的,但是解决循环依赖真的需要使用到三级缓冲吗?只使用两级缓存是否可以呢?本篇文章就 Spring 是如何使用三级缓存解决循环依赖作为引子,验证两级缓存是否可以解决循环依赖. 循环依赖 既然要解决循环依赖,那么就要知道循环依赖是什么.如下图所示: 通过上图,我们可以看出: A 依赖于 B B 依赖于 C C 依赖于 A public class A { private B b; } public class B { private C c; }

  • spring循环依赖策略解析

    循环依赖 所谓循环依赖就是多个Bean之间依赖关系形成一个闭环,例如A->B->C->...->A 这种情况,当然,最简单的循环依赖就是2个Bean之间互相依赖:A->B(A依赖B), B->A(B依赖A) .在Spring中,如果A->B,那么在创建A的过程中会去创建B,在创建B(或B的依赖)的过程中又发现B->A,这个时候就出现了循环依赖的现象. 循环依赖的解决 spring中的循环依赖只有当 1.Bean是单例, 2.通过属性注入的情况 这两个条件满足

  • 浅谈Spring如何解决循环依赖的问题

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的.这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够一下子思考出个中奥秘.本文主要针对这个问题,从源码的角度对其实现原理进行讲解. 1. 过程演示 关于Spring bean的创建,其本质上还是一个对象的创建,既然是对象,读者朋友一定要明白一点就是,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化.在Spring中,对象的实例化是通过反射实

  • 深入理解Spring中的循环依赖

    循环依赖 定义: 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比方CircularityA引用CircularityB,CircularityB引用CircularityC,CircularityC引用CircularityA.形成一个环状引用关系. 在使用Spring时,如果主要采用基于构造器的依赖注入方式,则可能会遇到循环依赖的情况,简而言之就是Bean A的构造器依赖于Bean B,Bean B的构造器又依赖于Bean A.在这种情况下Spring会在编译时抛出Bean

  • 详解Spring循环依赖的解决方案

    spring针对Bean之间的循环依赖,有自己的处理方案.关键点就是三级缓存.当然这种方案不能解决所有的问题,他只能解决Bean单例模式下非构造函数的循环依赖. 我们就从A->B->C-A这个初始化顺序,也就是A的Bean中需要B的实例,B的Bean中需要C的实例,C的Bean中需要A的实例,当然这种需要不是构造函数那种依赖.前提条件有了,我们就可以开始了.毫无疑问,我们会先初始化A.初始化的方法是org.springframework.beans.factory.support.Abstra

  • Spring循环依赖的三种方式(推荐)

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下spring是如果解决循环依赖的. 第一种:构造器参数循环依赖 Spring容器会将每一个正在创建的Bean 标识符放在一个"当前创建Bean池"中,Bean标识符在创建过程中将一直保持 在这个池中,因此如果在创建Bean过程中发现自己已经在"当前创建Bean池"里时将抛出 BeanCurrentlyInCrea

  • 浅入浅出的讲解Spring循环依赖问题

    目录 前言 概念 什么是循环依赖? 报错信息 通俗版理解 两人对峙 必须有一人妥协 Spring版理解 实例化和初始化什么区别? 三级缓存 创建过程(简易版) 创建过程(源码版) 最后 前言 最近有粉丝问到了循环依赖问题,以后再有人问你,拿这篇"吊打"他. 概念 什么是循环依赖? 多个bean之间相互依赖,形成了一个闭环.比如:A依赖于B.B依赖于C.C依赖于A. 通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,基于set方法构造注入的属性互相引用的

  • 浅谈MySQL之浅入深出页原理

    一.页的概览 我们往 MySQL 插入的数据最终都是存在页中的.在 InnoDB 中的设计中,页与页之间是通过一个双向链表连接起来. 而存储在页中的一行一行的数据则是通过单链表连接起来的. 上图中的 User Records 的区域就是用来存储行数据的.那 InnoDB 为什么要这么设计?假设我们没有页这个概念,那么当我们查询时,成千上万的数据要如何做到快速的查询出结果?众所周知,MySQL 的性能是不错的,而如果没有页,我们剩下的只能是逐条逐条的遍历数据了. 那页是如何做到快速查询的呢?在当前

  • 简单了解Spring循环依赖解决过程

    这篇文章主要介绍了简单了解Spring循环依赖解决过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而

  • Java Spring 循环依赖解析

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

  • Java中的Spring循环依赖详情

    目录 什么是循环依赖? 那么循环依赖是个问题吗? Bean的生命周期 三级缓存 解决循环依赖思路分析 Spring到底解决了哪种情况下的循环依赖 总结 什么是循环依赖? 很简单,就是A对象依赖了B对象,B对象依赖了A对象. 比如: 那么循环依赖是个问题吗? 如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情. 比如: 这样,A,B就依赖上了. 但是,在Spring中循环依赖就是一个问题了,为什么? 因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系

  • Java Spring循环依赖原理与bean的生命周期图文案例详解

    前言 Spring是如何处理循环依赖的,又是怎么做到,互相注入对方的proxy bean而不是raw bean的?现在就分析一下 一.循环依赖是什么 Spring中放入两个Service,分别是C1和C2,然后C1和C2又互为对方的成员变量.这种情况C1和C2就可以说是相互循环依赖了 二.源码图解 1. bean的主要生命周期图解 上图是一个没有循坏依赖的bean的主要生命周期节点,下图的循坏依赖可以结合该图解一起看 2.循环依赖图解 可以看到里面有一个很重要的逻辑: 当一个bean经过所有的步

  • Spring 循环依赖之AOP实现详情

    前言: 我们接着上一篇文章继续往下看,首先看一下下面的例子,前面的两个serviceA和serviceB不变,我们添加一个BeanPostProcessor: @Component public class MyPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansEx

  • Spring循环依赖的解决方法详解

    目录 什么是循环依赖: Spring实例Bean的本质 循环依赖主要场景 什么情况下循环依赖可以被解决 解决方式 说明:spring如何解决循环依赖,是面试中经常问到的题目,今天我们就来分享一下spring是如何解决循环依赖问题的. 什么是循环依赖: 我们先来看看官方文档的说法: 通俗来讲,就是A依赖B或者B依赖A,或者C依赖自己本身,或是三个以上,例如A依赖B,B依赖C,C又依赖A.如下图: Spring实例Bean的本质 Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所

  • Spring循环依赖的解决办法,你真的懂了吗

    介绍 先说一下什么是循环依赖,循坏依赖即循环引用,两个或多个bean相互引用,最终形成一个环.Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成 Spring的循环依赖有两种场景 构造器的循环依赖 属性的循环依赖 构造器的循环依赖,可以在构造函数中使用@Lazy注解延迟加载.在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入 属性的循环依赖主要是通过3个map来解决的 构造器的循环依赖 @Component publi

随机推荐