高频面试题:解决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);}@Beanpublic ClassA classA() {return new ClassA();}@Beanpublic ClassB classB() {return new ClassB();}
}@Component
class ClassA {@Autowiredprivate ClassB classB;public void setClassB(ClassB classB) {this.classB = classB;}
}@Component
class ClassB {@Autowiredprivate 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;@Autowiredpublic ClassA(@Lazy ClassB classB) {this.classB = classB;}
}@Component
public class ClassB {private final ClassA classA;@Autowiredpublic 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依赖关系,不必过于担心初始化顺序。
  • 增强稳定性:减少因循环依赖引起的应用启动失败。

结论与最佳实践

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

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

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

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

相关文章

Linux(Centos 7)环境下安装wget,并且更换阿里云镜像

Linux(Centos 7) Minimal 安装后,由于没有预装wget,在使用wget命令去下载安装相关应用时,提示:“wget: command not found” 先在Linux服务器窗口中,输入如下命令,检查Linux服务器有没有安装过wget。 rpm -…

第一篇【AI与传奇开心果系列】Python的AI相关库技术点案例示例:详解AI作画原理

AI与传奇开心果博文系列 系列博文目录Python的AI相关库技术点案例示例系列 博文目录前言一、AI作画算法原理介绍二、深度学习的神经网络AI作画算法原理应用示例代码三、特征学习AI作画算法原理应用示例代码四、风格迁移AI作画算法原理应用示例代码五、损失函数AI作画算法原理应…

软件测试之【合理的利用GPT来辅助软件测试一】

读者大大们好呀!!!☀️☀️☀️ 🔥 欢迎来到我的博客 👀期待大大的关注哦❗️❗️❗️ 🚀欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言GPT的原理及技巧GPT辅助接口自动化测试 前言 在编程基础栏目中&#xff…

就业班 第三阶段(tomcat) 2401--4.28 day1 tomcat1安装配置及单机多实例

企业 Tomcat 运维 文章目录 企业 Tomcat 运维一、Tomcat 简介1、Tomcat好帮手---JDK2、安装Tomcat & JDK1、系统环境说明2 、安装JDK3、安装Tomcat 二、Tomcat目录介绍1、tomcat主目录介绍2、webapps目录介绍3、Tomcat配置介绍(conf)4、Tomcat的管理…

什么是中间件?中间件有哪些?

什么是中间件? 中间件(Middleware)是指在客户端和服务器之间的一层软件组件,用于处理请求和响应的过程。 中间件是指介于两个不同系统之间的软件组件,它可以在两个系统之间传递、处理、转换数据,以达到协…

将要上市的自动驾驶新书《自动驾驶系统开发》中摘录片段

全书共分15章:第1章是自动驾驶系统的概述(场景分类、开发路径和数据闭环等),第2章简介自动驾驶的基础理论,即计算机视觉和深度学习等,第3~4章是自动驾驶的软硬件平台分析,包括传感器…

Android --- 常见UI组件

TextView 文本视图 设置字体大小:android:textSize"20sp" 用sp 设置颜色:android:textColor"#00ffff" 设置倍距(行距):android:lineSpacingMultiplier"2" 设置具体行距:android:lineSpacingExtra&q…

centos 7 yum install -y nagios

centos 7 systemctl disable firewalld --now vi /etc/selinux/config SELINUXdisabled yum install -y epel-release httpd nagios yum install -y httpd nagios systemctl enable httpd --now systemctl enable nagios --now 浏览器 IP/nagios 用户名:…

大白菜启动U盘想格式化但格式化不了

部分区域被修改分区表保护起来了。直接格式化的话,里面的文件夹都还在。根本格式化不了。特别是可用容量并未还原出来。 进入计算机管理》磁盘管理,看到U盘盘符。别搞错了。删除掉里面的已经分的区域和未分区区域,让它还原成一个整体。退出。…

「C++ 内存管理篇 1」C++动态内存分配

目录 〇、C语言的动态内存分配方式 一、C的动态内存分配方式 1. 什么是C的动态内存分配? 2. 为什么需要C的动态内存分配? a. new的优势 b. new的不足 c. delete的优势 d. 总结 3. 怎么使用new和delete? a. 对于内置类型 b. 对于自定义类型 c. 为什么ne…

prime1--vulnhub靶场通关教程

一. 信息收集 1. 探测目标主机IP地址 arp-scan -l //查看网段 vm 编辑--查看虚拟网络编辑器,看到靶机的网段 网段是: 192.168.83.0 是c段网络 2. 全面检测目标IP nmap -sP 192.168.83.1/24 靶机ip是: 192.168.83.145 攻击机的ip是&…

【Java难点】多线程终极

悲观锁和乐观锁 悲观锁 synchronized关键字和Lock的实现类都是悲观锁。 它很悲观,认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会一不做二不休的先加锁,确保数据不会被别的线程修改。 适合写操作多的场景&…

【SpringBoot】00 Maven配置及创建项目

一、Maven配置 1、下载Maven 进入官网下载:Maven – Welcome to Apache MavenMaven – Download Apache Maven 本文以最新版为例,可按需选择版本 Maven – Welcome to Apache Maven 2、解压下载好的安装包 将安装包解压到自己设置的空文件夹中 3、…

7.Prism框架之对话框服务

文章目录 一. 目标二. 技能介绍① 什么是Dialog?② Prism中Dialog的实现方式③ Dialog使用案例一 (修改器)④ Dialog使用案例2(异常显示窗口) 一. 目标 1. 什么是Dialog?2. 传统的Dialog如何实现?3. Prism中Dialog实现方式4. 使用Dialog实现一个异常信息弹出框 二. 技能介…

《HCIP-openEuler实验指导手册》1.3Apache动态功能模块加载卸载练习

1.3.1 配置思路 mod_status 模块可以帮助管理员通过web界面监控Apache运行状态,通过LoadModule指令加载该模块,再配置相关权限,并开启ExtendedStatus后,即可使用该模块。 1.3.2 配置步骤 检查mod_status模块状态(使…

C# Solidworks二次开发:访问平面、曲面相关API详解

大家好,今天要介绍的是关于平面、曲面相关的API。 下面是相关的API: (1)第一个为ISurfacePlanarFeatureData,这个API的含义为允许访问平面表面特征,下面是官方的具体解释: 下面是官方使用的例子&#xff…

【网络原理】IP协议的地址管理和路由选择

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序(万字博文) 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法) 【网络…

【网络原理】TCP协议的连接管理机制(三次握手和四次挥手)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】网络编程中的基本概念及Java实现UDP、TCP客户端服务器程序(万字博文) 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制(CRC算法、MD5算法) 【网络…

扭蛋机小程序带来了什么优势?扭蛋机收益攻略

在当下的潮流消费时代,人们对潮玩也日益个性化,扭蛋机作为一种新型的娱乐消费模式,深受大众喜爱。扭蛋机的价格低,各个年龄层的玩家都可以进行购买,潜在玩家量非常大。扭蛋机商品主打热门IP周边等,种类繁多…

大型零售企业,适合什么样的企业邮箱大文件解决方案?

大型零售企业通常指的是在全球或特定地区内具有显著市场影响力和知名度的零售商。这些企业不仅在零售业务收入上达到了惊人的规模,而且在全球范围内拥有广泛的销售网络和实体店铺。它们在快速变化的零售行业中持续创新,通过实体店、电商平台等多种渠道吸…