高频面试题:解决Spring框架中的循环依赖问题

引言:什么是Spring框架与循环依赖?

在Spring框架中,循环依赖是指两个或多个bean相互依赖对方以完成自己的初始化。这种依赖关系形成了一个闭环,导致无法顺利完成依赖注入。比如,如果Bean A在其构造函数中需要Bean B,而Bean B同样在其构造函数中需要Bean A,Spring容器在初始化这两个Bean时就会陷入困境,因为它无法确定应该先初始化哪一个Bean。

循环依赖不仅会导致应用程序启动失败,还可能导致运行时异常,因此理解并解决此问题对于保障Spring应用的健壮性至关重要。

循环依赖的常见表现和影响

在Spring中,如果A Bean依赖B Bean,而B Bean同时依赖A Bean,就形成了一个循环依赖。这种情况在使用构造函数注入时尤为明显,因为每个Bean在构造时就需要依赖的Bean完全实例化。

循环依赖不仅影响应用启动,还可能隐藏代码设计上的问题,比如过度耦合。例如,在一个电商应用中,订单管理(OrderManager)依赖库存服务(InventoryService),而库存服务又依赖订单管理来处理库存锁定,这种设计就可能引发循环依赖问题。

解决循环依赖的方法和技巧

构造函数注入 vs. Setter注入

  • 构造函数注入:由于在构造函数注入时,需要在构造器调用前解析所有依赖,这种方法不支持循环依赖。
  • Setter注入:通过Setter注入依赖,可以在对象创建之后,完成属性的赋值,从而支持循环依赖的解决。

以下是一个简单的Spring Boot应用示例,展示如何使用Setter注入来解决循环依赖:

@SpringBootApplication
public class CircularDependencyApplication {

    public static void main(String[] args) {
        SpringApplication.run(CircularDependencyApplication.class, args);
    }

    @Bean
    public ClassA classA() {
        return new ClassA();
    }

    @Bean
    public ClassB classB() {
        return new ClassB();
    }
}

@Component
class ClassA {
    @Autowired
    private ClassB classB;

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }
}

@Component
class ClassB {
    @Autowired
    private ClassA classA;

    public void setClassA(ClassA classA) {
        this.classA = classA;
    }
}

使用@Lazy注解

@Lazy注解延迟Bean的加载时机。例如,在其中一个Bean的依赖中加入@Lazy,Spring将在首次使用这个Bean时才创建和注入,从而打破循环依赖。

@Component
public class ClassA {
    private final ClassB classB;

    @Autowired
    public ClassA(@Lazy ClassB classB) {
        this.classB = classB;
    }
}

@Component
public class ClassB {
    private final ClassA classA;

    @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }
}

三级缓存的概念及其原理

三级缓存是Spring用来解决循环依赖的一个机制。在Spring的bean生命周期中,容器通过使用三个缓存来管理bean的实例化过程,这些缓存分别是:

  • 一级缓存(singletonObjects):存放完全初始化好的bean。
  • 二级缓存(earlySingletonObjects):存放原始的bean实例(尚未填充属性)。
  • 三级缓存(singletonFactories):存放用于生成bean的工厂对象。

当Spring容器创建一个bean时,它会首先检查一级缓存,如果没有找到,它将创建一个新的bean实例,并将一个工厂对象放入三级缓存中。这个工厂对象负责生成和配置bean。如果在bean的初始化过程中需要依赖另一个bean(比如B依赖A),Spring容器会再次走这个创建过程。

如果A也需要B来完成其初始化,此时B的实例化可能还未完成,但通过三级缓存中的工厂对象,可以提前暴露一个原始的B实例给A使用,从而避免死锁。一旦B初始化完成,它就会从三级缓存移动到二级缓存,最终到达一级缓存。

三级缓存创建Bean的详细过程
步骤 1: 创建 Bean A
  • 当 Spring 容器开始创建 Bean A 时,首先检查 Bean A 是否已经存在于一级缓存中。如果不存在,Spring 容器开始创建 Bean A 的实例。
  • 在 Bean A 的完整属性注入和初始化之前,Spring 容器将一个用于创建 Bean A 的工厂对象放入三级缓存中。
步骤 2: Bean A 需要 Bean B
  • 在 Bean A 的初始化过程中,发现需要注入 Bean B。
  • Spring 容器此时开始创建 Bean B。与创建 Bean A 的过程类似,Spring 首先检查一级缓存。如果 Bean B 也不存在,容器继续进行创建。
