Spring的循环依赖问题和解决方案

在Spring框架中,循环依赖指的是两个或多个Bean之间相互依赖,形成闭环。例如,Bean A依赖于Bean B,而Bean B又依赖于Bean A。这种情况如果处理不当,会导致应用程序无法正常启动。

形成原因

  • 构造函数注入:当使用构造函数注入时,若存在循环依赖,则会在创建Bean的过程中导致无限递归调用,最终抛出StackOverflowError异常。这是因为Spring无法在一个Bean完全创建之前将其传递给另一个Bean。(这种情况通常无法自动解决,对于这种情形,Spring建议使用setter注入代替构造器注入,或者考虑重新设计你的类结构以避免循环依赖。)

  • 属性依赖(Setter方法注入):对于属性依赖或setter方法注入的情况,尽管Spring能够处理单例模式下的循环依赖问题,但这也可能导致系统复杂性增加和难以维护。

为什么在java中对象中属性依赖不会出现循环依赖的现象而在spring就会出现循环依赖? 

普通Java对象:

在普通的Java编程中,当你使用new关键字创建一个对象时,该对象立即就可以被使用了。这意味着,即使两个对象相互依赖(比如A依赖B,同时B也依赖A),只要你在初始化这些对象时能够合理安排它们的构造顺序,并通过setter方法或者构造函数参数等方式设置依赖关系,就能避免循环依赖带来的问题。

Spring Bean:

然而,在Spring框架中,Bean的创建和管理涉及到更复杂的生命周期过程。Spring默认是按照单例模式来管理Bean的,这意呸着每个Bean在IoC容器中只有一个实例。Spring Bean的生命周期包括实例化、属性填充(设置依赖)、初始化等阶段。如果两个或多个Bean之间存在循环依赖,特别是在属性填充阶段需要完全解析所有依赖的情况下,就会导致问题。

Spring解决循环依赖的方式

  1. 三级缓存机制:Spring使用了三个主要的缓存来处理和解决循环依赖的问题:

    • singletonObjects(一级缓存):存储已经完全初始化好的单例Bean。
    • earlySingletonObjects(二级缓存):存储早期暴露的单例对象,这些对象可能还未完成属性填充。
    • singletonFactories(三级缓存):存储Bean工厂对象,用于创建早期暴露的单例对象。
  2. 具体解决流程

    • 当Spring尝试创建一个单例Bean A时,它首先会检查一级缓存singletonObjects是否已经有这个Bean。如果没有,则开始实例化Bean A,并将半成品的A放入三级缓存singletonFactories中。
    • 如果在创建A的过程中发现需要另一个Bean B,Spring会尝试去获取B。如果B也存在循环依赖,即B也需要A才能完成初始化,那么此时Spring会从三级缓存中取出A的工厂,利用它创建一个不完全的A实例并将其放入二级缓存earlySingletonObjects中。
    • 接下来,当Spring继续创建B并尝试注入A时,它会在一级缓存中找不到A,然后检查二级缓存earlySingletonObjects。由于前面步骤中已经把A放到了二级缓存里,所以现在可以从这里拿到A的引用并完成B的创建。
    • 最后,当B创建完成后,返回到A的创建过程中,这时A也能顺利完成了,因为它所需的B已经被正确创建并注入。

注意事项

  • 构造器注入不支持循环依赖:Spring不能解决通过构造函数进行注入时产生的循环依赖问题,这是因为构造函数注入要求在创建Bean之前就必须满足所有依赖条件,而循环依赖违反了这一原则。

  • 非单例作用域不支持循环依赖:Spring只对单例作用域内的Bean提供了循环依赖的支持,对于原型(Prototype)或其他非单例作用域的Bean,Spring不会尝试解决它们之间的循环依赖问题。

 补充:

@DependsOn与循环依赖

假设你有两个Bean A和B,并且A依赖于B,同时B也依赖于A。如果你尝试使用@DependsOn来管理它们的初始化顺序,这将导致问题,因为这种配置会创建一个无法解决的循环依赖关系:

@Component
@DependsOn("beanB")
public class BeanA {public BeanA(BeanB beanB) {// Constructor injection of BeanB into BeanA}
}@Component
@DependsOn("beanA")
public class BeanB {public BeanB(BeanA beanA) {// Constructor injection of BeanA into BeanB}
}

在这种情况下,Spring容器在尝试初始化这些Bean时将会陷入困境,因为它无法决定先初始化哪一个Bean。

解决方案

