本系列文章:html
据说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译
程序员配置类为何要添加@Configuration注解?
spring谈谈Spring中的对象跟Bean,你知道Spring怎么建立对象的吗?
缓存Spring中AOP相关的API及源码解析,原来AOP是这样子的
编辑器你知道Spring是怎么将AOP应用到Bean的生命周期中的吗?
ide推荐阅读:源码分析
本系列文章将会带你一行行的将Spring的源码吃透,推荐阅读的文章是阅读源码的基础!
”
Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是由于源码中为了解决循环依赖作了不少处理,另一方面是由于面试的时候,若是问到Spring中比较高阶的问题,那么循环依赖一定逃不掉。若是你回答得好,那么这就是你的必杀技,反正,那就是面试官的必杀技,这也是取这个标题的缘由,固然,本文的目的是为了让你在以后的全部面试中能多一个必杀技,专门用来绝杀面试官!
本文的核心思想就是,
当面试官问:
“请讲一讲Spring中的循环依赖。”的时候,
咱们到底该怎么回答?
主要分下面几点
同时本文但愿纠正几个目前业界内常常出现的几个关于循环依赖的错误的说法
OK,铺垫已经作完了,接下来咱们开始正文
从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样
体现到代码层次就是这个样子
@Component
public class A { // A中注入了B @Autowired private B b; } @Component public class B { // B中也注入了A @Autowired private A a; } 复制代码
固然,这是最多见的一种循环依赖,比较特殊的还有
// 本身依赖本身
@Component public class A { // A中注入了A @Autowired private A a; } 复制代码
虽然体现形式不同,可是实际上都是同一个问题----->循环依赖
在回答这个问题以前首先要明确一点,Spring解决循环依赖是有前置条件的
其中第一点应该很好理解,第二点:不能全是构造器注入是什么意思呢?咱们仍是用代码说话
@Component
public class A { // @Autowired // private B b; public A(B b) { } } @Component public class B { // @Autowired // private A a; public B(A a){ } } 复制代码
在上面的例子中,A中注入B的方式是经过构造器,B中注入A的方式也是经过构造器,这个时候循环依赖是没法被解决,若是你的项目中有两个这样相互依赖的Bean,在启动时就会报出如下错误:
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?
复制代码
为了测试循环依赖的解决状况跟注入方式的关系,咱们作以下四种状况的测试
依赖状况 | 依赖注入方式 | 循环依赖是否被解决 |
---|---|---|
AB相互依赖(循环依赖) | 均采用setter方法注入 | 是 |
AB相互依赖(循环依赖) | 均采用构造器注入 | 否 |
AB相互依赖(循环依赖) | A中注入B的方式为setter方法,B中注入A的方式为构造器 | 是 |
AB相互依赖(循环依赖) | B中注入A的方式为setter方法,A中注入B的方式为构造器 | 否 |
具体的测试代码跟简单,我就不放了。从上面的测试结果咱们能够看到,不是只有在setter方法注入的状况下循环依赖才能被解决,即便存在构造器注入的场景下,循环依赖依然被能够被正常处理掉。
那么究竟是为何呢?Spring究竟是怎么处理的循环依赖呢?不要急,咱们接着往下看
关于循环依赖的解决方式应该要分两种状况来讨论
咱们先来分析一个最简单的例子,就是上面提到的那个demo
@Component
public class A { // A中注入了B @Autowired private B b; } @Component public class B { // B中也注入了A @Autowired private A a; } 复制代码
经过上文咱们已经知道了这种状况下的循环依赖是可以被解决的,那么具体的流程是什么呢?咱们一步步分析
首先,咱们要知道Spring在建立Bean的时候默认是按照天然排序来进行建立的,因此第一步Spring会去建立A。
与此同时,咱们应该知道,Spring在建立Bean的过程当中分为三步
实例化,对应方法:AbstractAutowireCapableBeanFactory
中的createBeanInstance
方法
属性注入,对应方法:AbstractAutowireCapableBeanFactory
的populateBean
方法
初始化,对应方法:AbstractAutowireCapableBeanFactory
的initializeBean
这些方法在以前源码分析的文章中都作过详细的解读了,若是你以前没看过个人文章,那么你只须要知道
AOP
代理
基于上面的知识,咱们开始解读整个循环依赖处理的过程,整个流程应该是以A的建立为起点,前文也说了,第一步就是建立A嘛!
建立A的过程实际上就是调用getBean
方法,这个方法有两层含义
咱们如今分析的是第一层含义,由于这个时候缓存中尚未A嘛!
首先调用getSingleton(a)
方法,这个方法又会调用getSingleton(beanName, true)
,在上图中我省略了这一步
public Object getSingleton(String beanName) {
return getSingleton(beanName, true); } 复制代码
getSingleton(beanName, true)
这个方法实际上就是到缓存中尝试去获取Bean,整个缓存分为三级
singletonObjects
,一级缓存,存储的是全部建立好了的单例Bean
earlySingletonObjects
,完成实例化,可是还未进行属性注入及初始化的对象
singletonFactories
,提早暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象
由于A是第一次被建立,因此无论哪一个缓存中必然都是没有的,所以会进入getSingleton
的另一个重载方法getSingleton(beanName, singletonFactory)
。
这个方法就是用来建立Bean的,其源码以下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // .... // 省略异常处理及日志 // .... // 在单例对象建立前先作一个标记 // 将beanName放入到singletonsCurrentlyInCreation这个集合中 // 标志着这个单例Bean正在建立 // 若是同一个单例Bean屡次被建立,这里会抛出异常 beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { // 上游传入的lambda在这里会被执行,调用createBean方法建立一个Bean后返回 singletonObject = singletonFactory.getObject(); newSingleton = true; } // ... // 省略catch异常处理 // ... finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } // 建立完成后将对应的beanName从singletonsCurrentlyInCreation移除 afterSingletonCreation(beanName); } if (newSingleton) { // 添加到一级缓存singletonObjects中 addSingleton(beanName, singletonObject); } } return singletonObject; } } 复制代码
上面的代码咱们主要抓住一点,经过createBean
方法返回的Bean最终被放到了一级缓存,也就是单例池中。
那么到这里咱们能够得出一个结论:一级缓存中存储的是已经彻底建立好了的单例Bean
以下图所示:
在完成Bean的实例化后,属性注入以前Spring将Bean包装成一个工厂添加进了三级缓存中,对应源码以下:
// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 添加到三级缓存中 this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } 复制代码
这里只是添加了一个工厂,经过这个工厂(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
到底干了啥了,咱们看下它的源码
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; } 复制代码
它实际上就是调用了后置处理器的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
方法,对应的源码以下:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); // 若是须要代理,返回一个代理对象,不须要代理,直接返回当前传入的这个bean对象 return wrapIfNecessary(bean, beanName, cacheKey); } 复制代码
回到上面的例子,咱们对A进行了AOP
代理的话,那么此时getEarlyBeanReference
将返回一个代理后的对象,而不是实例化阶段建立的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段建立后的对象。
看到这个图你可能会产生下面这些疑问
答:当咱们对A进行了AOP
代理时,说明咱们但愿从容器中获取到的就是A代理后的对象而不是A自己,所以把A看成依赖进行注入时也要注入它的代理对象
在完成初始化后,Spring又调用了一次getSingleton
方法,这一次传入的参数又不同了,false能够理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,因此这里的这个getSingleton
方法作的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean
能够认为是一定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的Bean,例如增长一个后置处理器:
@Component
public class MyPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("a")) { return new A(); } return bean; } } 复制代码
不过,请不要作这种骚操做,徒增烦恼!
答:不会,这是由于不论是cglib
代理仍是jdk
动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化至关于代理对象自身也完成了初始化
答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提早生成代理对象,不然只会建立一个工厂并将其放入到三级缓存中,可是不会去经过这个工厂去真正建立对象
咱们思考一种简单的状况,就以单首创建A为例,假设AB之间如今没有依赖关系,可是A被代理了,这个时候当A完成实例化后仍是会进入下面这段代码:
// A是单例的,mbd.isSingleton()条件知足
// allowCircularReferences:这个变量表明是否容许循环依赖,默认是开启的,条件也知足 // isSingletonCurrentlyInCreation:正在在建立A,也知足 // 因此earlySingletonExposure=true boolean 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代理。
为何在下表中的第三种状况的循环依赖能被解决,而第四种状况不能被解决呢?
提示:Spring在建立Bean时默认会根据天然排序进行建立,因此A会先于B进行建立
依赖状况 | 依赖注入方式 | 循环依赖是否被解决 |
---|---|---|
AB相互依赖(循环依赖) | 均采用setter方法注入 | 是 |
AB相互依赖(循环依赖) | 均采用构造器注入 | 否 |
AB相互依赖(循环依赖) | A中注入B的方式为setter方法,B中注入A的方式为构造器 | 是 |
AB相互依赖(循环依赖) | B中注入A的方式为setter方法,A中注入B的方式为构造器 | 否 |
若是本文对你由帮助的话,记得点个赞吧!也欢迎关注个人公众号,微信搜索:程序员DMZ,或者扫描下方二维码,跟着我一块儿认认真真学Java,踏踏实实作一个coder。
我叫DMZ,一个在学习路上匍匐前行的小菜鸟! 码字不易,本文要是对你有帮助的话,记得点个赞吧!