前言

我们前面说了几遍Spring的文章,了解了比较核心的知识点IOC和AOP,还有就是事务传播这种,不知道大家听过Spring的循环依赖这个问题吗,而且这个问题是面试经常问的,属于Spring的一个比较重要的话题,也比较典型,比较考验一个人对Spring的研究程度,也算是Spring的一个高阶问题之一了

有的小伙伴内心对于Spring的看法可能是,感觉自己懂了,但是让自己来叙述给一个小白的话,可能部分人就讲不出来了,这一篇呢,主要就是帮你解决这个痛点,让你彻底搞定Spring的循环依赖,也让你下次不再担心,而是张口就来

我当年面试的时候,也是遇到不少面试官问过这个问题,每次都是绝杀面试官

循环依赖问题,本文会通过三个方面来简单介绍

1、什么是Spring的循环依赖

2、多种情况下的循环依赖

3、Spring如何解决循环依赖

了解Spring循环依赖

什么是循环依赖

@Component
public class AService {// AService中注入了BService@Autowiredprivate BService bService;
}@Component
public class BService {// BService中也注入了AService@Autowiredprivate AService aService;
}

这是属于比较常见的一种循环依赖,还有就是更多的之间的相互依赖,比如A依赖B,B依赖C,C依赖A,类似于三角恋...

当然也有特殊的,自己的依赖自己

// 自己依赖自己
@Component
public class AService {// A中注入了A@Autowiredprivate AService aService;
}

关于上面service的Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化

我们如果用一个常人的思维去考虑,这肯定是做不到的啊,A需要B,B需要A,这不成死循环了,和死锁一个道理了,这就很尴尬了,该怎么解决呢,接着看下去

多种情况下的循环依赖

Spring的循环依赖也是可能出现多种情况的,比如构造器注入,setter注入等等,那什么情况下Spring可以解决循环依赖,什么情况下又不能解决呢

Spring解决循环依赖的前提条件就是:

1、出现循环依赖的bean必须是单例的

2、依赖注入的方式不能全是构造器注入

注意不能全是这几个字眼,这里需要强调一点的是,大家可能会看到很多关于Spring解决循环依赖的博客,其中只能解决setter注入的方式这种说法是错误的,只要不全是构造器注入Spring就可以解决

Spring bean的创建,其实本质上也会一个对象的创建,既然是对象,就要明白一个对象需要完成的对象包含两个部分:当前对象的实例化和对象属性的实例化,在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方法设置的,知道了这一点,可以更好的帮助大家去理解

上面说的第一点必须是单例的其实很好理解,你想啊,如果是多个实例,该引入哪个实例就不知道了,Spring框架就蒙圈了,大胆猜测一下,Spring以后可能会尝试解决这个问题,大概率是通过配置的方式来告诉该注入哪个

但是第二点呢,不能全是构造器注入呢,先看代码

@Component
public class AService {
// @Autowired
// private BService bService;public AService(BService bService) {}
}@Component
public class BService {// @Autowired
// private AService aService;public BService(AService aService){}
}

上面这个例子大家看得懂吧,别告诉我你看不懂,AService中的BService注入是通过构造器,反之也是通过构造器注入,这个时候Spring是无法解决循环依赖问题的,如果项目中出现两个这样的引用,在启动的时候就会直接抛出异常

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

解决循环依赖

首先呢,Spring解决循环依赖依靠的就是内部维护的三个Map,也就是咱们常说的三级缓存,不知道大家听过没有

之所以被叫做三级缓存,大概是因为注释上都是用Cache,而起的作用也类似一个缓存的作用

1、singletonObjects:这个是单例的容器池,缓存创建完成单例Bean的地方

2、singletonFactories:用来映射创建Bean的原始工厂

3、earlySingletonObjects:用来映射Bean的早起引用的,也就是说在这个Map中的Bean不是完整的,属于半成品,甚至还不能被称为Bean,只是一个Instance

后面的两个Map其实属于是过程中的消耗品,什么意思呢,就是创建Bean的时候,需要暂时存储在这里,过后完成之后就清除掉了

我们先来看一下这个创建过程,我把这个大致分了四个步骤,看着也比较清晰,大家也比较好理解:

1、A找B找不到:

创建AService的过程中发现需要BService,于是AService将去寻找BService,发现找不到,寻找的路径是一级缓存、二级缓存、三级缓存,于是AService把自己放到了三级缓存中

2、B找A找到了:

实例化BService,实例化过程中发现需要AService,于是也是按照上述路径去寻找,在三级缓存中找到了AService

3、B创建完成:

