面试
对象先创建出来,但先不急着把它完全初始化好,而是先"提前曝光"一个半成品对象,让别的 Bean 先用。这个过程主要靠 三级缓存。
一级缓存:存放已经创建完成、可以直接使用的单例 Bean。
二级缓存:存放"提前曝光"的 Bean,也就是还没完全初始化好,但已经能先拿来用的对象。
三级缓存:存放一个工厂,用来生成"提前曝光对象"。为什么不直接放对象,而要放工厂?因为这样可以决定提前曝光的是原始对象,还是代理对象。
假设:
- A 依赖 B
- B 依赖 A
Spring 创建过程大致如下:
第一步:创建 A
Spring 发现 A 还没有,于是先实例化 A。
注意,这时候 A 只是被 new 出来了,属性还没注入完,还不是完整对象。
然后 Spring 会把一个能获取 A 早期对象的工厂放到三级缓存里,表示:
"A 虽然还没完全好,但如果别人急着要,可以先拿到一个早期引用。"
第二步:给 A 注入属性时,发现它依赖 B
于是 Spring 转头去创建 B。
第三步:创建 B
同样,先实例化 B。
然后也把 B 的早期对象工厂放到三级缓存里。
第四步:给 B 注入属性时,发现它依赖 A
这时 Spring 发现 A 正在创建中,还没进一级缓存。
怎么办?
Spring 就会去 三级缓存 里找 A 对应的工厂,拿到 A 的早期引用 。
这个早期引用会放到二级缓存里,同时三级缓存中的那个工厂就可以删掉了。
然后 B 就可以顺利注入这个 A。
第五步:B 创建完成
B 完整初始化后,放入一级缓存。
第六步:回到 A 的创建过程
这时 A 需要的 B 已经有了,于是把 B 注入给 A。
A 也完成初始化,最后放入一级缓存。
这样循环依赖就解开了。
三、最本质的理解
你可以把它理解成:
- 一级缓存:成品
- 二级缓存:半成品对象
- 三级缓存:半成品对象的"生产工厂"
四、要注意的一点
Spring 这种方式主要解决的是:
单例 Bean 的 setter 注入 / 字段注入 的循环依赖
但是下面这种一般解决不了:
- 构造器注入循环依赖
因为构造器注入时,对象连 new 完都做不到,更别说提前曝光了。
例如:
- A 的构造器里要 B
- B 的构造器里要 A
那就谁也没法先创建出来。