添加微信BGM7756 可免费领取面试资料复制代码
Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是由于源码中为了解决循环依赖作了不少处理,另一方面是由于面试的时候,若是问到Spring中比较高阶的问题,那么循环依赖一定逃不掉。若是你回答得好,那么这就是你的必杀技,反正,那就是面试官的必杀技,这也是取这个标题的缘由,固然,本文的目的是为了让你在以后的全部面试中能多一个必杀技,专门用来绝杀面试官!面试
本文的核心思想就是,缓存
当面试官问:bash
“请讲一讲Spring中的循环依赖。”的时候,微信
咱们到底该怎么回答?ide
主要分下面几点源码分析
OK,铺垫已经作完了,接下来咱们开始正文post
什么是循环依赖?测试
从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样ui
体现到代码层次就是这个样子this
固然,这是最多见的一种循环依赖,比较特殊的还有
虽然体现形式不同,可是实际上都是同一个问题----->循环依赖
在回答这个问题以前首先要明确一点,Spring解决循环依赖是有前置条件的
出现循环依赖的Bean必需要是单例
依赖注入的方式不能全是构造器注入的方式(不少博客上说,只能解决setter方法的循环依赖,这是错误的)
其中第一点应该很好理解,第二点:不能全是构造器注入是什么意思呢?咱们仍是用代码说话
在上面的例子中,A中注入B的方式是经过构造器,B中注入A的方式也是经过构造器,这个时候循环依赖是没法被解决,若是你的项目中有两个这样相互依赖的Bean,在启动时就会报出如下错误:
为了测试循环依赖的解决状况跟注入方式的关系,咱们作以下四种状况的测试
具体的测试代码跟简单,我就不放了。从上面的测试结果咱们能够看到,不是只有在setter方法注入的状况下循环依赖才能被解决,即便存在构造器注入的场景下,循环依赖依然被能够被正常处理掉。
那么究竟是为何呢?Spring究竟是怎么处理的循环依赖呢?不要急,咱们接着往下看
关于循环依赖的解决方式应该要分两种状况来讨论
结合了AOP的循环依赖
简单的循环依赖(没有AOP)
咱们先来分析一个最简单的例子,就是上面提到的那个demo
经过上文咱们已经知道了这种状况下的循环依赖是可以被解决的,那么具体的流程是什么呢?咱们一步步分析
首先,咱们要知道Spring在建立Bean的时候默认是按照天然排序来进行建立的,因此第一步Spring会去建立A。
与此同时,咱们应该知道,Spring在建立Bean的过程当中分为三步
实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法
属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法
初始化,对应方法:AbstractAutowireCapableBeanFactory的initializeBean
这些方法在以前源码分析的文章中都作过详细的解读了,若是你以前没看过个人文章,那么你只须要知道
实例化,简单理解就是new了一个对象
属性注入,为实例化中new出来的对象填充属性
初始化,执行aware接口中的方法,初始化方法,完成AOP代理
基于上面的知识,咱们开始解读整个循环依赖处理的过程,整个流程应该是以A的建立为起点,前文也说了,第一步就是建立A嘛!
建立A的过程实际上就是调用getBean方法,这个方法有两层含义
建立一个新的Bean
从缓存中获取到已经被建立的对象
咱们如今分析的是第一层含义,由于这个时候缓存中尚未A嘛!
首先调用getSingleton(a)方法,这个方法又会调用getSingleton(beanName, true),在上图中我省略了这一步
getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,整个缓存分为三级
singletonObjects,一级缓存,存储的是全部建立好了的单例Bean
earlySingletonObjects,完成实例化,可是还未进行属性注入及初始化的对象
singletonFactories,提早暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象
由于A是第一次被建立,因此无论哪一个缓存中必然都是没有的,所以会进入getSingleton的另一个重载方法getSingleton(beanName, singletonFactory)。
调用getSingleton(beanName, singletonFactory)
这个方法就是用来建立Bean的,其源码以下:
上面的代码咱们主要抓住一点,经过createBean方法返回的Bean最终被放到了一级缓存,也就是单例池中。
那么到这里咱们能够得出一个结论:一级缓存中存储的是已经彻底建立好了的单例Bean
以下图所示:
在完成Bean的实例化后,属性注入以前Spring将Bean包装成一个工厂添加进了三级缓存中,对应源码以下:
这里只是添加了一个工厂,经过这个工厂(ObjectFactory)的getObject方法能够获得一个对象,而这个对象实际上就是经过getEarlyBeanReference这个方法建立的。那么,何时会去调用这个工厂的getObject方法呢?这个时候就要到建立B的流程了。
当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring又会去getBean(b),而后反射调用setter方法完成属性注入。
由于B须要注入A,因此在建立B的时候,又会去调用getBean(a),这个时候就又回到以前的流程了,可是不一样的是,以前的getBean是为了建立Bean,而此时再调用getBean不是为了建立了,而是要从缓存中获取,由于以前A在实例化后已经将其放入了三级缓存singletonFactories中,因此此时getBean(a)的流程就是这样子了
从这里咱们能够看出,注入到B中的A是经过getEarlyBeanReference方法提早暴露出去的一个对象,还不是一个完整的Bean,那么getEarlyBeanReference到底干了啥了,咱们看下它的源码
它实际上就是调用了后置处理器的getEarlyBeanReference,而真正实现了这个方法的后置处理器只有一个,就是经过@EnableAspectJAutoProxy注解导入的AnnotationAwareAspectJAutoProxyCreator。也就是说若是在不考虑AOP的状况下,上面的代码等价于:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; return exposedObject;}复制代码
也就是说这个工厂啥都没干,直接将实例化阶段建立的对象返回了!因此说在不考虑AOP的状况下三级缓存有用吗?讲道理,真的没什么用,我直接将这个对象放到二级缓存中不是一点问题都没有吗?若是你说它提升了效率,那你告诉我提升的效率在哪?
那么三级缓存到底有什么做用呢?不要急,咱们先把整个流程走完,在下文结合AOP分析循环依赖的时候你就能体会到三级缓存的做用!
到这里不知道小伙伴们会不会有疑问,B中提早注入了一个没有通过初始化的A类型对象不会有问题吗?
答:不会
这个时候咱们须要将整个建立A这个Bean的流程走完,以下图:
从上图中咱们能够看到,虽然在建立B时会提早给B注入了一个还未初始化的A对象,可是在建立A的流程中一直使用的是注入到B中的A对象的引用,以后会根据这个引用对A进行初始化,因此这是没有问题的。
以前咱们已经说过了,在普通的循环依赖的状况下,三级缓存没有任何做用。三级缓存实际上跟Spring中的AOP相关,咱们再来看一看getEarlyBeanReference的代码:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject;}复制代码
若是在开启AOP的状况下,那么就是调用到AnnotationAwareAspectJAutoProxyCreator的getEarlyBeanReference方法,对应的源码以下:
回到上面的例子,咱们对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段建立的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段建立后的对象。
看到这个图你可能会产生下面这些疑问
在给B注入的时候为何要注入一个代理对象?
答:当咱们对A进行了AOP代理时,说明咱们但愿从容器中获取到的就是A代理后的对象而不是A自己,所以把A看成依赖进行注入时也要注入它的代理对象
明明初始化的时候是A对象,那么Spring是在哪里将代理对象放入到容器中的呢?
在完成初始化后,Spring又调用了一次getSingleton方法,这一次传入的参数又不同了,false能够理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,因此这里的这个getSingleton方法作的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean能够认为是一定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的Bean,例如增长一个后置处理器:
@Componentpublic class MyPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("a")) { return new A(); } return bean; }}复制代码
不过,请不要作这种骚操做,徒增烦恼!
初始化的时候是对A对象自己进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?
答:不会,这是由于不论是cglib代理仍是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化至关于代理对象自身也完成了初始化
三级缓存为何要使用工厂而不是直接使用引用?换而言之,为何须要这个三级缓存,直接经过二级缓存暴露一个引用不行吗?
答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提早生成代理对象,不然只会建立一个工厂并将其放入到三级缓存中,可是不会去经过这个工厂去真正建立对象
咱们思考一种简单的状况,就以单首创建A为例,假设AB之间如今没有依赖关系,可是A被代理了,这个时候当A完成实例化后仍是会进入下面这段代码:
// A是单例的,mbd.isSingleton()条件知足// allowCircularReferences:这个变量表明是否容许循环依赖,默认是开启的,条件也知足// isSingletonCurrentlyInCreation:正在在建立A,也知足// 因此earlySingletonExposure=trueboolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));// 仍是会进入到这段代码中if (earlySingletonExposure) { // 仍是会经过三级缓存提早暴露一个工厂对象 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}复制代码
看到了吧,即便没有循环依赖,也会将其添加到三级缓存中,并且是不得不添加到三级缓存中,由于到目前为止Spring也不能肯定这个Bean有没有跟别的Bean出现循环依赖。
假设咱们在这里直接使用二级缓存的话,那么意味着全部的Bean在这一步都要完成AOP代理。这样作有必要吗?
不只没有必要,并且违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期自己就是经过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。若是出现了循环依赖,那没有办法,只有给Bean先建立代理,可是没有出现循环依赖的状况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
如今咱们已经知道了三级缓存的真正做用,可是这个答案可能还没法说服你,因此咱们再最后总结分析一波,三级缓存真的提升了效率了吗?分为两点讨论:
没有进行AOP的Bean间的循环依赖
从上文分析能够看出,这种状况下三级缓存根本没用!因此不会存在什么提升了效率的说法
进行了AOP的Bean间的循环依赖
就以咱们上的A、B为例,其中A被AOP代理,咱们先分析下使用了三级缓存的状况下,A、B的建立流程
假设不使用三级缓存,直接在二级缓存中
上面两个流程的惟一区别在于为A对象建立代理的时机不一样,在使用了三级缓存的状况下为A建立代理的时机是在B中须要注入A的时候,而不使用三级缓存的话在A实例化后就须要立刻为A建立代理而后放入到二级缓存中去。对于整个A、B的建立过程而言,消耗的时间是同样的
综上,不论是哪一种状况,三级缓存提升了效率这种说法都是错误的!
面试官:”Spring是如何解决的循环依赖?“
答:Spring经过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去建立一个对象工厂,并添加到三级缓存中,若是A被AOP代理,那么经过这个工厂获取到的就是A代理后的对象,若是A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去建立B,同时B又依赖了A,因此建立B的同时又会去调用getBean(a)来获取须要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,获得这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B建立完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
面试官:”为何要使用三级缓存呢?二级缓存能解决循环依赖吗?“
答:若是要使用二级缓存解决循环依赖,意味着全部Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是经过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。