  1. 重构代码以避免直接循环依赖:最直接的方法是重构你的代码,消除直接的循环依赖。可以通过提取共同依赖到一个新的Bean中,或者使用事件、回调等机制间接地建立依赖关系。

  2. 使用Setter注入而非构造器注入:如果设计允许的话,可以考虑使用Setter注入而不是构造器注入。Spring能够更好地处理通过setter方法建立的循环依赖,因为它可以在对象实例化后设置属性。

  3. 应用延迟加载(Lazy Initialization):对于某些场景,你可以声明一个Bean为懒加载(@Lazy),这样只有在实际需要该Bean的时候才会进行初始化。这种方法有时可以帮助缓解循环依赖的问题。

  4. 谨慎使用@DependsOn:尽量只在确实需要控制初始化顺序的情况下使用@DependsOn,并且确保这样做不会引入循环依赖。在多数情况下,Spring的自动装配机制已经足够智能,不需要额外指定依赖顺序。

记住,虽然Spring提供了一些工具和技术来处理循环依赖,但最好的做法还是尽量设计你的应用程序以避免出现循环依赖的情况。这样不仅可以简化配置,还可以提高代码的可维护性和清晰度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PDF转换工具xpdf-tools-4.05

XPDF是一个开源的PDF查看、提取和转换工具套件,使用C编写,支持多种操作系统,包括Linux、Unix、OS/2、Windows和Mac OS X‌1。XPDF不仅是一个PDF查看器,还包含多个实用工具,如文本提取器、图像转换器和HTML转换器等‌&a…

Vivado FPGA 开发 | 创建工程 / 仿真 / 烧录

注:本文为 “Vivado FPGA 开发 | 创建工程 / 仿真 / 烧录” 相关文章合辑。 略作重排,未整理去重。 如有内容异常,请看原文。 Vivado 开发流程(手把手教学实例)(FPGA) 不完美先生 于 2018-04-…

【PINN】DeepXDE学习训练营(13)——operator-antiderivative_aligned.py

一、引言 随着人工智能技术的飞速发展,深度学习在图像识别、自然语言处理等领域的应用屡见不鲜,但在科学计算、工程模拟以及物理建模方面,传统的数值方法仍然占据主导地位。偏微分方程(Partial Differential Equations, PDEs&…

NPP库中libnppc模块介绍

1. libnppc 模块简介 libnppc 是 NVIDIA Performance Primitives (NPP) 的 核心基础模块,提供跨模块的通用数据类型、内存管理和基础运算功能,不直接实现图像或信号处理算法,而是为其他模块(如 libnppi、libnpps)提供支持。 主要功能包括: 基础数据类型定义(如 Npp8u、…

#基于PCL的多平面分割系统技术文档

多平面分割系统技术文档 一、数据结构定义 /*** @brief 平面分割结果结构体* @param coefficients 平面方程系数(ax+by+cz+d=0)* @param cloud 属于该平面的点云子集* @param centroid 平面质心坐标(齐次坐标)* @param plane_id 平面编号(主平面为1)*/ struct PlaneSegmentRes…

PyTorch_张量拼接

张量的拼接操作在神经网络搭建过程中是非常常用的方法,例如:残差网络,注意力机制中都使用张量拼接。 torch.cat 函数的使用 可以将两个张量根据指定的维度拼接起来。 import torch import numpy as np def test01():data1 torch.randint(…

【SQL触发器、事务、锁的概念和应用】

【SQL触发器、事务、锁的概念和应用】 1.触发器 (一)触发器概述 1.触发器的定义 触发器(Trigger)是一种特殊的存储过程,它与表紧密相连,可以是表定义的一部分。当预定义的事件(如用户修改指定表或者视图中的数据)发生时,触发器会自动执行。 触发器基于一个表创建,…

设计模式每日硬核训练 Day 17:中介者模式(Mediator Pattern)完整讲解与实战应用

🔄 回顾 Day 16:责任链模式小结 在 Day 16 中,我们学习了责任链模式(Chain of Responsibility Pattern): 将请求沿链传递,节点可选择处理或传递下一节点。实现了请求发送者与多个处理者的解耦…

如何提升个人情商?

引言 提升个人情商(EQ)是一个持续的自我修炼过程,涉及自我认知、情绪管理、人际沟通等多个方面。以下是一些具体且可实践的方法,帮助你逐步提升情商: 一、提升自我觉察能力 1. 记录情绪日记 每天回顾自己的情绪…

STM32Cube-FreeRTOS任务调度与任务管理-笔记

STM32Cube-FreeRTOS任务调度与任务管理-笔记 一、任务调度机制1.1 调度算法类型 二、抢占式调度实现与分析2.1 时间片轮转机制2.2 调度触发条件2.3 抢占式调度例子 三、合作式调度实现3.1 核心逻辑3.1 合作式调度例子 四、任务管理函数详解4.1 任务创建4.1.1 动态创建任务4.1.2…

对称加密算法(AES、ChaCha20和SM4)Python实现——密码学基础(Python出现No module named “Crypto” 解决方案)

文章目录 一、对称加密算法基础1.1 对称加密算法的基本原理1.2 对称加密的主要工作模式 二、AES加密算法详解2.1 AES基本介绍2.2 AES加密过程2.3 Python中实现AES加密Python出现No module named “Crypto” 解决方案 2.4 AES的安全考量 三、ChaCha20加密算法3.1 ChaCha20基本介…

MATLAB图像加密案例

下面是一个使用 MATLAB 编写的简单图像块置乱加密/解密程序,主要利用了函数来组织代码。 这个程序通过将图像分割成小块,然后根据一个密钥(用于随机数生成器种子)打乱这些块的顺序来实现加密。解密过程则使用相同的密钥恢复原始块顺序。 核心思想: 分块: 将图像划分为 …

阿里云服务器全栈技术指导手册(2025版)

阿里云服务器全栈技术指导手册(2025版) 一、基础配置与核心架构设计 1. 精准实例选型策略 • 通用计算场景:选择ECS通用型(如ecs.g7)实例,搭载第三代Intel Xeon处理器,适合Web应用、中小型数…

word批量转pdf工具

word批量转pdf工具 图片 说到了办公,怎能不提PDF转换哦? 这是一款一键就可以批量word转换为PDF的小工具,简直是VB界的一股清流。 图片 操作简单到不行,只要把需要转换的word文件和这个工具放在同一个文件夹里,双击…

C++类_协变返回类型

协变返回类型定义 在 C11 中,协变返回类型是指在基类和派生类的虚函数重写时,派生类中重写的虚函数的返回类型可以是基类中对应虚函数返回类型的派生类型。也就是说,当基类的虚函数返回一个基类指针或引用时,派生类中重写该虚函数…

补充:建立实体类与数据表的映射关系

目录 前言 1 成员变量数据类型和字段类型 保持对应 2 成员变量的变量名,应采用小驼峰命名法 3 数据表中的主键如id 应采用自增方式 4 数据表中的时间类型数据与实体类中的时间类型的变量无法实现自动映射可以使用JsonFormat 注解 4.1 配置全局时间处理器&#x…

HTML/CSS 魔法第二弹:会逃跑的调皮按钮(悬停自动闪避)

引言 在网页设计中,交互性是吸引用户的关键因素之一。普通的按钮在用户悬停时可能只是颜色或样式发生改变,但今天我们要创造一个 “调皮” 的按钮,当用户鼠标悬停在上面时,它会自动闪避,仿佛在和用户玩游戏。本文将详…

**Java面试:技术大比拼**

互联网大厂Java面试:一场严肃与搞笑交织的技术拷问 场景:互联网大厂面试间 面试官(严肃):请坐。马小帅,我们今天主要考察一下你在Java技术栈上的掌握程度,以及如何将这些技术应用于实际业务场景…

25考频高的前端面试题

请求失败会弹出一个toast,如何保证批量请求失败,只弹出一个toast 设置全局标志位,定义一个全局变量(如isToastShown)来表示是否已经弹出过toast。在请求失败的处理逻辑中,首先检查该标志位。如果尚未弹出toast&#xf…

命令模式(Command Pattern)详解

文章目录 1. 什么是命令模式?2. 为什么需要命令模式?3. 命令模式的核心概念4. 命令模式的结构5. 命令模式的基本实现5.1 简单的灯光控制示例5.2 家电控制示例6. 带有撤销功能的命令模式6.1 修改命令接口6.2 实现可撤销的灯光命令6.3 实现可撤销的风扇命令6.4 修改调用者,支持…