Spring的一些小心得

Felix 2021年08月06日 64次浏览

做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

  1. 三级缓存只能解决通过属性注入 bean 的循环依赖, 而无法解决构造函数注入 bean 的循环依赖(@Lazy 貌似有奇奇怪怪的问题,忘了,总之不推荐).
  2. 通常项目中会推荐在构造函数中注入 bean, 比如 idea
    实际项目中, 设计上通常会精细化service层的功能代码, 所以会尽量避免出现循环依赖的情况.

二级缓存能否解决循环依赖

可以, 但是会破环 spring 对 AOP 的支持.
spring 的 AOP 是通过代理对象来实现的, 那么什么时候哪个点去创建代理对象呢, 这就是 beanFatory 出现的意思了, 这里会判断对象是否是一个接口或者存在 AOP 的方法, 最后返回原始对象或者生成的代理对象.