做java发开好多年, 用的最多的框架应该就是Spring全家桶了吧.
虽然好久没有写代码了, 但是时间长了, 回头想想, 总有一些小心得, 这里做一些记录, 不一定完全正确.
以下是一些关于 Spring Bean 循环依赖 的一些知识回顾, 不涉及源代码 ( 很久没看过了)
三级缓存干了什么
捋一下spring的三级缓存
1. singletonObjects 存储 实例化完成的**单例对象**或**代理对象**
2. earlySingletonObjects 存储 未完成实例化的**单例对象**或**代理对象**
3. singletonFactories 存储 完成构造函数的**对象工厂**
都知道三级缓存是用来解决 bean 的循环依赖的, 那么具体的过程是怎么样的?
(懒得画图...)
举个例子如下
@Component
class A{
@Autowired
B b;
public A(){}
}
@Component
class B{
@Autowired
A A;
public A(){}
}
A的实例化需要依赖B, 而B的实例化又依赖A, 这就是一个最简单的循环依赖, 实际情况中可能会存在更复杂的情况, 不过万变不离其宗, 就像递归函数一样, 调用一边和一百遍道理都一样.
所以 A 和 B 的实例化大致流程如下(是的, 在写上边的一段内容时候, 我还在犹豫要不要画图, 结果....)
省略用来 list 查询筛选等步骤内容...
spring 扫描到 class A -> 在三级缓存中查找有没有 A 的缓存 ---> 没有
---> 生成 A 的 beanFactory ---> 调用 A 构造方法
---> 将 A.beanFatory 存入 singletonFactories (第三级缓存) ---> 扫描实例化 A 需要的 filed 参数 ---> 需要实例 B
---> 在缓存中查找有没有 B 的缓存 ---> 没有
---> 生成 B 的 beanFactory ---> 调用 B 构造方法
---> 将 B.beanFatory 存入 singletonFactories (第三级缓存) ---> 扫描实例化 B 需要的 filed 参数 ---> 需要实例 A
---> 在缓存中查找有没有 A 的缓存 ---> 有
---> 拿到 A.beanFatory 判断是否需要代理 (需要的话, 生成代理对象), 将对象移入 earlySingletonObjects, 返回 A 的实例对象
---> B 的 field 注入完成, 判断是否需要代理 (需要的话, 生成代理对象) ---> 将 B 的实例对象移入 singletonObjects , 返回 B 的实例对象给 A
---> A 的实例化完成 ---> 将 A 的实例对象移入 singletonObjects
---> 初始化完成
总之, 一个bean的生成都需要经过下面3个阶段
1. new, 也就是调用构造函数
2. filed 依赖注入
3. 判断是否需要代理, 是否生成代理对象
NOTE
- 三级缓存只能解决通过属性注入 bean 的循环依赖, 而无法解决构造函数注入 bean 的循环依赖(@Lazy 貌似有奇奇怪怪的问题,忘了,总之不推荐).
- 通常项目中会推荐在构造函数中注入 bean, 比如 idea
实际项目中, 设计上通常会精细化service层的功能代码, 所以会尽量避免出现循环依赖的情况.
二级缓存能否解决循环依赖
可以, 但是会破环 spring 对 AOP 的支持.
spring 的 AOP 是通过代理对象来实现的, 那么什么时候哪个点去创建代理对象呢, 这就是 beanFatory 出现的意思了, 这里会判断对象是否是一个接口或者存在 AOP 的方法, 最后返回原始对象或者生成的代理对象.