Spring循环依赖,三级缓存详解

1.Spring如何解决循环依赖?

分析:

点出什么是循环依赖,然后说出三级缓存分别缓存了什么,然后说一下具体解决的流程,说一下 Spring 解决循环依赖有一定条件(单例和构造器,在举例子说一下),说一下为什么需要三级缓存,二级不行吗

回答:

什么是循环依赖:

循环依赖是指多个 Bean 循环引用 形成一个闭环 导致 Spring 容器无法正常初始化他们。

例如:A 要依赖 B,发现 B 没创建,于是开始创建 B,创建的过程发现 B 要依赖 A,而 A 还没创建好,因为它要等 B 创建好,这样他们就在这儿 卡 bug 了

Spring是根据三级缓存解决循环依赖的

三级缓存分别缓存了什么:

  1. 一级缓存:singletonObjects,存储所有已创建完成的单例 Bean(完整的 Bean)

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

  3. 三级缓存:singletonFactories,存储一个 Bean 工厂,通过这个 Bean 工厂,可以获得仅实例化,还没有进行属性注入和初始化的Bean。因为 Bean 的创建分为 3 个阶段:实例化,属性注入,初始化。每个 Bean 实例化之后,都会向三级缓存 singletonFactories 塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个仅进行实例化的 Bean,并将这个得到的 Bean 塞入到二级缓存。通过三级缓存,就可以将未初始化完成的 Bean 提前暴露,这也是解决循环依赖的关键

三级缓存(3 个 Map)具体是怎么解决循环依赖的:

我们以 A,B 形成循环依赖为例说明,前提是:A,B 都是在进行属性注入的时候需要对方,而且 A,B 都是单例

  1. 首先根据 BeanName ,A 先进行创建,然后 A 实例化成功,并且将 A 的工厂对象放入三级缓存,将 A 标记为正在创建中

  2. 然后进行 A 的属性注入,属性注入 B 时,去一级缓存找,没找到。然后发现 B 不再创建中,就进行 B 的创建

  3. B 进行实例化之后,来到了属性注入,需要注入 A,去一级缓存找,没找到,发现 A 在创建中,去 二级缓存找,也没找到,就去三级缓存找,找到了 A 的工厂对象,于是调用 工厂对象的 getObject() 方法,得到仅仅实例化的 A,并将其放入 二级缓存,同时把 三级缓存中的工厂对象删除(因为二级缓存中已经有仅实例化的 A了)。

  4. 然后 B 就能拿到 仅实例化的A,就能进行正常的属性注入,完成 正常 B 的初始化,将 B 放入一级缓存

  5. 然后回到 A 的属性注入,这时就可以从一级缓存中拿到完整的 B 对象,完成 A 的属性注入和后续的初始化,最后将二级缓存中的 A 对象删除,同时向 一级缓存 添加完整的 A 对象。至此结束

Spring 解决循环依赖有一定条件:

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

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

先说一下为什么必须是单例

普通类(原型类)都需要创建新对象,不能用和以前一样的对象。所以就会出现这种情况

创建 A1 需要 B1,创建 B1 又要创建 A2,创建 A2 又要创建 B2,B2 需要 A3,A3 需要 B3.... 就卡bug了,因此不能

再说一下为什么不全是构造器注入,且 BeanName 字母序排在前面的不能是构造器注入

        1.如果循环依赖都是构造器注入,那么 Spring 无法处理

原因:因为如果全是构造器注入,那么就是在实例化阶段就要得到依赖的对象,但是三级缓存中每一层都找不到,因为实例化之后第三层缓存中才有 Bean 工厂,所以实例化之前 三级缓存都是空的,都找不到对方依赖的对象。所以全是构造器注入,Spring 无法解决循环依赖

        2.如果循环依赖不完全是构造器注入,那么可能成功,可能失败,具体根 BeanName 的字母序有关,因为 Spring 容器是按照字母序创建 Bean 的,比如 A 的创建一定在 B 的前面

原因:

  • 比如 A 构造器注入,B setter 注入,就不行。因为 A 实例化依赖 B,B setter 注入在三级缓存中找不到 A,就会报错

  • A Setter 注入,B 构造器注入就行。A 属性注入依赖 B,B 构造器注入可以在第三级缓存中拿到 A 工厂对象从而得到 仅实例化的 A,所以可以正常注入。

为什么需要三级缓存,二级不行吗(见下一题)

Spring中的循环依赖为什么需要三级缓存,二级不行吗?

分析:

先说 二级缓存也可以(具体说明),再说 AOP 代理时二级缓存违反 Bean 生命周期,再说三级缓存是为了解决 AOP 代理的问题

回答:

Spring 用三级缓存的原因,主要是为了解决 AOP 代理的问题

如果单纯是要解决循环依赖,那么二级缓存其实就够了:

在实例化Bean A之后,我在二级map里面塞入这个A,然后继续属性注入,发现A依赖B所以要创建Bean B,这时候B就能从二级map得到A,完成B的建立之后,A自然而然能完成。

但是在 AOP 代理时,就会违反 Bean 的生命周期:

我们都知道如果有代理的话,那么我们想要直接拿到的是代理对象,也就是说如果 A 需要被代理,那么 B 依赖的 A 是已经被代理的 A,所以我们不能返回 A 给 B,而是返回代理的 A 给 B。

那么应该会想到,那和三级缓存有什么关系,我可以在放入二级缓存的时候判断这个 Bean 是否需要被代理,如果是直接放代理的对象不就行了么。

好像这么看也没什么问题,但是这违反了 Bean 生命周期的原则,因为被代理的对象是在对象初始化后期,也就是 BeanPostProcessor 后置处理这个阶段生成的,所以如果提早代理了其实是违背了 Bean 定义的生命周期。

所以二级缓存出现的问题是:不管有没有循环依赖,代理对象都会被提前暴露

那么三级缓存为什么能解决代理对象提前暴露的问题呢:

第三级缓存中存放的是 Bean工厂,当我们调用 Bean 工厂的 getObject() 方法时候,这个方法会判断该对象是否需要被代理,如果需要返回代理对象,如果不需要则返回不需要代理的对象。

  • 先看正常的没有循环依赖的情况下:就是正常的创建,第三级缓存的工厂根本不会被调用,所以 Bean 的生命周期就是对的,初始化完成之后才生成代理对象

  • 再看产生循环依赖的情况下:循环依赖会调用 三级缓存 Bean 工厂的 getObject 方法,这个方法会返回 代理对象,代理对象提前暴露。因为产生循环依赖,代理对象就得提前暴露

所以有了三级缓存,在没有循环依赖时,代理对象不会被提前暴露,有了循环依赖,代理对象就会提前暴露

所以 三级缓存解决了 二级缓存中:不管有没有循环依赖,代理对象都会被提前暴露 的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray-国

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值