Spring 中的循环引用问题解决方法

2025-04-27 17:50

本文主要是介绍Spring 中的循环引用问题解决方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Spring中的循环引用问题解决方法》:本文主要介绍Spring中的循环引用问题解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧...

本章来聊聊Spring 中的循环引用问题该如何解决。这里聊的很粗糙,并没有那么细节,只是大概了解了一点。

什么是循环引用?

如下图所示:

Spring 中的循环引用问题解决方法

图中有两个类,一个 Class A ,A 中又引用了 B,Class B 中又引用了 A 。(基于 setter 注入和字段注入)

当 Spring 容器想要创建 A 时发现 A 中需要使用到 B 对象,此时就去创建 B ,在创建 B 对象时又发现还需要引用 A ,此时就发生循环引用了。

当然循环引用并非就是 A 引用 B ,B 引用 A 的情况,还有很多,如:

Spring 中的循环引用问题解决方法

当然,此图比较草率。

有了循环依赖就会有死循环的问题。

循环依赖

来看看死循环产生的过程:

Spring 中的循环引用问题解决方法

为什么这里是半成品呢?要是熟悉 Spring-bean 的 生命周期就会知道,首先会去调用的是构造函数,像一些依赖注入啊,还有一些接口的实现的方法重写啊还有后置处理器初始化方法,想这些都还没有去执行,所以说他还是一个半成品对象。

继续向后执行:

Spring 中的循环引用问题解决方法

此时,在容器中找不到 A 对象啊,那么他又去实例化 A 了。此时死循环就产生了,这个就叫做 循环依赖。

三级缓存:

缓存级别源码名称作用
一级缓存singletonObjects存储已经完全初始化好的单例 Bean。当 Bean 初始化完成,所有依赖项都已注入,就会放入此缓存,供后续获取 Bean 时直接使用,提高获取单例 Bean 的效率。
简单来说:单例池,缓存已经经历了完整的生命周期,已经初始化完成了的对象。
二级缓存
earlySingletonObjects

存储提前暴露的原始 Bean,即已经开始实例化但还未完成初始化(如未填充属性)的 Bean。用于解决在构造器注入过程中发生的循环依赖问题,确保在循环依赖情况下 Bean 只被创建一次,也能解决多线程并发下获取不完整 Bean 的性能问题。

简单来说:缓存早期的 http://www.chinasem.cnbean 对象(生命周期未完成)

三级缓存singletonFactories

存储 Bean 的 ObjectFactory,用于生成原始 Bean 的代理对象。当遇到循环依赖且涉及到代理对象创建时,Spring 会将 ObjectFactory 放入三级缓存,在后续需要时通过它来获取 Bean 的实例,以处理代理相关情况,统一处理普通 Bean 和代理 Bean。

简单来说:缓存 objectFactory,表示对象工厂,用来创建某个对象

我们一个个来说

三级缓存解决循环依赖

回到上个图片:

Spring 中的循环引用问题解决方法

按照一级缓存的逻辑,走完一个生命周期才能将对象存储在缓存中啊(全是半成品),那么这个流程中必然是没有对象存在缓存中的。

走到这里发现了,单凭一级缓存是没有办法解决循环依赖的。

要想打破这个循环依赖,需要一个中间人的参与(暂时存放一个半成品的 对象),这个中间人就是二级缓存。

二级缓存

Spring 中的循环引用问题解决方法

我们重新走一遍流程:

实例化一个 A 对象,此时生成一个 原始对js象 A(半成品的 A )只是开辟了空间,执行了一个构造方法
就将这个方法存入这个二级缓存中
发现创建 A 对象需要依赖 B 对象,就注入 B,B 又不存在,那么就实例化一个 B
B还是和之前的 A 一样,同时也将 原始对象存入这个php 二级缓存中
但是在 B 中注入 A 时,发现了缓存中有一个原始对象 A ,那么就可以打破循环依赖,使 B创建成功 
此时 B 已经创建成功了,就可以存储到 一级缓存中了,那么也就可以将 B 注入进 A 了
A 也就可以创建成功,成功之后也需要存入一级缓存(此时二级缓存中的半成品对象也可以清理掉了)

此时似乎使用 二级缓存 + 一级缓存 就可以解决循环依赖的问题了啊,那还要三级缓存干嘛呢?

我们假设,如果这个 A 是个代理对象呢?

两个原因:

代理对象创建时机的问题:Spring 的代理对象通常是在 Bean 初始化过程的特定阶段创建的(例如在初始化方法执行之后)。在处理循环依赖时,如果仅使用一级和二级缓存,当一个 Bean 在创建过程中需要依赖另一个 Bean,而另一个 Bean 又依赖php这个 Bean 时www.chinasem.cn,由于代理对象还未创建,二级缓存中存储的只是原始的 Bean 实例,不是代理对象。如果将原始 Bean 实例注入到依赖它的 Bean 中,就会导致依赖方持有的是原始 Bean 而不是代理 Bean,这不符合 Spring 的设计要求。
保证代理对象的一致性:对于同一个 Bean,无论从何处获取,都应该保证是同一个代理对象。如果没有三级缓存,在循环依赖场景下可能会出现多次创建代理对象的情况,从而破坏了单例 Bean 的唯一性和代理对象的一致性。

