Spring三级缓存解决循环依赖

我们都知道Spring中的BeanFactory是一个IOC容器 , 负责创建Bean和缓存一些单例的Bean对象 , 以供项目运行过程中使用 。
创建Bean的大概的过程:

  1. 实例化Bean对象 , 为Bean对象在内存中分配空间 , 各属性赋值为默认值
  2. 初始化Bean对象 , 为Bean对象填充属性
  3. 将Bean放入缓存
首先 , 容器为了缓存这些单例的Bean需要一个数据结构来存储 , 比如Map {k:name; v:bean} 。
【Spring三级缓存解决循环依赖】而我们创建一个Bean就可以往Map中存入一个Bean 。这时候我们仅需要一个Map就可以满足创建+缓存的需求 。
但是创建Bean过程中可能会遇到循环依赖问题 , 比如A对象依赖了一个B对象 , 而B对象内部又依赖了一个A , 如下:
public class A {B b;}public class B {A a;}假设A和B我都定义为单例的对象 , 并且需要在项目启动过程中自动注入 , 如下:
@Componentpublic class A {@AutowiredB b;}@Componentpublic class B {@AutowiredA a;}一级缓存
  1. 实例化A对象 。
  2. 填充A的属性阶段时需要去填充B对象 , 而此时B对象还没有创建 , 所以这里为了完成A的填充就必须要先去创建B对象;
  3. 实例化B对象 。
  4. 执行到B对象的填充属性阶段 , 又会需要去获取A对象 , 而此时Map中没有A , 因为A还没有创建完成 , 导致又需要去创建A对象 。
  5. 这样 , 就会循环往复 , 一直创建下去 , 只到堆栈溢出 。
为什么不能在实例化A之后就放入Map?因为此时A尚未创建完整 , 所有属性都是默认值 , 并不是一个完整的对象 , 在执行业务时可能会抛出未知的异常 。所以必须要在A创建完成之后才能放入Map 。
二级缓存此时我们引入二级缓存用另外一个Map2 {k:name; v:earlybean} 来存储尚未已经开始创建但是尚未完整创建的对象 。
  1. 实例化A对象之后 , 将A对象放入Map2中 。
  2. 在填充A的属性阶段需要去填充B对象 , 而此时B对象还没有创建 , 所以这里为了完成A的填充就必须要先去创建B对象 。
  3. 创建B对象的过程中 , 实例化B对象之后 , 将B对象放入Map2中 。
  4. 执行到B对象填充属性阶段 , 又会需要去获取A对象 , 而此时Map中没有A , 因为A还没有创建完成 , 但是我们继续从Map2中拿到尚未创建完毕的A的引用赋值给a字段 。这样B对象其实就已经创建完整了 , 尽管B.a对象是一个还未创建完成的对象 。
  5. 此时将B放入Map并且从Map2中删除 。
  6. 这时候B创建完成 , A继续执行b的属性填充可以拿到B对象 , 这样A也完成了创建 。
  7. 此时将A对象放入Map并从Map2中删除 。
二级缓存已然解决了循环依赖问题 , 为什么还需要三级缓存?从上面的流程中我们可以看到使用两级缓存可以完美解决循环依赖的问题 , 但是Spring中还有另外一个问题需要解决 , 这就是初始化过程中的AOP实现 。
AOP是Spring的重要功能 , 实现方式就是使用代理模式动态增强类的功能 。
动态单例目前有两种技术可以实现 , 一种是JDK自带的基于接口的动态Proxy技术 , 一种是CGlib基于字节码动态生成的Proxy技术 , 这两种技术都是需要原始对象创建完毕 , 之后基于原始对象生成代理对象的 。
那么我们发现 , 在二级缓存的设计下 , 我们需要在放入缓存Map之前将代理对象生成好 。
将流程改为:
  1. 实例化Bean对象 , 为Bean对象在内存中分配空间 , 各属性赋值为默认值
  2. 如果有动态单例 , 生成Bean对象的代理Proxy对象
  3. 初始化Proxy对象 , 为Bean对象填充属性
  4. 将Proxy放入缓存
这样虽然也可以解决 , AOP的问题 , 但是我们知道Spring中AOP的实现是通过后置处理器BeanPostProcessor机制来实现的 , 而后置处理器是在填充属性结束后才执行的 。流程如下:


推荐阅读