步骤 3: 创建 Bean B
  • 在创建 Bean B 的过程中,容器同样将一个生成 Bean B 的工厂对象放入三级缓存中。
  • 如果 Bean B 的初始化同样需要依赖 Bean A,此时 Bean A 尚未完全初始化完成,因此不能从一级缓存中获取。
步骤 4: 循环依赖检测与解决
  • Bean B 在初始化过程中请求 Bean A。Spring 容器检查一级缓存未发现 Bean A,然后检查三级缓存。
  • 从三级缓存中找到生成 Bean A 的工厂对象,通过这个工厂对象提前暴露一个还未完全初始化的 Bean A 的引用,并将这个早期引用移至二级缓存。
  • Bean B 完成对 Bean A 的引用注入后,继续自己的初始化过程。一旦 Bean B 初始化完成,Bean B 的实例会被移至一级缓存,并从二级和三级缓存中清除。
步骤 5: 完成 Bean A 的初始化
  • 一旦 Bean B 完全初始化并存放在一级缓存中,Spring 容器回到 Bean A 的初始化过程。此时 Bean A 可以解析其对 Bean B 的依赖,因为 Bean B 已经在一级缓存中可用。
  • Bean A 完成所有依赖注入后,它的初始化也完成,然后它被移至一级缓存。

通过这种方式,Spring 的三级缓存机制有效地处理了循环依赖,允许两个互相依赖的 Bean 可以被正确地初始化和注入,避免了在依赖注入过程中发生的死锁或者缺失依赖的问题。这个机制是 Spring 容器高效处理复杂依赖关系的关键所在。下面提供一张示意图,帮助大家更好的理解三级缓存的初始化过程。
在这里插入图片描述

三级缓存的优势和适用场景

三级缓存提供了以下几个优势:

  • 解决循环依赖:允许在bean的依赖中引用尚未完全初始化的bean。
  • 提高灵活性:开发者可以设计更为复杂的bean依赖关系,不必过于担心初始化顺序。
  • 增强稳定性:减少因循环依赖引起的应用启动失败。

结论与最佳实践

在使用三级缓存时,开发者应该遵循以下最佳实践:

  • 避免不必要的依赖:尽管有三级缓存,也应尽量设计松耦合的系统。
  • 使用接口隔离:通过接口而非直接依赖具体类来减少代码之间的直接依赖。
  • 定期重构:随着应用的发展,应定期审视和重构代码,解决因历史原因形成的复杂依赖关系。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图像处理:乘法滤波器(Multiplying Filter)和逆FFT位移

