不知道Spring如何解决循环依赖问题?看这一篇

本文详细介绍了Spring框架中循环依赖的概念,包括单例模式在解决循环依赖中的作用,以及依赖注入的不同方式如何影响处理循环依赖。文章还深入探讨了Spring的三级缓存机制,解释了为何需要三级缓存来处理循环依赖和代理问题,并通过实例展示了整个流程。
摘要由CSDN通过智能技术生成


前言

之前我们已经学习过SpringBean生命周期的相关概念,在创建Bean对象的时候可能会出现一个循环依赖问题。本章主要介绍了循环依赖的基本概念以及解决办法。


一、循环依赖是什么?

现在有个A对象,它的属性是B对象,而B对象的属性也是A对象,这就是循环依赖。
在这里插入图片描述
说白了就是A依赖B,而B又依赖A。(我们主要分析两个Bean之间的依赖)

这种循环依赖可能会产生哪些问题?
例如:A 要依赖 B,发现 B 还没创建。于是开始创建 B ,创建的过程发现 B 要依赖 A, 而 A 还没创建好呀,因为它要等 B 创建好,就这样 它们俩就搁这卡 bug 了 。

Spring 需要解决这个问题,那如何解决呢?
关键点:提前暴露未完全创建完毕的 Bean 。


二、如何解决

在 Spring 中,只有同时满足以下两点才能解决循环依赖的问题:
1.依赖的 Bean 必须都是单例。
2.依赖注入的方式,必须不全是构造器注入,且 beanName 字母序在前的不能是构造器注入。

2.1单例模式

可能看到这里大家有点迷惑,为什么依赖的Bean必须是单例模式,不能是原型模式?
从源码来看的话,循环依赖的 Bean 是原型模式,会直接抛错。
在这里插入图片描述
其次从逻辑上讲,原型模式每次都会创建一个新的对象,创建A1时需要创建一个B1,创建B1时需要创建一个A2,创建A2时需要创建一个B2…,这样又会出现无限循环问题,因为原型模式每次都会创建一个新的对象,不能用以前那个对象。

而单例模式一个对象只被创建一次,B需要的就是之前那个A,也是基于这点,Spring 就能操作了。

具体怎么做呢?
1.先创建 A,此时的 A 是不完整的(没有注入 B),然后用个map保存这个不完整的 A。
2.再创建 B ,B 需要 A,从那个 map 得到“不完整”的 A,此时的 B 就完整了。
3.然后 A 就可以注入 B,然后 A 就完整了,B 也完整了
在这里插入图片描述

总结:关键点就是提前暴露未完全创建完毕的 Bean 。

2.2依赖注入的方式

依赖注入的方式,必须不全是构造器注入,且 beanName 字母序在前的不能是构造器注入(Spring 容器是按照字母序创建 Bean 的,A 的创建永远排在 B 前面)。
要理解这一点,我们必须先了解Bean的生命周期的相关内容,首先我们来用一张图了解一下Spring的生命周期。
在这里插入图片描述
在 Spring 中创建 Bean 分三步:实例化,属性注入,初始化。
1.如果全是构造器注入会怎么样?

1.比如 A(B b) ,那表明在 new A的时候,就需要得到 B,此时需要 new B 。
2.B 也是要在构造的时候注入 A ,即 B(A a) ,这时候 B 需要在一个 map 中找到不完整的 A ,发现找不到。

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

2.A是set注入B,B是构造器注入A会怎么样?

1.实例化 A 之后,可以在 map 中存入 A,开始为 A 进行属性注入,发现需要 B。
2.此时 new B,发现构造器需要 A,此时从 map 中得到 A ,B 构造完毕。
3.B 进行属性注入,初始化,然后 A 注入 B 完成属性注入,然后初始化 A。
总结:整个过程很顺利,没毛病。

3.A 是通过构造器注入B,B 通过set 注入A会怎么样?。

1.实例化 A,发现构造函数需要 B, 此时去实例化 B。
2.然后进行 B 的属性注入,从 map 里面找不到 A,因为 A 还没 new 成功,所以 B 也卡住了,然后就 gg。

总结:在Setter注入,可以将依赖项部分注入,构造器注入不能部分注入,构造器注入直到你所有的依赖都注入后才开始创建实例(关于依赖注入的方式如果有不理解的地方可能暂时放一下,毕竟真的有点绕)。


三、三级缓存

3.1什么是三级缓存

明确了 Spring 创建 Bean 的三步骤之后,我们再来看看它为单例设置的三级缓存
在这里插入图片描述
1.一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)
2.二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行完属性注入和初始化的 Bean。
3.三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存。

具体流程如下:
1.A对象实例化之后,属性注入之前,其实会把A对象放入三级缓存中。key是BeanName,Value是ObjectFactory

2.等到A对象属性注入时,发现依赖B,又去实例化B时,B属性注入需要去获取A对象,这里就是从三级缓存里拿出ObjectFactory,从ObjectFactory得到对应的Bean(就是对象A)

3.然后把三级缓存的A记录给干掉,然后放到二级缓存中,二级缓存存储的key是BeanName,value就是Bean(这里的Bean还没做完属性注入相关的工作,就是个半成品)

4.B 属性注入成功,紧接着 B 进行初始化,最终返回,此时 B 已经被加到了一级缓存里 。

5.这时候就回到了 A 的属性注入,此时注入了 B,接着执行初始化,最后 A 也会被加到一级缓存里,且从二级缓存中删除 A。

3.2为什么不能是二级缓存?

有很多人会迷惑,为什么循环依赖需要三级缓存,二级不够吗?

在实例化 Bean A 之后,我在二级 map 里面塞入这个 A,然后继续属性注入。
发现 A 依赖 B 所以要创建 Bean B,这时候 B 就能从二级 map 得到 A ,完成 B 的建立之后, A 自然而然能完成。
所以 为什么要搞个三级缓存,且里面存的是创建 Bean 的工厂呢 ?

结论:如果仅仅只是为了破解循环依赖,二级缓存够了,压根就不必要三级。二级缓存就能解决缓存依赖,三级缓存解决的是代理。

也就是说如果 A 需要被代理,那么 B 依赖的 A 是已经被代理的 A,所以我们不能返回 A 给 B,而是返回代理的 A 给 B。这个工厂的作用就是判断这个对象是否需要代理,如果否则直接返回,如果是则返回代理对象。

为什么不能直接在二级 map 里面塞入这个A时就判断这个 Bean 是否需要代理,如果要直接放代理的对象,这样做不是更方便吗?

正常代理对象的生成是基于后置处理器,是在被代理的对象初始化后期调用生成的。
所以如果你提早代理了其实是违背了 Bean 定义的生命周期 。
所以 Spring 先在一个三级缓存放置一个工厂,如果产生循环依赖,那么就调用这个工厂提早得到代理对象。
如果没产生依赖,这个工厂根本不会被调用,所以 Bean 的生命周期就是对的。

总结

循环依赖的解决主要通过三级的缓存,在实例化后,会把自己扔到三级缓存,在注入属性时,发现需要依赖B,也会走B的实例化过程,B属性注入依赖A,从三级缓存找到A,然后删掉三级缓存的A,放到二级缓存去。最后 A初始化完毕后 也会被加到一级缓存里,且从二级缓存中删除 A。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JinziH Never Give Up

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

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

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

打赏作者

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

抵扣说明:

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

余额充值