Spring 循环依赖处理机制

Spring 循环依赖处理机制

概念

  1. 单例 bean 构造器参数循环依赖 (无法解决)
  2. prototype 原型 bean 循环依赖 (无法解决),没有被 spring 管理了。

当 A B 中存在循环依赖,并且二者都进行了切面,也就是动态代理,那么返回的这两个 bean 就都是 proxy 类。

  • Map<String, Object> singletonObjects: 一级缓存,初始化完全的 bean。
  • Map<String, Object> earlySingletonObjects:二级缓存,存放的为动态代理对象。
  • Map<String, ObjectFactory<?>> singletonFactories:三级缓存,key 为 beanName, value 为产生 bean 的工厂。

下面的逻辑是A -> B -> C, A 依赖 B, B 依赖 C, C 依赖 A。这三个 bean 都进行了增强,也就是动态代理,如下图所示,然后分析它的 getBean(A.class) 过程。

大致结构

每个 bean 的加载可以分为三个步骤:实例化,填充属性,初始化。

  • 实例化:调用反射实例化一个 bean,但是该 bean 中的属性还未填充,是一个不完整的 bean。
  • 填充属性:将刚实例化的 bean 中的属性进行赋值,其中会进行属性 bean 的获取。
  • 初始化:对 bean 的增强,调用后置处理器,可以完成生成 bean 的代理对象。

具体流程

  1. 首先加载A,从三级缓存池中获取,这里第一次是获取不到的,如果 bean 是单例,那么通过反射创建实例 a,然后判断 a 是否是单例、是否依赖循环、是否早期暴露
  2. 判断 a 是否需要早期暴露对象,这里存在循环依赖,那么表示需要。将getEarlyBeanReference(beanName, mbd, bean) 方法加入到三级缓存中 a 的工厂方法。
  3. 然后进行属性的注入,这里会去进行 B b 属性的注入,这里会按照步骤1执行,去加载 B,
    执行完的结果是,实例化属性 b,三级缓存中存在着 b 的工厂方法,也就是暴露了早期引用。
  4. 执行 b 的属性注入,按照步骤3执行,执行完的结果是,实例化属性 c,三级缓存中存在着 c 的工厂方法,也就是暴露了早期引用。
  5. 执行 c 的属性注入,存在 A 的属性依赖,这里和上面步骤不同了,但是这里可以从三级缓存池中获取的到,获取的是在三级缓存中 a 的工厂方法,然后调用工厂方法,也就是 getEarlyBeanReference(beanName, mbd, bean) ,这里会判断是否需要生成代理对象,如果是的话,那么就会返回新生成的代理对象 proxyA, 否则返回 a。然后删除三级缓存中的 a 的工厂方法,将返回对象放入二级缓存,这里需要返回 proxyA
  6. 将 proxyA 对象通过反射赋值给 c 中的 A 属性,如果存在多个属性那么继续按照上述步骤执行,这里只有 A 那么属性注入就完成了。
  7. 执行 c 的初始化,initializeBean(beanName, exposedObject, mbd),判断是否需要执行后置处理进行增强,如果前面已经是代理对象,那么不会再创建新的代理对象了,因为存在一个代理对象的缓存,解决了多次动态代理的问题。这里需要返回的是 c 的代理对象 proxyC。
  8. 判断是否早期暴露 earlySingletonExposure ,那么从一、二级缓存中获取 c 的引用,返回结果为 earlySingletonReference ,这里是保证不能通过三级缓存再去创建对象。如果 earlySingletonReference 不为空,则会返回它,否则返回 proxyC。
  9. 走到这一步,C 的获取已经完全完成了。将 proxyC 放入一级缓存,并删除二、三级缓存中的C 的值。
  10. 将生成的好的 proxyC 作为属性值,b 进行 属性填充,B,A 按照上述步骤6执行。最终返回 proxyB,proxyA,都会被放入一级缓存,并删除二、三级缓存中的A,B 的值。

流程疑问

1. 在 c 进行 A 的赋值操作时,c 还只是实例对象,为什么生成代理后的 proxyC 存在着 proxyA 的引用关系?

spring 中的生成代理方式,生成的 proxy 对象中,存在 target 属性,其中 target 属性值就是最开始实例化的 c ,这样当你调用 proxyC 的属性、方法时,实际是调用 target 属性中的实例 c 中的属性、方法。

2. 为什么采用三级缓存解决循环依赖呢?两级不是也可以吗?那一级呢?

首先来分析下情况,如果只存在一级的情况:

  1. 加载 A, 先从一级缓存中查找,没有找到,创建一个 a,
  2. 然后属性注入 B,然后从缓存找B,没有找到,创建一个 b,
  3. 这时获取 bean,那么 a 是未成品,那么就会造成空指针异常。

只存在二级情况:

  1. 规定成品都在一级缓存中,半成品都在二级缓存,按照上面的逻辑,
  2. 创建 b 返回给 a,然后 a 变成完整的,那么放在一级缓存中,这样操作是能够解决的。

二级情况,存在代理:

  1. 按照上面的逻辑,唯一的不同的就是创建的时候就判断 A 是否需要增强,产生代理对象。
  2. 第一次就生成代理对象 proxyA 放入二级缓存,接着执行 B,生成 proxyB 放入二级缓存
    并返回给 proxyA,proxyA 现在是完整的,接着放入一级缓存中。这样也是可以的。

那 spring 为什么使用三级缓存呢?
Spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 AOP 代理(尽可能延迟代理对象的生成),所以 Spring 用了第三级缓存,既维持了设计原则,又处理了循环依赖;牺牲那么一丢丢内存空间是愿意接受的

比较好的有代码的文章:https://www.cnblogs.com/youzhibing/p/14337244.html

猜你喜欢

转载自blog.csdn.net/LarrYFinal/article/details/119057073