一、乘法滤波器(Multiplying Filter) 乘法滤波器是一种以像素值为权重的滤波器,它通过将滤波器的权重与图像的像素值相乘,来获得滤波后的像素值。具体地,假设乘法滤波器的权重为h(i,j),图像的像素值为f(m,…

基于51单片机的电子秤LCD1602液晶显示( proteus仿真+程序+设计报告+讲解视频)

基于51单片机电子秤LCD显示 1. 主要功能:2. 讲解视频:3. 仿真设计4. 程序代码5. 设计报告6. 设计资料内容清单&&下载链接 基于51单片机电子秤LCD显示( proteus仿真程序设计报告讲解视频) 仿真图proteus8.9及以上 程序编译器&#xf…

NiceGUI:一个超赞的Python UI库

1. 引言 NiceGUI是一个基于Python的简单用户界面框架,可与浏览器或桌面应用程序流畅运行。无论你是制作小型网络应用程序、还是玩机器人项目,NiceGUI 都能以其简单的界面和众多的功能满足你的需求。这篇文章的目的是通过向大家展示如何构建和部署NiceGU…

如何选择适合自己需求的DC电源模块?

BOSHIDA 如何选择适合自己需求的DC电源模块? 在选择适合自己需求的DC电源模块时,需要考虑一些关键因素,以确保选择的模块能够满足电源要求并具有良好的性能。下面是一些值得考虑的因素: 1. 电压输出范围:首先&#xf…

短视频素材从哪里获取?推荐8个短视频素材高清网站

在这个视觉内容至关重要的数字化时代,高质量的视频素材是任何成功视频项目的核心。无论是加强品牌宣传、提升社交媒体互动还是制作引人注目的广告,这些精选的全球视频素材网站都将为你的创意注入活力,帮助你在激烈的市场竞争中脱颖而出。 1.…

2024LarkXR新增功能系列之六 | ⽀持8K分辨率

Paraverse平行云企业级实时云渲染解决方案LarkXR是平行云自主研发的CloudXR解决方案,在业界实现了创新突破。通过分钟级部署大规模云端资源、高度适配XR所有主流引擎、以及灵活支持不同交互和沉浸方式的内容形式,LarkXR解决了Cloud XR商业化过程中所面临…

Linux之进程间通信(二)

system V system V共享内存是内核中专门设计的通信的方式, 粗粒度划分操作系统分为进程管理, 内存管理, 文件系统, 驱动管理.., 粒度更细地分还有 进程间通信模块. 对于操作系统, 通信的场景有很多, 有以传送数据, 快速传送数据, 传送特定数据块, 进程间协同与控制以目的, 它…

SystemUI GlobalActions plugin解析

com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS 系统的默认实现为GlobalActionsImpl: 是谁发送了showShutdownUi指令? GlobalActionsImpl 是通过inject的方式创建的 GlobalActionsComponent是一个system UI services,配置在config.xml中&#xff…

Docker容器:网络模式与资源控制

目录 一、Docker 网络模式 1、Docker 网络实现原理 2、Docker 网络模式概述 2.1 Host 模式 2.2 Container 模式 2.3 None 模式 2.4 Bridge 模式 2.5 自定义网络(user-defined network) 3、配置 docker 网络模式 3.1 查看网络基础命令 3.1.1 查…

“怡宝”冲刺港股,饮用水基本盘稳如磐石

最近,饮用水市场异常热闹。 先是“怡宝”所属的华润饮料正式向港交所提交上市申请。随即,多名农夫山泉员工在朋友圈发文“推出绿瓶纯净水”,撞脸怡宝经典包装。“怡宝”遭遇奇袭的背后,是双方持续“交锋”的多年,随着…

Vue从入门到精通-01-Vue的介绍和vue-cli

MVVM模式 Model:负责数据存储 View:负责页面展示 View Model:负责业务逻辑处理(比如Ajax请求等),对数据进行加工后交给视图展示 关于框架 为什么要学习流行框架 1、企业为了提高开发效率:…

【Harmony3.1/4.0】笔记三-计算器

概念 网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器等…

python-pytorch 如何使用python库Netron查看模型结构(以pytorch官网模型为例)0.9.2

Netron查看模型结构 参照模型安装Netron写netron代码运行查看结果需要关注的地方 2024年4月27日14:32:30----0.9.2 参照模型 以pytorch官网的tutorial为观察对象,链接是https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html 模型代…

基于Springboot的新生宿舍管理系统

基于SpringbootVue的新生宿舍管理系统的设计与实现 开发语言:Java数据库:MySQL技术:SpringbootMybatis工具:IDEA、Maven、Navicat 系统展示 用户登录 首页 公告信息管理 院系管理 班级管理 学生管理 宿舍信息管理 宿舍安排管理…

清华军团推出中国首个对标Sora的视频大模型Vidu,扒一扒它背后的模型架构

就在前天,Vidu 在 2024 中关村论坛年会之中横空出世。 伴随着“中国首个”,“Sora 级视频模型”,“模拟真实的物理世界”等关键词下的刷屏式的报道,Vidu 一下成为国产视频模型的一剂强心针。 尽管目前 Vidu 支持的视频长度是 16 …

二叉树理论和题目

二叉树的种类 在我们解题过程中二叉树有两种主要的形:满二叉树和完全二叉树。 满二叉树 满二叉树:如果一棵二叉树只有度为0的结点和度为 2 的结点,并且度为 0 的结点在同一层上,则这棵二叉树为满二叉树。 这棵二叉树为满二叉树…

vscode的终端区乱码怎么办呢?

vscode的终端区乱码怎么办呢? 错误效果解决办法一解决办法二(极力推荐方法二)最终效果参考文献 错误效果 解决办法一 解释:你之所以使用了utf8还乱码,是因为你的电脑目前根本无法兼容utf8,只兼容gbk 怎么让你的电脑兼容utf8,我写在方法二 在设置中,输入encoding 解决办法二(极…

水稻病害检测(YOLO数据集,多分类,稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫)

是自己利用LabelImg工具进行手工标注,数据集制作不易,请尊重版权(稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫) 如果需要yolv8检测模型和数据集放在一起的压缩包,可以关注:最新最…

求解约瑟夫问题

思路: 我们要创建两个指针 有一个指针pcur指向头结点,该pcur作为报数的指针,还有一个指针ptail指向尾结点,作为记录pcur的地址 每报数为m时,pcur指向下一个元素的地址,ptail销毁报数为m的地址&#xff0…

分光光度法基本原理与应用

本文介绍分光光度法基本原理与应用。 分光光度法是分光光度计采用的方法,在医疗检测仪器,实验室测量仪器中经常使用。本文简要分析其原理,并给出实际工作过程中如何应用及应用过程中可能的误差来源。 1.基本概念 设一平行单色光垂直照射某…