Spring 循环依赖详解
Spring 循环依赖详解
1. 引言
在Spring框架中,依赖注入(Dependency Injection, DI)是其核心功能之一,它通过配置来管理对象的创建和它们之间的依赖关系。然而,在复杂的应用程序中,开发人员有时会遇到循环依赖的问题,即Bean A依赖于Bean B,而Bean B又依赖于Bean A。如果不加以处理,这种情况会导致应用程序无法启动。在本文中,我们将深入探讨Spring循环依赖的原理、处理机制、最佳实践以及可能遇到的问题。
2. 什么是循环依赖?
循环依赖是指两个或多个Bean相互依赖,形成一个闭环。例如,假设有两个Bean,Bean A和Bean B:
- Bean A依赖于Bean B
- Bean B依赖于Bean A
这种依赖关系就形成了一个循环,导致Spring容器在初始化Bean时无法确定哪个Bean应先创建。
3. Spring循环依赖的分类
根据依赖注入的方式不同,循环依赖可以分为以下几种类型:
3.1 构造器循环依赖
构造器循环依赖是指两个或多个Bean通过构造器参数相互依赖。例如:
public class BeanA {private final BeanB beanB;public BeanA(BeanB beanB) {this.beanB = beanB;}
}public class BeanB {private final BeanA beanA;public BeanB(BeanA beanA) {this.beanA = beanA;}
}
3.2 属性循环依赖
属性循环依赖是指两个或多个Bean通过属性相互依赖。例如:
public class BeanA {private BeanB beanB;public void setBeanB(BeanB beanB) {this.beanB = beanB;}
}public class BeanB {private BeanA beanA;public void setBeanA(BeanA beanA) {this.beanA = beanA;}
}
4. Spring如何解决循环依赖
Spring框架通过三级缓存(三级缓存机制)来解决大多数情况下的循环依赖问题。三级缓存机制包括以下三个层次:
- 单例池(singletonObjects):用于存放完全初始化好的单例Bean。
- 早期曝光对象池(earlySingletonObjects):用于存放部分初始化完成的单例Bean,通常是通过提前暴露Bean引用来解决循环依赖。
- 三级缓存(singletonFactories):用于存放Bean工厂,以便在需要时创建Bean实例。
4.1 三级缓存机制详解
4.1.1 单例池(singletonObjects)
单例池是一个Map,用于存放完全初始化好的单例Bean。当Spring容器创建一个Bean时,会首先检查单例池中是否已经存在该Bean,如果存在则直接返回,否则继续创建。
4.1.2 早期曝光对象池(earlySingletonObjects)
早期曝光对象池是一个Map,用于存放部分初始化完成的单例Bean。当Spring容器检测到循环依赖时,会将部分初始化完成的Bean提前放入该池中,以便其他Bean能够引用。
4.1.3 三级缓存(singletonFactories)
三级缓存是一个Map,用于存放Bean工厂。Bean工厂是一个用于创建Bean实例的对象,当需要创建Bean实例时,Spring容器会从三级缓存中获取相应的Bean工厂,并通过它来创建Bean实例。
4.2 三级缓存的工作流程
- Spring容器创建Bean A,首先检查单例池中是否存在Bean A。
- 如果单例池中不存在Bean A,则检查早期曝光对象池中是否存在Bean A。
- 如果早期曝光对象池中也不存在Bean A,则从三级缓存中获取Bean A的工厂,并通过该工厂创建Bean A的实例。
- 创建Bean A实例的过程中,发现Bean A依赖于Bean B,因此开始创建Bean B。
- 创建Bean B的过程中,发现Bean B依赖于Bean A,此时检测到循环依赖。
- 将Bean A的实例放入早期曝光对象池中,以便Bean B可以引用。
- 继续完成Bean B的创建,并将其放入单例池中。
- 返回Bean B的实例,继续完成Bean A的创建,并将其放入单例池中。
通过上述流程,Spring容器可以成功处理大多数情况下的循环依赖。
5. 实践中的循环依赖
5.1 避免构造器循环依赖
构造器循环依赖是无法通过Spring的三级缓存机制解决的,因为构造器循环依赖会导致Spring无法实例化任何一个Bean。解决这种问题的方法有:
- 重构代码,避免循环依赖。
- 使用Setter方法注入而不是构造器注入。
5.2 使用@Lazy注解
在某些情况下,可以使用@Lazy注解来延迟Bean的初始化,从而避免循环依赖。例如:
public class BeanA {@Autowired@Lazyprivate BeanB beanB;
}public class BeanB {@Autowiredprivate BeanA beanA;
}
5.3 使用代理对象
使用代理对象也是解决循环依赖的一种方法。Spring AOP(面向切面编程)通过动态代理机制创建Bean的代理对象,可以在一定程度上缓解循环依赖的问题。
6. Spring循环依赖的潜在问题
尽管Spring可以通过三级缓存机制解决大多数情况下的循环依赖,但在实际开发中,循环依赖仍可能导致一些潜在的问题:
- 代码难以维护:循环依赖会使代码逻辑复杂,增加代码的维护难度。
- 性能问题:频繁使用三级缓存可能会影响性能,特别是在Bean数量较多的情况下。
- 潜在的内存泄漏:不正确的依赖管理可能导致内存泄漏,从而影响应用程序的稳定性。
7. 总结
Spring循环依赖是一个复杂的问题,理解其工作原理和解决机制对于开发高质量的Spring应用程序至关重要。通过合理的设计和最佳实践,可以有效避免和解决循环依赖,确保应用程序的稳定性和可维护性。
在本篇文章中,我们深入探讨了Spring循环依赖的概念、分类、解决机制以及实际开发中的最佳实践。希望通过这些内容,能够帮助读者更好地理解和应对Spring循环依赖问题。
8. 扩展阅读
对于想要进一步深入了解Spring循环依赖的读者,可以参考以下资料:
- 《Spring实战》:本书详细介绍了Spring框架的核心概念和使用方法。
- Spring官方文档:Spring官方文档是了解Spring框架最新特性和最佳实践的重要资源。
- GitHub上的Spring源码:通过阅读Spring源码,可以深入了解Spring内部的工作机制和实现细节。
通过这些扩展阅读,读者可以进一步提高对Spring循环依赖的理解和应对能力。
相关文章:
Spring 循环依赖详解
Spring 循环依赖详解 1. 引言 在Spring框架中,依赖注入(Dependency Injection, DI)是其核心功能之一,它通过配置来管理对象的创建和它们之间的依赖关系。然而,在复杂的应用程序中,开发人员有时会遇到循环…...
项目经理真的不能太“拧巴”
前期的项目经理经常是“拧巴”的,就是心里纠结、思路混乱、行动迟缓。对于每天需要面对各种挑战、协调各方资源、确保项目顺利进行的项目经理来说,这种“拧巴”不仅会让自己陷入内耗中,还会让项目出大问题。 项目计划总是改来改去࿰…...
企业如何选择合适的CRM工具?除Salesforce之外的10大主流选择
对比salesforce,其他10款优秀CRM:纷享销客CRM、Zoho CRM、腾讯企点、销售易、企业微信 (WeCom)、Odoo CR、OroCRM、金蝶、用友CRM、EspoCRM 虽然Salesforce以其全面的功能和强大的市场占有率在海外收获了许多客户,但Salesforce在国内市场的接…...
每年1-1.2万人毕业,男女比例约3:1,测绘工程的就业率如何
测绘工程,一个让人闻风丧胆的理科专业,虎扑评分4.2: 干过测绘的,苦不苦只有大家心里知道,带大家来感受一下,兄弟们的精神状态都十分美妙: 测绘专业到底是什么情况? PS.测绘分为本科…...
JimuReport 积木报表 v1.7.6 版本发布,免费的低代码报表
项目介绍 一款免费的数据可视化报表工具,含报表和大屏设计,像搭建积木一样在线设计报表!功能涵盖,数据报表、打印设计、图表报表、大屏设计等! Web 版报表设计器,类似于excel操作风格,通过拖拽完…...
“灵活就业者“超两亿人 游戏开发者如何破局?
随着“灵活就业”者数量突破两亿,我相信“寒气”已经传递到每一位普通人!对于游戏行业的“灵活就业”者,应当如何破局? 首先应该恭喜大家,选择了一个相对“稳健”的行业,无论大环境如何,游戏/软…...
MySQL事务与存储引擎
一、事务的概念 是一种机制、一个操作序列,包含了一组数据库操作命令,并且把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行是一个不可分割的工作逻辑单元,在数据库…...
总是给数据库表字段设置默认值的好处
1、NOT NULL DEFAULT 的好处 在设计数据库表结构时,将字段设置为不能为空并设置默认值有以下几种好处: 1.1、数据完整性 通过设置字段不能为空,可以确保每条记录都包含必要的数据,从而保证了数据的完整性。例如,在用…...
11.2 Go 常用包介绍
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...
Sqlite3数据库基本使用
一、基本概念 数据:能够输入计算机并能被计算机程序识别和处理的信息集合 数据库:长期存储在计算机内、有组织的、可共享的大量数据的集合 DBMS:位于用户与操作系统之间的一层数据管理软件,用于操纵和管理数据库 二、安装 在线…...
实现贪吃蛇小游戏【简单版】
1. 贪吃蛇游戏设计与分析 1.1 地图 我们最终的贪吃蛇大纲要是这个样子,那我们的地图如何布置呢? 这里不得不讲⼀下控制台窗口的⼀些知识,如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介…...
uniapp实现内嵌其他网页的功能
一、用到的知识点 页面跳转页面间跳转,参数传递web-view使用 二、使用navigator 页面跳转。 navigator 组件类似HTML中的<a>组件,但只能跳转本地页面。目标页面必须在pages.json中注册。所以这么写是不行的: <navigator url&quo…...
【Ruby简单脚本01】查看wifi密码
脚本 # 使用io库 def get_cmd_result(cmd) IO.popen(cmd,:external_encoding>GBK).read.encode("utf-8") end def list_wifi wifi_pwds Hash.new # 获取所有wifi文件 o1 get_cmd_result("netsh wlan show profiles") # 获取所有匹配结果 …...
VSG/VSA 矢量信号模拟/分析软件
_Ceyear思仪 _ VSG/VSA 矢量信号模拟/分析软件 苏州新利通仪器仪表 在现代无线通信中,IQ调制属于标准配置,经常应用于通信系统的信号调制和解调环节。IQ调制的应用简化了通信设备的硬件结构,同时提高了频谱资源的利用效率,提…...
C++使用GDAL库完成tiff图像的合并
全色图 完整代码: #include "gdal_priv.h" #include "cpl_string.h" #include <vector> #include <algorithm> #include <iostream> #include <filesystem>using namespace std; namespace fs std::filesystem; vec…...
深入理解AQS:Java并发编程中的核心组件
目录 AQS简介AQS的设计思路AQS的核心组成部分 状态(State)同步队列(Sync Queue)条件队列(Condition Queue) AQS的内部实现 节点(Node)锁的获取与释放 独占锁共享锁 条件变量 AQS的应…...
集合进阶:List集合
一.List集合的特有方法 1.Collection的方法List都继承了 2.List集合因为有索引,所以多了很多索引操作的方法。 3.add // 1.创建一个集合List<String> list new ArrayList<>(); // 2.添加元素list.add("aaa");list.add("bbb"…...
el-table表头修改文字或者背景颜色,通过header-row-style设置样式
方式一 <el-table :header-cell-style"{text-align: center}" />方式二 <template><el-table :header-cell-style"tableHeaderColor" /> </template> <script> export default {methods: {tableHeaderColor ({row, column…...
web前端-CSS
CSS CSS概述: CSS是Cascading Style Sheets(级联样式表),是一种样式表语言,用于控制网页布局,外观(比如背景图片,图片高度,文本颜色,文本字体,高级定位等等) 可将页面的内容与样式分离开,样式放于单独的.css文件或者HTML某处 CSS是网页样式,HTML是网页…...
u8g2 使用IIC驱动uc1617 lcd 字符显示只显示上半部分,不显示下半部
使用u8g2 使用硬件iic驱动某些page为4个字节 带灰度的lcd显示屏幕的时候有时候只显示上半部,下半部不显示,例如uc1617等。 原因: 以uc1617为例,链接https://github.com/olikraus/u8g2/blob/master/csrc/u8x8_d_uc1617.c 在u8x8_d_uc1617_common方法中的case U8X8_MSG_DI…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权
摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题:安全。文章将详细阐述认证(Authentication) 与授权(Authorization的核心概念,对比传统 Session-Cookie 与现代 JWT(JS…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...
