聊透Spring循环依赖

    本文聊一下和依赖注入密切相关,并且在实际开发中很常见,面试也很喜欢问的一个问题:Spring是怎么解决循环依赖的?

 之前就被问过Spring是怎么解决循环依赖的问题,当时年少无知,对Spring源码知之甚少,也没有做足功课。只是支支吾吾的说到:好像是通过多级缓存解决的吧。面试官看我实在窘迫,也没有深问,算是逃过一劫,可在心里总是个羁绊。后来随着对Spring源码的深入阅读和理解,慢慢清楚了Spring解决循环依赖的方式。

 后来也一直在想,为什么这么多年了,面试还是喜欢问Spring是怎么解决循环依赖的这种问题呢?除了工作中比较常见,究其原因,可能跟需要你对Spring bean的生命周期和AOP有所了解才能回答好这个问题有关吧,而这两者也能直接反应出你对Spring框架的理解程度,也许这就是面试喜欢问这道题深层的含义吧。

 好了,我们不猜面试官的心里了。既然循环依赖这么实用,那本章节我们就一起聊聊Spring循环依赖吧,我们主要分为以下几个部分进行讨论:

什么是循环依赖? Spring循环依赖的解决之道 一定需要三级缓存来解决循环依赖吗 在哪些场景下是不支持循环依赖的?有解决方法吗

1. 什么是循环依赖

 在探讨Spring循环依赖的解决方式以前,我们先来回忆一下什么是循环依赖:A依赖B的同时,B也依赖了A,就构成了循环依赖。依赖关系如下图所示:

聊透Spring循环依赖_第1张图片

​ 体现到代码中为:

@Component public class A{ // 依赖B @Autowired private B b; public B getB() { return b; } } @Component public class B { // 依赖A @Autowired private A a; public A getA() { return a; } } //比较特殊的循环依赖 @Component public class A{ // 依赖B @Autowired private A a; } 复制代码

 当然也有一些链路较长,隐藏的比较深的循环依赖,比如:A -> B -> C -> D -> ... -> B这种,无论如何,都要形成一个环才能被称为循环依赖。

 Spring的循环依赖过程,跟bean的生命周期密切相关,在实例化bean的时候,会完成依赖注入。所以就会出现:实例化A -> 属性填充注入B -> B还没有实例化,需要先进行实例化B(A等待) -> 实例化B -> 注入A -> A实例化未完成,无法注入 -> 实例化B失败 -> 实例化A失败。这个问题类似于我们常见的死锁,颇有点有点窈窕淑女,求之不得的味道。

 有没有办法解决呢?当然有,英文Spring就解决了嘛。就是在实例化过程中,提前把bean暴露出来,虽然此时还是个半成品(属性未填充),但是我们允许先注入,这样确实能解决问题。我们梳理一下: 实例化A -> 暴露a对象(半成品)-> 属性填充注入B -> B还没有实例化,需要先进行实例化B(A等待) -> 实例化B -> 注入A(半成品) -> 实例化B成功 -> 实例化A成功。通过提前把半成品的对象暴露出来,支持别的bean注入,确实可以解决循环依赖的问题,实际上,Spring也确实是这么做的。沿着这个思路,下文我们详细分析Spring的处理方式。

 什么,你问b对象中注入的a属性还是半成品怎么办?大兄弟,难道你忘了java对象的传递,本质是传递引用的吗,内存中是同一个对象啊,对象的修改是相互影响的啊。有朝一日,对象a完整了,b对象中的a属性,肯定也码生完整了。

2. Spring循环依赖解决之道

2.1 Spring通过三级缓存解决依赖注入

  Spring究竟是不是通过上述我们说的方式解决的呢,其实思路是一致的,只是Spring处理的更加严谨。Spring是通过三级缓存来解决循环依赖的,提前暴露的对象存放在三级缓存中,二级缓存存放过渡bean,一级缓存存放最终形态的bean。下面我们还是用A -> B -> A的场景,看一下Spring是如何解决循环依赖的。我们按照过程一步步来分析,首先是实例化A的过程:

聊透Spring循环依赖_第2张图片

​ 此时执行到属性填充环节,需要注入b,因为Spring管理的bean默认是单例的,为防止重复创建,Spring会先去容器中查找b,如果查找不到,再进行创建。此时容器中是没有b的,所以需要先实例化b,流程和实例化a一致。

聊透Spring循环依赖_第3张图片

 此时B也执行到属性填充的环节了,有意思的地方开始了,此时又需要注入a,此时还是会先去容器中查找a,此时的a虽然没在单例池中,但是因为在创建中,并且也在三级缓存中了。所以此时获取a的流程就发生了变化:不再是直接创建,而是会从三级缓存中获取a,三级缓存存放的并不是bean对象,而是生成bean的ObjectFactory,在获取时会经过AbstractAutowireCapableBeanFactory#getEarlyBeanReference()的处理,才能获取到bean,然后放入二级缓存中,同时返回a进行依赖注入。

聊透Spring循环依赖_第4张图片

​这里小伙伴可能有疑问:为什么三级缓存中存放的是ObjectFactory而不是bean呢? 而AbstractAutowireCapableBeanFactory#getEarlyBeanReference()的处理又起什么作用,为什么三级缓存要经过它的处理之后,才能放入二级缓存呢?这些问题请小伙伴们稍安勿躁,后面我们会详细说明的。

 截止到目

你可能感兴趣的:(spring,面试,java,程序人生,后端)