一文深入分析阿里Java二面神仙问题:Spring循环依赖
什么是循环依赖?
举个例子
/**
* A 类,引入 B 类的属性 b
*/public class A {
private B b;}/**
* B 类,引入 A 类的属性 a
*/public class B {
private A a;}再看个简单的图:

像这样,创建 a 的时候需要依赖 b,那就创建 b,结果创建 b 的时候又需要依赖 a,那就创建 a,创建 a 的时候需要依赖 b,那就创建 b,结果创建 b 的时候又需要依赖 a ……
互相依赖何时了,死循环了吧? 诺,这就是循环依赖!
循环依赖其实不算个问题或者错误,我们实际在开发的时候,也可能会用到。 再拿最开始的 A 和 B 来说,我们手动使用的时候会用以下方式:
A a = new A(); B b = new B(); b.setA(a); a.setB(b);
其实这样就解决了循环依赖,功能上是没有问题的,但是为什么 Spring 要解决循环依赖?
为什么 Spring 要解决循环依赖?
首先简单了解下,我们用 Spring 框架,它帮我们做了什么事情?总结上来说,六字真言:IoC 和 AOP。
由于 Spring 解决循环依赖是考虑到 IoC 和 AOP 相关知识了,所以这里我先提一下。
由于本文主要的核心是 Spring 的循环依赖处理,所以不会对 IoC 和 AOP 做详细的说明,想了解以后有机会再说
IoC,主要是将对象的创建、管理都交给了 Spring 来管理,能够解决对象之间的耦合问题,对开发人员来说也是省时省力的。
AOP,主要是在不改变原有业务逻辑情况下,增强横切逻辑代码,也是解耦合,避免横切逻辑代码重复;也是对 OOP 的延续、补充。
既然类的实例化都交给了 Spring 来管理了,那么循环依赖 Spring 肯定也要考虑到怎么去处理(怎么总觉得有点像是废话 )。
解决循环依赖的方式
参考我们能想到的肯定是手动处理的方式,先将对象都 new 出来,然后进行 set 属性值,而 Spring 也是通过这样的形式来处理的(你说巧不巧?其实一点都不巧 ,后面再说为什么),其实 Spring 管理 Bean 的实例化底层其实是由反射实现的。
而我们实例化的方式也有好多种,比如通过构造函数,一次性将属性赋值,像下面这样
// 假设有学生这个类public class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}}// 通过构造器方式实例化并赋值new Student(1, "Suremotoo");但是使用构造器这样的方式,是无法解决循环依赖的!为什么不能呢?
我们还是以文中开头的 A 和 B 互相依赖来说, 要通过构造器的方式实现 A 的实例化,如下
new A(b);
Wow,是不是发现问题了?要通过构造器的方式,首先要将属性值实例化出来啊!A 要依赖属性 b,就需要先将 B 实例化,可是 B 的实例化是不是还是需要依赖 A❗️ 这不就是文中开头描述的样子嘛,所以通过构造器的方式,Spring 也没有办法解决循环依赖。
我们使用 set 可以解决,那么 Spring 也使用 set 方式呢?答案是可以的。
既然底层是通过反射实现的,我们自己也用反射实现的话,大概思路是这样的(还是以 A 和 B 为例)
先实例化 A 类
再实例化 B 类
set B 类中的 a 属性
set A 类中的 b 属性
其实就是通过反射,实现以下代码
A a = new A(); B b = new B(); b.setA(a); a.setB(b);
这里可以稍微说明一下,为什么这样可以?
A a = new A(),说明 A 只是实例化,还未初始化
同理,B b = new B() 也只是实例化,并未初始化
a.setB(b);, 对 a 的属性赋值,完成 a 的初始化
b.setA(a);, 对 b 的属性赋值,完成 b 的初始化
现在是不是有点感觉了,先把狗骗进来,再杀
Spring 如何解决循环依赖问题
先上个通俗的答案解释,三级缓存。
/** * 单例对象的缓存:bean 名称——bean 实例,即:所谓的单例池。 * 表示已经经历了完整生命周期的 Bean 对象 * <b>第一级缓存</b
原创不易,完成人机校验,阅读全文