跳槽那些事儿@面试问你Spring如何解决循环依赖的时候,不要一脸懵逼了!


来自:开源中国 , 作者:爱宝贝丶
链接:https://my.oschina.net/zhangxufeng/blog/3096394
在关于Spring的面试中 , 我们经常会被问到一个问题 , 就是Spring是如何解决循环依赖的问题的 。 这个问题算是关于Spring的一个高频面试题 , 因为如果不刻意研读 , 相信即使读过源码 , 面试者也不一定能够一下子思考出个中奥秘 。 本文主要针对这个问题 , 从源码的角度对其实现原理进行讲解 。
1、过程演示 关于Spring bean的创建 , 其本质上还是一个对象的创建 , 既然是对象 , 读者朋友一定要明白一点就是 , 一个完整的对象包含两部分:当前对象实例化和对象属性的实例化 。 在Spring中 , 对象的实例化是通过反射实现的 , 而对象的属性则是在对象实例化之后通过一定的方式设置的 。 这个过程可以按照如下方式进行理解:
跳槽那些事儿@面试问你Spring如何解决循环依赖的时候,不要一脸懵逼了!
本文插图

理解这一个点之后 , 对于循环依赖的理解就已经帮助一大步了 , 我们这里以两个类A和B为例进行讲解 , 如下是A和B的声明:
@Componentpublic class A {private B b;public void setB(B b) {this.b = b;}}@Componentpublic class B {private A a;public void setA(A a) {this.a = a;}} 可以看到 , 这里A和B中各自都以对方为自己的全局属性 。 这里首先需要说明的一点是 , Spring实例化bean是通过ApplicationContext.getBean()方法来进行的 。 如果要获取的对象依赖了另一个对象 , 那么其首先会创建当前对象 , 然后通过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象 , 最后将获取到的对象注入到当前对象中 。
这里我们以上面的首先初始化A对象实例为例进行讲解 。 首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例 , 由于Spring容器中还没有A对象实例 , 因而其会创建一个A对象 , 然后发现其依赖了B对象 , 因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例 , 但是Spring容器中此时也没有B对象的实例 , 因而其还是会先创建一个B对象的实例 。 读者需要注意这个时间点 , 此时A对象和B对象都已经创建了 , 并且保存在Spring容器中了 , 只不过A对象的属性b和B对象的属性a都还没有设置进去 。 在前面Spring创建B对象之后 , Spring发现B对象依赖了属性A , 因而此时还是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例 , 因为Spring中已经有一个A对象的实例 , 虽然只是半成品(其属性b还未初始化) , 但其也还是目标bean , 因而会将该A对象的实例返回 。 此时 , B对象的属性a就设置进去了 , 然后还是ApplicationContext.getBean()方法递归的返回 , 也就是将B对象的实例返回 , 此时就会将该实例设置到A对象的属性b中 。 这个时候 , 注意A对象的属性b和B对象的属性a都已经设置了目标对象的实例了 。
读者朋友可能会比较疑惑的是 , 前面在为对象B设置属性a的时候 , 这个A类型属性还是个半成品 。 但是需要注意的是 , 这个A是一个引用 , 其本质上还是最开始就实例化的A对象 。 而在上面这个递归过程的最后 , Spring将获取到的B对象实例设置到了A对象的属性b中了 , 这里的A对象其实和前面设置到实例B中的半成品A对象是同一个对象 , 其引用地址是同一个 , 这里为A对象的b属性设置了值 , 其实也就是为那个半成品的a属性设置了值 。 下面我们通过一个流程图来对这个过程进行讲解:
跳槽那些事儿@面试问你Spring如何解决循环依赖的时候,不要一脸懵逼了!
本文插图

图中getBean()表示调用Spring的ApplicationContext.getBean()方法 , 而该方法中的参数 , 则表示我们要尝试获取的目标对象 。 图中的黑色箭头表示一开始的方法调用走向 , 走到最后 , 返回了Spring中缓存的A对象之后 , 表示递归调用返回了 , 此时使用绿色的箭头表示 。 从图中我们可以很清楚的看到 , B对象的a属性是在第三步中注入的半成品A对象 , 而A对象的b属性是在第二步中注入的成品B对象 , 此时半成品的A对象也就变成了成品的A对象 , 因为其属性已经设置完成了 。


推荐阅读