Spring的循环依赖问题


目录

  1. 循环依赖
  2. 解决方法
  3. Spring的三级缓存
  4. 典型:Swagger的循环依赖报错

参考/来源:

循环依赖

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

这种循环依赖可能会产生问题,例如 A 要依赖 B,发现 B 还没创建

解决方法

关键就是提前暴露未完全创建完毕的 Bean

在 Spring 中,只有同时满足以下两点才能解决循环依赖的问题:

  1. 依赖的 Bean 必须都是单例

    先创建 A,此时的 A 是不完整的(没有注入 B),用个 map 保存这个不完整的 A,再创建 B ,B 需要 A。

    所以从那个 map 得到“不完整”的 A,此时的 B 就完整了,然后 A 就可以注入 B,然后 A 就完整了,B 也完整了,且它们是相互依赖的

  2. 依赖注入的方式,必须不全是构造器注入,且 beanName 字母序在前的不能是构造器注入

    在 Spring 中创建 Bean 分三步:

    • 实例化,createBeanInstance,就是 new 了个对象

    • 属性注入,populateBean, 就是 set 一些属性值

    • 初始化,initializeBean,执行一些 aware 接口中的方法,initMethod,AOP代理等

    明确了上面这三点,再结合我上面说的“不完整的”,我们来理一下。

    如果全是构造器注入,比如A(B b),那表明在 new 的时候,就需要得到 B,此时需要 new B 。

    但是 B 也是要在构造的时候注入 A ,即B(A a),这时候 B 需要在一个 map 中找到不完整的 A ,发现找不到。

    为什么找不到?因为 A 还没 new 完呢,所以找到不完整的 A,因此如果全是构造器注入的话,那么 Spring 无法处理循环依赖。

    注意, Spring 容器是按照字母序创建 Bean 的,A 的创建永远排在 B 前面。

Spring的三级缓存

Spring创建单例Bean过程中的三个map:

  • 一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)

  • 二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的 Bean

  • 三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存

这三个 map 是如何配合的呢?

  1. 首先,获取单例 Bean 的时候会通过 BeanName 先去 singletonObjects(一级缓存) 查找完整的 Bean,如果找到则直接返回,否则进行步骤 2。
  2. 看对应的 Bean 是否在创建中,如果不在直接返回null,如果是,则会去 earlySingletonObjects (二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3
  3. 去 singletonFactories (三级缓存)通过 BeanName 查找到对应的工厂,如果存着工厂则通过工厂创建 Bean ,并且放置到 earlySingletonObjects 中。
  4. 如果三个缓存都没找到,则返回 null。

比如还是上面类A、B的例子,创建时,Spring解决循环依赖的过程如下:

  1. 在实例化 Bean A 之后,会往 singletonFactories 塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个 Bean
  2. 然后就开始执行属性注入,这个时候 A 发现需要注入 B,所以去 getBean(B),此时又会走一遍上面描述的逻辑,到了 B 的属性注入这一步。
  3. 此时 B 调用 getBean(A),这时候一级缓存里面找不到,但是发现 A 正在创建中的,于是去二级缓存找,发现没找到,于是去三级缓存找,然后找到了。并且通过上面提前在三级缓存里暴露的工厂得到 A。
  4. 然后将这个工厂从三级缓存里删除,并将 A 加入到二级缓存中。然后结果就是 B 属性注入成功。
  5. 紧接着 B 调用 initializeBean 初始化,最终返回,此时 B 已经被加到了一级缓存里 。
  6. 这时候就回到了 A 的属性注入,此时注入了 B,接着执行初始化,最后 A 也会被加到一级缓存里,且从二级缓存中删除 A。

其实破坏循环依赖,其实只有二级缓存就够了,但是碍于生命周期的问题,提前暴露工厂延迟代理对象的生成,详情看:https://mp.weixin.qq.com/s/JVhRgiEcaNf6KLQehQwx1Q

典型: Swagger的循环依赖报错

Spring 2.6.4版本后,Springboot项目启动,Spring Security相关类出现循环依赖错误。

详见例子:https://mp.weixin.qq.com/s/zipKaEv2n6cIr6b02riZkg


文章作者: 小小千千
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小小千千 !
评论
  目录