吐血给女朋友讲解spring循环依赖

共 4450字,需浏览 9分钟

 ·

2021-10-25 00:21



前言


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

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

我当年面试的时候,也是遇到不少面试官问过这个问题,每次都是绝杀面试官,想了解更多能够绝杀面试官的杀手锏,点赞关注不迷路


面试前看一看,offer轻松拿一拿,你还不关注等啥呢,月薪50K的薪资等着你呢,到时候如果你纠结选择哪个offer,可以来把你的喜讯分享给我的嘞

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

1、什么是Spring的循环依赖
2、多种情况下的循环依赖
3、Spring如何解决循环依赖

了解Spring循环依赖


什么是循环依赖


@Componentpublic class AService {    // AService中注入了BService @Autowired private BService bService;}
@Componentpublic class BService {    // BService中也注入了AService @Autowired private AService aService;}
这是属于比较常见的一种循环依赖,还有就是更多的之间的相互依赖,比如A依赖B,B依赖C,C依赖A,类似于三角恋...

当然也有特殊的,自己的依赖自己
// 自己依赖自己@Componentpublic class AService {    // A中注入了A @Autowired private AService aService;}

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

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



多种情况下的循环依赖




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

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

1、出现循环依赖的bean必须是单例的
2、依赖注入的方式不能全是构造器注入

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

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

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

但是第二点呢,不能全是构造器注入呢,先看代码
@Componentpublic class AService {// @Autowired// private BService bService; public AService(BService bService) {
}}

@Componentpublic 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 earlySingletonObjects。


求赞


Captain希望有一天能够靠写作养活自己,现在还在磨练,这个时间可能会持续很久,但是,请看我漂亮的坚持


感谢大家能够做我最初的读者和传播者,请大家相信,只要你给我一份爱,我终究会还你们一页情的。


船长会持续更新技术文章,和生活中的暴躁文章,欢迎大家关注【Java贼船】,成为船长的学习小伙伴,和船长一起乘千里风、破万里浪


哦对了,后续所有的远程文章都会更新到这里


https://github.com/DayuMM2021/Java



浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报