Spring循环依赖

插播一条 本来计划学习Spring cloud 面试咨询 问我Spring 依赖注入的时候 有时会有循环依赖的场景 这种Spring内部是如何解决的?
嗯? 这是什么问题 没准备过啊

Spring循环依赖

1、 什么是循环依赖
循环依赖就是循环引用,就是两个或多个bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A
在这里插入图片描述
例如这张图 三个对象之间相互依赖

2、Spring 怎么解决?
Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的(但是构造器必须是在获取引用之前)

Spring的单例对象的初始化主要分为三步:

  1. createBeanInstance:实例化,调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是对bean的依赖属性进行填充
  3. initializeBean:调用Spring xml中的init方法

从上面单例bean的初始化可以知道:循环依赖主要发生在第一、二步,也就是构造器循环依赖和field循环依赖。那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存

这三级缓存分别指:

singletonFactories:单例对象工厂的Cache
earlySingletonObjects:提前暴光的单例对象的Cache
singletonObjects:单例对象的Cache
在创建bean的时候,首先想到的是从Cache中获取这个单例的bean,这个缓存就是singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则从singletonFactories中移除,并放入earlySingletonObjects中(其实也就是从三级缓存移动到了二级缓存)

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory。这里就是解决循环依赖的关键,发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还没有进行初始化的第二步和第三步,但是已经能能够根据对象引用能定位到堆中的对象,所以Spring此时将这个对象提前曝光出来

“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况:A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中
以上完全复制博客https://blog.csdn.net/qq_40378034/article/details/104093014

说的有些复杂 来简单粗暴一点解读这个事:

首先理解一个概念 初始化单例对象的三个步骤分别对应着三级缓存, 刚开始在三级缓存中,完全初始化完成后 就在一级缓存中

  1. A和B两个对象 相互依赖 Spring现在要初始化这两个对象
  2. 调用A的构造方法 初始化A实例 放到三级缓存 SingletonFactories中 暴露出来
  3. 然后 初始化A的属性参数 发现 诶 要初始化B对象
  4. 好我们来初始化B实例 调用B的构造方法 初始化B的实例 放到三级缓存中来
  5. 初始化B的属性参数 发现 诶里面 有A实例需要初始化 好 这时幸好已经在三级缓存中 初始化好了A实例 直接放过来 初始化完成
  6. 然后回过头来继续初始化A对象 这时B对象已经完全初始化好了 A实例直接从一级缓存中 把B实例拿出来即可
  7. 到此 完成了A和B实例的循环依赖的初始化

3、构造器注入搞不定
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

4、prototype 作用域就知道搞不定了

首先需要知道 什么是prototype? 
prototype作用域部署的bean,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被singleton作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用。)

看描述就知道搞不定了 Spring 初始化完成之后就不管他了 那怎么来解决循环依赖的问题。

查了几篇博客 看到这个 基本明白了 https://blog.csdn.net/qq_40378034/article/details/104093014
https://www.cnblogs.com/cocoxu1992/p/10576651.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值