然后就把三级缓存中的AService拿出来,放到了二级缓存中,并删除三级缓存中的AService,此时BService可以成功引用,顺利初始化完毕,把自己放到了一级缓存中了(而此时BService中的AService依然是创建中的状态

4、A创建完成:

继续完善AService,此时再去寻找BService,拿出来直接引用就好了,把自己放入到一级缓存中,删除二级缓存,删除在创建的状态信息

问题的本质和two sum的本质有些类似,这个是leetcode上序号为1 的题目,问题的内容是:

给定nums = [2,7,11,15] , target = 9,那么要返回[0,1] ,因为2 + 7 = 9

这道题的解题思路就是通过Map,先去Map中找到需要的数字,没有就把当前数字放进去,有的话就直接拿到并一起返回

和三级缓存的本质类似,先去缓存中找到需要的Bean,找到了万事大吉,直接创建完成返回,找不到就把自己放进去

来一起简单的分析一波源码,不看太多,不用发愁

单例模式下,第一次获取Bean时由于Bean示例还未实例化,因此会先创建Bean然后放入缓存中,以后再次调用获取Bean方法将直接从缓存中获取,不再重新创建。

缓存添加过程主要发生在创建Bean也就是doCreateBean()过程中。

从代码中可以看到在这里将beanName放入三级缓存中,并且从二级缓存中移除。这里的addSingletonFactory发生在createBeanInstance之后,此时已经有了实例对象,但是还没创建完成便放入三级缓存当中。

在doCreateBean()方法执行完成之后,Bean实例创建完成,因此在第二个getSingleton()中的finally块中如果是新的单例对象则会调用addSingleton()方法

可以看到此时将加入一级缓存中,并且从二级、三级缓存中移除。

缓存的获取是通过getSingleton(String beanName)方法获取的,其源码如下:

在代码中,首先从一级缓存singletonObjects中获取Bean;如果获取不到并且获取的Bean被标记为正在创建中,则从二级缓存earlySingletonObjects中获取

如果二级缓存中依然获取不到Bean,并且允许从三级换从中获取Bean,则从三级缓存中获取Bean,此时如果获取到了Bean,则将该Bean从三级缓存中移除,然后添加进二级缓存(缓存升级),否则返回null。

最后问一个灵魂的问题,为什么不用二级缓存,而用三级缓存呢?

如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:

不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。

不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?

Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

吐血给女朋友讲解spring循环依赖相关推荐

  1. 【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)

    本次我们通过源码介绍ApplicationContext容器初始化流程,主要介绍容器内bean的实例化和初始化过程.ApplicationContext是Spring推出的先进Ioc容器,它继承了旧版 ...

  2. 终于有人把 Spring 循环依赖讲清楚了!

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

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

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

  4. spring循环依赖解决办法

    Spring循环依赖的解决办法!包含代码讲解!!! 大家面试相信遇到过这么一个问题. 面试官问:你知道spring的循环依赖吗,可以讲一下吗? 我:这个我熟啊,循环依赖就是spring构造一个bean ...

  5. 聊透Spring循环依赖

    本文聊一下和依赖注入密切相关,并且在实际开发中很常见,面试也很喜欢问的一个问题:Spring是怎么解决循环依赖的?  之前就被问过Spring是怎么解决循环依赖的问题,当时年少无知,对Spring源码 ...

  6. 最易懂Spring循环依赖

    前言 了解Spring循环依赖和三级缓存需要熟悉IOC.AOP流程. 工作中常用new语句创建对象,通过new方法创建的对象是没有属性填充的,而Spring创建对象时可指定对象的生命周期: 说Spri ...

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

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Mythsman 原文:https://blog.myths ...

  8. 烂大街的Spring循环依赖该如何回答?

    什么是循环依赖? 从字面上来理解就是A依赖B的同时B也依赖了A,就像上面这样,或者C依赖与自己本身.体现到代码层次就是这个样子 @Component public class A {// A中注入了B ...

  9. spring处理循环依赖时序图_spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖...

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

  10. Spring循环依赖源码剖析

    Spring循环依赖源码剖析 一.场景介绍 二.整理执行流程总结 三.源码分析 编写测试类 /*** 测试循环依赖*/@Testpublic void testCyclicDependence(){A ...

最新文章

  1. BCGSoft Demo示例展示:菜单示例集合(2/2)
  2. Spring注解@Value获取属性文件值且解决在controller无法获取到值的问题
  3. 解决:Unable to open debugger port (127.0.0.1:55017): java.net.SocketException “Socket closed“
  4. java实现鼠标截图,java实现屏幕截图(附源码)
  5. 【es】ES RestHighLevelClient 请求报错:Connection reset by peer
  6. cnn加工是什么意思_天秤座R-CNN:全面平衡的目标检测器
  7. 剑指offer面试题55 - II. 平衡二叉树(后序遍历)(剪枝)
  8. IE无法打开新链接的问题
  9. ubuntu风扇转速控制与系统状态监控
  10. scratch动态三角形拖动/自制素材/少儿编程scratch教研教案课件课程素材脚本
  11. Linux之nmap扫描多网段
  12. c语言遥控器程序设计,基于51单片机的红外线遥控器解码程序设计
  13. 实习期间的一些思考整理(4)2018.4.14~4.16
  14. ABAP BAPI 复制标准项目模板实现项目立项
  15. Flex 弹性布局(上)
  16. 京东首页案例(流式布局)
  17. A. Sequence with Digits
  18. 《网络安全法》及其法律体系介绍
  19. C语言做线性分析,C语言版的线性回归分析函数
  20. Programming Exercise 6:Support Vector Machines

热门文章

  1. 点云配准1-ICP算法 原理代码实现
  2. 苹果手机无法连接wifi_苹果11pro连接wifi信号差
  3. 关于stm32单片机的通讯方式
  4. Linux下软连接(softlink)和硬连接(hardlink)的区别
  5. 主流前端框架实现原理
  6. win10下LPT并口打印失败和POS打印机的钱箱不能打开,win10的坑
  7. macos 升级ruby
  8. C++自定义列表实现贪吃蛇
  9. java支付宝扫码支付,简单生成二维码方法
  10. 支付宝PC(二维码扫码)支付(Java开发)完整版