三级缓存

此时我们使用 三级缓存来代替二级缓存

Spring 中的循环引用问题解决方法

一级缓存+二级缓存 不是不能解决 代理对象的问题吗,那么这里就使用三级缓存来做,再注入 A 时,通过对象工厂生成,你是代理对象那就生成一个代理对象再注入,你是一个普通对象就生成一个普通对象 再注入。

虽然说 通过 A 的 ObjectFactory 对象创建了一个代理对象,但是此时还是一个半成品对象,这里就需要 二级缓存 来存放了。此时存放的是一个 A 的代理对象(半成品)。在完成的对象存放在一级缓存中后被删除了。

Spring 中的循环引用问题解决方法

到这里借助了三级缓存,解决了大部分循环依赖的问题;借助工厂类,帮助我们生成工厂对象来产生代理对象。

为什么是大部分的循环依赖呢,因为某些循环引用 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;
    }
}

Spring 中的循环引用问题解决方法

三级缓存可以解决初始化过程中的循环依赖,却无法解决构造函数产生的循环依赖,那么该怎么办解决呢?

我们只需要在 参数之前加上一个 @Lazy (延迟加载)

public A(@Lazy B b) {
        System.out.println("A的构造方法");
        this.b = b;
    }

延迟加载的意思就是什么时候需要对象再进行对象的创建,而不再是直接把对象注入进来。

到此就解决循环依赖了。

三级缓存 + 一级缓存 存在的问题

再回顾三级缓存中,既然三级缓存代替了二级缓存,那么能否使用通过 一级缓存 + 三级缓存来解决循环依赖呢?

没有二级缓存的后果:

如果没有二级缓存,当出现循环依赖时,虽然三级缓存可以提供早期引用,但无法区分同一个 Bean 的不同状态。例如,无法区分是正在创建的 Bean 还是已经创建好但还未注入属性的 Bean,可能会导致错误的 Bean 引用被使用,进而引发循环依赖问题无法正确解决。

Spring 中的循环引用问题解决方法

每次需要使用工厂生成好的对象直接去二级缓存拿出来就可以了,不用再次去生成这个对象。这也是二级缓存的核心作用之一。

如果没有二级缓存,那么就有可能产生多例的情况,此时处理起来就更麻烦了。

到此这篇关于Spring 中的循环引用问题的文章就介绍到这了,更多相关Spring 循环引用内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于Spring 中的循环引用问题解决方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154393

相关文章

python获取网页表格的多种方法汇总

《python获取网页表格的多种方法汇总》我们在网页上看到很多的表格,如果要获取里面的数据或者转化成其他格式,就需要将表格获取下来并进行整理,在Python中,获取网页表格的方法有多种,下面就跟随小编... 目录1. 使用Pandas的read_html2. 使用BeautifulSoup和pandas3.

SpringBoot UserAgentUtils获取用户浏览器的用法

《SpringBootUserAgentUtils获取用户浏览器的用法》UserAgentUtils是于处理用户代理(User-Agent)字符串的工具类,一般用于解析和处理浏览器、操作系统以及设备... 目录介绍效果图依赖封装客户端工具封装IP工具实体类获取设备信息入库介绍UserAgentUtils

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

Pandas统计每行数据中的空值的方法示例

《Pandas统计每行数据中的空值的方法示例》处理缺失数据(NaN值)是一个非常常见的问题,本文主要介绍了Pandas统计每行数据中的空值的方法示例,具有一定的参考价值,感兴趣的可以了解一下... 目录什么是空值?为什么要统计空值?准备工作创建示例数据统计每行空值数量进一步分析www.chinasem.cn处

Spring Boot中JSON数值溢出问题从报错到优雅解决办法

《SpringBoot中JSON数值溢出问题从报错到优雅解决办法》:本文主要介绍SpringBoot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和... 目录一、问题背景:为什么我的接口突然报错了?二、为什么会发生这个错误?1. Java 数据类型的“容量”限制

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置

SpringBoot基于配置实现短信服务策略的动态切换

《SpringBoot基于配置实现短信服务策略的动态切换》这篇文章主要为大家详细介绍了SpringBoot在接入多个短信服务商(如阿里云、腾讯云、华为云)后,如何根据配置或环境切换使用不同的服务商,需... 目录目标功能示例配置(application.yml)配置类绑定短信发送策略接口示例:阿里云 & 腾

关于MongoDB图片URL存储异常问题以及解决

《关于MongoDB图片URL存储异常问题以及解决》:本文主要介绍关于MongoDB图片URL存储异常问题以及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录MongoDB图片URL存储异常问题项目场景问题描述原因分析解决方案预防措施js总结MongoDB图

SpringBoot项目中报错The field screenShot exceeds its maximum permitted size of 1048576 bytes.的问题及解决

《SpringBoot项目中报错ThefieldscreenShotexceedsitsmaximumpermittedsizeof1048576bytes.的问题及解决》这篇文章... 目录项目场景问题描述原因分析解决方案总结项目场景javascript提示:项目相关背景:项目场景:基于Spring