本章来聊聊spring 中的循环引用问题该如何解决。这里聊的很粗糙,并没有那么细节,只是大概了解了一点。
什么是循环引用?
如下图所示:
图中有两个类,一个 class a ,a 中又引用了 b,class b 中又引用了 a 。(基于 setter 注入和字段注入)
当 spring 容器想要创建 a 时发现 a 中需要使用到 b 对象,此时就去创建 b ,在创建 b 对象时又发现还需要引用 a ,此时就发生循环引用了。
当然循环引用并非就是 a 引用 b ,b 引用 a 的情况,还有很多,如:
当然,此图比较草率。
有了循环依赖就会有死循环的问题。
循环依赖
来看看死循环产生的过程:
为什么这里是半成品呢?要是熟悉 spring-bean 的 生命周期就会知道,首先会去调用的是构造函数,像一些依赖注入啊,还有一些接口的实现的方法重写啊还有后置处理器初始化方法,想这些都还没有去执行,所以说他还是一个半成品对象。
继续向后执行:
此时,在容器中找不到 a 对象啊,那么他又去实例化 a 了。此时死循环就产生了,这个就叫做 循环依赖。
三级缓存:
缓存级别 | 源码名称 | 作用 |
---|---|---|
一级缓存 | singletonobjects | 存储已经完全初始化好的单例 bean。当 bean 初始化完成,所有依赖项都已注入,就会放入此缓存,供后续获取 bean 时直接使用,提高获取单例 bean 的效率。 简单来说:单例池,缓存已经经历了完整的生命周期,已经初始化完成了的对象。 |
二级缓存 | earlysingletonobjects | 存储提前暴露的原始 bean,即已经开始实例化但还未完成初始化(如未填充属性)的 bean。用于解决在构造器注入过程中发生的循环依赖问题,确保在循环依赖情况下 bean 只被创建一次,也能解决多线程并发下获取不完整 bean 的性能问题。 简单来说:缓存早期的 bean 对象(生命周期未完成) |
三级缓存 | singletonfactories | 存储 bean 的 objectfactory,用于生成原始 bean 的代理对象。当遇到循环依赖且涉及到代理对象创建时,spring 会将 objectfactory 放入三级缓存,在后续需要时通过它来获取 bean 的实例,以处理代理相关情况,统一处理普通 bean 和代理 bean。 简单来说:缓存 objectfactory,表示对象工厂,用来创建某个对象 |
我们一个个来说
三级缓存解决循环依赖
回到上个图片:
按照一级缓存的逻辑,走完一个生命周期才能将对象存储在缓存中啊(全是半成品),那么这个流程中必然是没有对象存在缓存中的。
走到这里发现了,单凭一级缓存是没有办法解决循环依赖的。
要想打破这个循环依赖,需要一个中间人的参与(暂时存放一个半成品的 对象),这个中间人就是二级缓存。
二级缓存
我们重新走一遍流程:
实例化一个 a 对象,此时生成一个 原始对象 a(半成品的 a )只是开辟了空间,执行了一个构造方法
就将这个方法存入这个二级缓存中
发现创建 a 对象需要依赖 b 对象,就注入 b,b 又不存在,那么就实例化一个 b
b还是和之前的 a 一样,同时也将 原始对象存入这个 二级缓存中
但是在 b 中注入 a 时,发现了缓存中有一个原始对象 a ,那么就可以打破循环依赖,使 b创建成功
此时 b 已经创建成功了,就可以存储到 一级缓存中了,那么也就可以将 b 注入进 a 了
a 也就可以创建成功,成功之后也需要存入一级缓存(此时二级缓存中的半成品对象也可以清理掉了)
此时似乎使用 二级缓存 + 一级缓存 就可以解决循环依赖的问题了啊,那还要三级缓存干嘛呢?
我们假设,如果这个 a 是个代理对象呢?
两个原因:
代理对象创建时机的问题:spring 的代理对象通常是在 bean 初始化过程的特定阶段创建的(例如在初始化方法执行之后)。在处理循环依赖时,如果仅使用一级和二级缓存,当一个 bean 在创建过程中需要依赖另一个 bean,而另一个 bean 又依赖这个 bean 时,由于代理对象还未创建,二级缓存中存储的只是原始的 bean 实例,不是代理对象。如果将原始 bean 实例注入到依赖它的 bean 中,就会导致依赖方持有的是原始 bean 而不是代理 bean,这不符合 spring 的设计要求。
保证代理对象的一致性:对于同一个 bean,无论从何处获取,都应该保证是同一个代理对象。如果没有三级缓存,在循环依赖场景下可能会出现多次创建代理对象的情况,从而破坏了单例 bean 的唯一性和代理对象的一致性。
三级缓存
此时我们使用 三级缓存来代替二级缓存
一级缓存+二级缓存 不是不能解决 代理对象的问题吗,那么这里就使用三级缓存来做,再注入 a 时,通过对象工厂生成,你是代理对象那就生成一个代理对象再注入,你是一个普通对象就生成一个普通对象 再注入。
虽然说 通过 a 的 objectfactory 对象创建了一个代理对象,但是此时还是一个半成品对象,这里就需要 二级缓存 来存放了。此时存放的是一个 a 的代理对象(半成品)。在完成的对象存放在一级缓存中后被删除了。
到这里借助了三级缓存,解决了大部分循环依赖的问题;借助工厂类,帮助我们生成工厂对象来产生代理对象。
为什么是大部分的循环依赖呢,因为某些循环引用 spring 框架解决不了,需要手动来解决。例如:构造方法注入产生的循环依赖。
class a { private b b; public a(b b) { this.b = b; } } class b { private a a; public b(a a) { this.a = a; } }
三级缓存可以解决初始化过程中的循环依赖,却无法解决构造函数产生的循环依赖,那么该怎么办解决呢?
我们只需要在 参数之前加上一个 @lazy (延迟加载)
public a(@lazy b b) { system.out.println("a的构造方法"); this.b = b; }
延迟加载的意思就是什么时候需要对象再进行对象的创建,而不再是直接把对象注入进来。
到此就解决循环依赖了。
三级缓存 + 一级缓存 存在的问题
再回顾三级缓存中,既然三级缓存代替了二级缓存,那么能否使用通过 一级缓存 + 三级缓存来解决循环依赖呢?
没有二级缓存的后果:
如果没有二级缓存,当出现循环依赖时,虽然三级缓存可以提供早期引用,但无法区分同一个 bean 的不同状态。例如,无法区分是正在创建的 bean 还是已经创建好但还未注入属性的 bean,可能会导致错误的 bean 引用被使用,进而引发循环依赖问题无法正确解决。
每次需要使用工厂生成好的对象直接去二级缓存拿出来就可以了,不用再次去生成这个对象。这也是二级缓存的核心作用之一。
如果没有二级缓存,那么就有可能产生多例的情况,此时处理起来就更麻烦了。
到此这篇关于spring 中的循环引用问题的文章就介绍到这了,更多相关spring 循环引用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论