有两个业务代码分别是 A、B,并且还存在在相互引用,这样就形成了经典的循环依赖,而且这种循环依赖 Spring 不会出现问题,那么 Spring 是怎么解决的呢?让我们追溯下源码(目前分析的是单实例场景)
示例代码如下:
@Service
publci class A {
@Autowired private B b;
}
@Service
publci class B {
@Autowired private A a;
}
要分析怎么解决循环的问题,就得从 Spring 调用开始 getBean() 方法实例化 bean 的流程开始了,在 getBean() 方法中有四个非常重要的步骤如下:
假设第一个调用 getBean() 方法的是 A ,它必定要经过上面四个步骤。
A 首先过去查看123级缓存中是否有值?答案肯定是没有的,因为 A 还没有实例化过,正准备去实例化的路上呢。查找123级缓存的源码如下:
A 从缓存中没有找到,A 就会去调用 createBeanInstance() 方法创建 bean 对象(此时是 A 的半成品原始 bean,因为还没有属性赋值),源码如下:
此时 A 的半成品的原始 bean 已经创建好了,就要开始将实例化的 bean 保存到三级缓存中,源码如下:
此时这里有可能保存的是一个 Proxy、Cglib 代理对象,因为你要注意到,这里 key 对应的 value 值可不是一个简单的 bean,而是一个可以扩展的方法体,这里可以由你自己或者 Spirng 自己返回很多乱七八糟的东西,源码如下:
更多代理这方面的东西这里不展开细说,咱这里主要讲下单例 bean 的循环依赖 Spring 是怎么解决的。想要看可以参考我另一篇文章有说到。
回到正题来,这里已经把 A 的半成品原始 bean 存放到了三级缓存中,然后呢要干什么?是不是得要去给 A 的 bean 填充属性,源码如下:
注意此时 @Autowired 注入的是 B,那么就是要给 B 进行属性填充,然后 B 又会触发 getBean() 操作,此时注意哦,是 B 过来执行 getBean() 的流程,A 现在卡在了给 B 属性赋值的地方,等给属性 B 赋值完之后还有回来这个地方继续往下执行代码。
那么 B 又会经历过前面提到过的 getBean() 的四个核心步骤:
B 又过去查看123级缓存中是否有值?答案肯定是没有的,因为 B 还没有实例化过,现在正准备去实例化的路上呢。查找123级缓存的源码如下所示:
B 从缓存中也没有找到数据,B 也会去调用 createBeanInstance() 方法创建 bean 对象(此时是 B 的半成品原始 bean,因为还没有属性赋值),源码如下:
此时 B 的半成品的原始 bean 已经创建好了,就要开始将实例化的 bean 保存到三级缓存中,源码如下:
这里已经把 B 的半成品原始 bean 也存放到了三级缓存中,然后呢要干什么?是不是得要去给 B 的 bean 填充属性,源码如下:
注意此时 @Autowired 注入的是 A,那么就是要给 A 进行属性填充,然后 A 又会触发 getBean() 操作,此时注意哦,是 A 过来执行 getBean() 的流程,B 现在卡在了给 A 属性赋值的地方,等给属性 A 赋值完之后还有回来这个地方继续往下执行代码。
有没有注意到此时现在 Sping 的缓存中已经保留了两个半成品的原始 bean 对象,就是 A、B,都是保存在了三级缓存中
而且 A 第一次已经调用过一次 getBean() 方法了,第一次的调用还卡在给 B 属性赋值的地方,然后现在 B 过来触发了 A 的属性赋值,A 开始第二次调用 getBean() 方法了
那么 A 又要开始执行 getBean() 的四大核心步骤:
记住现在 Spring 缓存中已经有两个半成品 A、B,都保存着三级缓存中呢
A 又开始从123级缓存中查看是否有数据,此时过来发现三级缓存中有值了(是 A 在第一次调用 getBean() 的时候放进去的),源码如下:
A 就从三级缓存中取出数据,然后还会把取出的额数据移到二级缓存 earlySingletonObjects 中保存,这个二级缓存说实话,如果不存在三者之间的循环依赖问题,基本没啥用,多此一举。这种两个之间的循环依赖完全用一个三级缓存就可以解决。
A 过来做完数据转移,就拿着取出的 bean 对象开始返回,注意此时应该返回到哪里呢?
你要知道这个 A 是因为 B 在给属性 A 赋值时触发的 A 调用 getBean() 方法的,此时属性 A 调用 getBean() 拿到了值,这也就表示 B 给属性 A 赋值已经赋值好了(底层都是通过反射赋值的)。
B 给属性 A 赋值完成了,那么表示 B 的整个 getBean() 就结束了,此时应该返回到哪里呢?
B 是怎么过来的?是不是因为在 A 调用 getBean() 流程中,A 要给属性 B 赋值操作,然后出发的 B 调用 getBean() ,现在 B 调用 getBean() 流程已经结束,是不是就会返回 A 给属性 B 赋值的地方,源码如下:
A 给属性 B 赋值完之后,A 继续往下执行,最终会把完成实例化的 A bean 存放到一级缓存,并且清空23级缓存和一个标记缓存,源码如下:
至此 A 里面有了 B,B 里面有了 A 的对象引用,这就是 Spring 怎么解决单实例的循环依赖问题,核心是通过缓存先保存自己的一份引用。
至于多例模式下的循环依赖,Spring 是不支持的,因为多例情况下不会走缓存,并且是以懒加载的形式创建 bean 实例的。