Spring 如何解决循环依赖?( 三 )


3. 原理深度解读3.1 什么要有 3 级缓存 ?这是一道非常经典的面试题,前面已经告诉大家详细的执行流程,包括源码解读,但是没有告诉大家为什么要用 3 级缓存?
这里是重点!敲黑板?。。?
我们先说“一级缓存”的作用 , 变量命名为 singletonObjects,结构是 Map<String, Object>,它就是一个单例池 , 将初始化好的对象放到里面,给其它线程使用,如果没有第一级缓存,程序不能保证 Spring 的单例属性 。
“二级缓存”先放放,我们直接看“三级缓存”的作用,变量命名为 singletonFactories,结构是 Map<String, ObjectFactory<?>>,Map 的 Value 是一个对象的代理工厂,所以“三级缓存”的作用,其实就是用来存放对象的代理工厂 。
那这个对象的代理工厂有什么作用呢,我先给出答案,它的主要作用是存放半成品的单例 Bean,目的是为了“打破循环”,可能大家还是不太懂,这里我再稍微解释一下 。
我们回到文章开头的例子,创建 A 对象时 , 会把实例化的 A 对象存入“三级缓存”,这个 A 其实是个半成品,因为没有完成依赖属性 B 的注入,所以后面当初始化 B 时,B 又要去找 A,这时就需要从“三级缓存”中拿到这个半成品的 A(这里描述 , 其实也不完全准确,因为不是直接拿,为了让大家好理解,我就先这样描述),打破循环 。
那我再问一个问题 , 为什么“三级缓存”不直接存半成品的 A,而是要存一个代理工厂呢 ?答案是因为 AOP 。
在解释这个问题前 , 我们看一下这个代理工厂的源码,让大家有一个更清晰的认识 。
直接找到创建 A 对象时,把实例化的 A 对象存入“三级缓存”的代码,直接用前面的两幅截图 。

Spring 如何解决循环依赖?

文章插图
图片
Spring 如何解决循环依赖?

文章插图
图片
下面我们主要看这个对象工厂是如何得到的,进入 getEarlyBeanReference() 方法 。
Spring 如何解决循环依赖?

文章插图
图片
Spring 如何解决循环依赖?

文章插图
图片
Spring 如何解决循环依赖?

文章插图

Spring 如何解决循环依赖?

文章插图
图片
最后一幅图太重要了 , 我们知道这个对象工厂的作用:
  • 如果 A 有 AOP,就创建一个代理对象;
  • 如果 A 没有 AOP,就返回原对象 。
那“二级缓存”的作用就清楚了,就是用来存放对象工厂生成的对象 , 这个对象可能是原对象,也可能是个代理对象 。
我再问一个问题,为什么要这样设计呢?把二级缓存干掉不行么 ?我们继续往下看 。
3.2 能干掉第 2 级缓存么 ?@Servicepublic class A {@Autowiredprivate B b;@Autowiredprivate C c;public void test1() {}}@Servicepublic class B {@Autowiredprivate A a;public void test2() {}}@Servicepublic class C {@Autowiredprivate A a;public void test3() {}}根据上面的套娃逻辑,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A 。
假如 A 需要进行 AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:
  • B 找到 A 时 , 直接通过三级缓存的工厂的代理对象,生成对象 A1 。
  • C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2 。
看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现 , 我们搞个二级缓存,把 A1 存下来,下次再获取时 , 直接从二级缓存获取,无需再生成新的代理对象 。
所以“二级缓存”的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean 。
如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满足要求 。
4. 写在最后我们再回顾一下 3 级缓存的作用: