前言
我们前面说了几遍Spring的文章,了解了比较核心的知识点IOC和AOP,还有就是事务传播这种,不知道大家听过Spring的循环依赖这个问题吗,而且这个问题是面试经常问的,属于Spring的一个比较重要的话题,也比较典型,比较考验一个人对Spring的研究程度,也算是Spring的一个高阶问题之一了
有的小伙伴内心对于Spring的看法可能是,感觉自己懂了,但是让自己来叙述给一个小白的话,可能部分人就讲不出来了,这一篇呢,主要就是帮你解决这个痛点,让你彻底搞定Spring的循环依赖,也让你下次不再担心,而是张口就来
面试前看一看,offer轻松拿一拿,你还不关注等啥呢,月薪50K的薪资等着你呢,到时候如果你纠结选择哪个offer,可以来把你的喜讯分享给我的嘞
循环依赖问题,本文会通过三个方面来简单介绍
1、什么是Spring的循环依赖
2、多种情况下的循环依赖
3、Spring如何解决循环依赖
了解Spring循环依赖
什么是循环依赖
- @Component
- public class AService {
- // AService中注入了BService
- @Autowired
- private BService bService;
- }
- @Component
- public class BService {
- // BService中也注入了AService
- @Autowired
- private AService aService;
- }
这是属于比较常见的一种循环依赖,还有就是更多的之间的相互依赖,比如A依赖B,B依赖C,C依赖A,类似于三角恋...
当然也有特殊的,自己的依赖自己
- // 自己依赖自己
- @Component
- public 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以后可能会尝试解决这个问题,大概率是通过配置的方式来告诉该注入哪个
但是第二点呢,不能全是构造器注入呢,先看代码
- @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
本文转载自微信公众号「Java贼船」