一文彻底搞懂spring循环依赖

news2024/10/10 8:23:40

文章目录

  • 1. 什么是循环依赖
  • 2. Spring怎么解决循环依赖
  • 3. 无法处理的循环依赖

1. 什么是循环依赖

Spring 中的循环依赖是指两个或多个 Bean 之间相互依赖,形成一个循环引用的情况。在 Spring 容器中,循环依赖通常指的是单例(Singleton)作用域的 Bean 之间的循环引用。

循环依赖可能会导致以下问题:

1.提前暴露不完整的 Bean:如果 A 依赖于 B,而 B 又依赖于 A,那么在初始化过程中,A 可能会拿到一个尚未完成初始化的 B 对象,导致对象状态不完整或不一致。
2.无限循环:如果循环依赖链路过长或者存在循环引用关系,可能会导致 Bean 初始化的时候发生死循环,最终导致堆栈溢出。
在这里插入图片描述

2. Spring怎么解决循环依赖

Spring 解决循环依赖的机制主要基于三级缓存和提前曝露半初始化的 Bean 的思想。具体步骤如下:

1.实例化对象并放入缓存

当 Spring 容器创建 Bean 时,会先实例化对象,然后将对象放入第一级缓存(singletonObjects)中。此时对象还未完全初始化。

2.设置对象引用

Spring 将对象放入第二级缓存(earlySingletonObjects),并设置对象的引用。这时候对象已经可以被其他对象引用,但仍未完成初始化。

3.提前曝光半初始化的 Bean

如果 Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A,Spring 在创建 Bean A 时,会提前曝光一个半初始化的 Bean A 到第二级缓存中。这个半初始化的 Bean A 具有 Bean A 的代理对象,可以提供给 Bean B 使用。

4.完成 Bean 的初始化

Spring 继续初始化 Bean B,当 Bean B 初始化完成后,Spring 再回头来完成 Bean A 的初始化。这时,Bean A 已经可以通过代理对象访问到 Bean B。

5.将对象移至第三级缓存

当 Bean A 和 Bean B 都初始化完成后,Spring 将它们从第二级缓存移动到第三级缓存(singletonFactories)中,同时清除第一级和第二级缓存中的对象。

public class DefaultSingletonBeanRegistry {
    
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64); // 第一级缓存
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 第二级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 第三级缓存
    
    public Object getSingleton(String beanName) {
        // 1. 从第一级缓存中获取对象
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }
        
        // 2. 从第二级缓存中获取对象
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }
        
        // 3. 从第三级缓存中获取对象工厂,并使用工厂创建对象
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
            synchronized (this.singletonObjects) {
                singletonObject = singletonFactory.getObject();
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
            }
            // 4. 完成 Bean 的初始化
            initializeBean(beanName, singletonObject);
            // 5. 将对象移至第一级缓存
            addToSingletons(beanName, singletonObject);
            return singletonObject;
        }
        
        return null;
    }
    
    private void initializeBean(String beanName, Object singletonObject) {
        // 初始化 Bean 的逻辑,包括填充属性、调用初始化方法等
    }
    
    private void addToSingletons(String beanName, Object singletonObject) {
        this.singletonObjects.put(beanName, singletonObject);
    }
}

在这段代码中,模拟了 Spring 的 DefaultSingletonBeanRegistry 类,其中包含了三级缓存 singletonObjects、earlySingletonObjects 和 singletonFactories。当获取单例 Bean 时,首先会从第一级缓存中获取,如果没有找到则尝试从第二级缓存中获取,如果还没有则尝试从第三级缓存中获取对象工厂,并使用工厂创建对象。创建过程中会将对象暂时放入第二级缓存中,等待完成初始化后再移至第一级缓存中。

3. 无法处理的循环依赖

Spring 的循环依赖处理机制无法处理以下两种情况:

1.构造器循环依赖

如果 Bean A 的构造函数依赖于 Bean B,而 Bean B 的构造函数又依赖于 Bean A,则无法通过 Spring 的循环依赖处理机制解决。这是因为在创建 Bean 的过程中,构造函数的调用是在对象实例化之前发生的,此时无法确定构造函数所需的依赖对象是否已经创建,从而导致循环依赖无法被解决。

// BeanA.java
public class BeanA {
    private BeanB beanB;

    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

// BeanB.java
public class BeanB {
    private BeanA beanA;

    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}

BeanA 的构造函数依赖于 BeanB,而 BeanB 的构造函数又依赖于 BeanA,构成了构造器循环依赖。

2.原型 Bean 属性注入循环依赖

对于原型(prototype)作用域的 Bean,Spring 容器在创建时不会缓存对象实例,而是在每次请求时都会创建一个新的实例。因此,如果原型 Bean A 的某个属性依赖于原型 Bean B,而 Bean B 的某个属性又依赖于 Bean A,这种循环依赖无法通过 Spring 的循环依赖处理机制解决。这是因为 Spring 容器无法在创建原型 Bean 时提前暴露半初始化的对象,也无法缓存原型 Bean 的实例。

// PrototypeBeanA.java
@Scope("prototype")
public class PrototypeBeanA {
    private PrototypeBeanB beanB;

    public void setBeanB(PrototypeBeanB beanB) {
        this.beanB = beanB;
    }
}

// PrototypeBeanB.java
@Scope("prototype")
public class PrototypeBeanB {
    private PrototypeBeanA beanA;

    public void setBeanA(PrototypeBeanA beanA) {
        this.beanA = beanA;
    }
}

PrototypeBeanA 的属性 beanB 依赖于 PrototypeBeanB,而 PrototypeBeanB 的属性 beanA 又依赖于 PrototypeBeanA,构成了原型 Bean 属性注入循环依赖。

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

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

相关文章

使用 Idea 快速搭建 SpringMVC 项目的详细步骤

一、开篇 SpringMVC 是一款当下流行的优秀的 MVC 框架&#xff0c;关于 MVC 的概念、作用、优点等内容介绍&#xff0c;在作者之前的一篇 Chat 《深入理解 MVC 框架原理&#xff1a;自定义 Struts2 框架》中有详细的描述。描述了关于另一款主流 MVC 框架的原理介绍&#xff0c;…

Docker-Container

Docker ①什么是容器②为什么需要容器③容器的生命周期容器 OOM容器异常退出容器暂停 ④容器命令清单总览docker createdocker rundocker psdocker logsdocker attachdocker execdocker startdocker stopdocker restartdocker killdocker topdocker statsdocker container insp…

unrealbuildtool 无法找到,执行 Generate Visual Studio Project 错误

参考链接 Generate cpp project Couldnt find UnrealBuildTool - Pipeline & Plugins / Plugins - Epic Developer Community Forums (unrealengine.com) 错误提示如下图&#xff1a; 解决方案&#xff1a; 打开 UnrealBuildTool&#xff0c;生成解决方案就可以了

学习Fast-LIO系列代码中相关概念理解

目录 一、流形和流形空间&#xff08;姿态&#xff09; 1.1 定义 1.2 为什么要有流形? 1.3 流形要满足什么性质&#xff1f; (1) 拓扑同胚 (2) 可微结构 1.4 欧式空间和流形空间的区别和联系? (1) 区别&#xff1a; (2) 联系&#xff1a; 1.5 将姿态定义在流形上比…

【Python实用标准库】argparser使用教程

argparser使用教程 1.介绍2.基本使用3.add_argument() 参数设置4.参考 1.介绍 &#xff08;一&#xff09;argparse 模块是 Python 内置的用于命令项选项与参数解析的模块&#xff0c;其用主要在两个方面&#xff1a; 一方面在python文件中可以将算法参数集中放到一起&#x…

CavalierContours 二维线操作

CavalierContours 二维线操作 2D polyline library for offsetting, combining, etc. 用于偏移、交并补等组合等操作的 2D 多折段线库。 Polyline Structure 多段线结构 Polylines are defined by a sequence of vertexes and a bool indicating whether the polyline is cl…

关系型数据库mysql(7)sql高级语句①

目录 一.MySQL常用查询 1.按关键字&#xff08;字段&#xff09;进行升降排序 按分数排序 &#xff08;默认为升序&#xff09; 按分数升序显示 按分数降序显示 根据条件进行排序&#xff08;加上where&#xff09; 根据多个字段进行排序 ​编辑 2.用或&#xff08;or&…

【C语言】内存函数(memmove)的使用和模拟实现

目录 前言memmove定义1.在cplusplus中的定义 memmove的模拟实现1、思路2、难点3、解决方法 模拟实现代码 前言 这篇文章讲述了memcpy的使用、模拟实现和一个未解决的问题内存函数(memcpy)的使用和模拟实现 当我们使用我们模拟的my_memcpy拷贝&#xff0c;当源拷贝地址与目标拷…

C语言编译与链接

前言 我们想一个问题&#xff0c;我们写的C语言代码都是文本信息&#xff0c;电脑能直接执行c语言代码吗&#xff1f;肯定不能啊&#xff0c;计算机能执行的是二进制指令&#xff0c;所以将C语言转化为二进制指令需要一段过程&#xff0c;这篇博客讲一下编译与链接&#xff0c;…

ISAC代码仿真学习笔记

文章目录 A. MIMO Communication ModelB. MIMO Radar Model III. Joint Waveform and Phase Shift Matrix Design for Given Radar BeampatternA. Problem FormulationB. Proposed Algorithm IV. JOINT DESIGN WITH TRADE-OFF BETWEEN RADAR AND COMMUNICATION PERFORMANCEA. P…

boost::asio::ip::tcp/udp::socket::release 函数为什么限制 Windows 8.1 才可以调用?

如本文题目所示&#xff0c;这是因为只有在 Windows 8.1&#xff08;Windows Server 2012 RC&#xff09;及以上 Windows 操作版本才提供了运行时&#xff0c;修改/删除完成端口关联的ABI接口。 boost::asio 在 release 函数底层实现之中是调用了 FileReplaceCompletionInform…

Ubuntu20安装python3.10

1、添加 deadsnakes PPA 到源列表 add-apt-repository ppa:deadsnakes/ppa apt update 2、安装 apt install python3.10 3设置默认版本为 Python3.10 查看所有python版本 ls -l /usr/bin/python* update-alternatives --install /usr/bin/python3 python3 /usr/bin/pytho…

java-pytorch 使用手动下载FashionMNIST数据集进行测试

java-pytorch 使用手动下载FashionMNIST数据集进行测试 先定义训练数据和测试数据的位置查看一下读取到的标签数据格式使用loc和iloc访问下数据&#xff0c;便于下面操作使用read_image函数查看下图片的数据大小开始写数据集使用DataLoader去加载我们自己的数据看下加载后的dat…

亚马逊跨境电商迎来崭新时代,武汉星起航携手卖家共赴新征程

随着全球经济一体化的深入发展&#xff0c;跨境电商已成为推动国际贸易的重要力量。据最新数据显示&#xff0c;2023年中国跨境电商出口规模达到1.83万亿元&#xff0c;同比增长19.6%&#xff0c;增速远超电商行业整体水平。在这一背景下&#xff0c;2024年有望成为中国跨境电商…

ForkJoinPool、CAS原子操作

ForkJoinPool ForkJoinPool是由JDK1.7后提供多线程并行执行任务的框架。可以理解为一种特殊的线程池。 1.任务分割&#xff1a;Fork&#xff08;分岔&#xff09;&#xff0c;先把大的任务分割成足够小的子任务&#xff0c;如果子任务比较大的话还要对子任务进行继续分割。 …

构造器练习

练习一 题目 案例&#xff1a; (1)定义Student类,有4个属性&#xff1a;String name;int age;String school;String major;(2)定义Student类的3个构造器:- 第一个构造器Student(String n, int a)设置类的name和age属性&#xff1b; - 第二个构造器Student(String n, int a, St…

大话设计模式之策略模式

策略模式是一种行为设计模式&#xff0c;它允许在运行时选择算法的行为。这种模式定义了一族算法&#xff0c;将每个算法都封装起来&#xff0c;并且使它们之间可以互相替换。 在策略模式中&#xff0c;一个类的行为或其算法可以在运行时改变。这种模式包含以下角色&#xff1…

2024/3/29 IOday2

所有人&#xff0c;今日作业&#xff1a;用fwrite 和 fseek功能&#xff0c;将一张bmp格式的图片更改成 德国国旗 #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {FILE* fpfopen("./rising_free…

android 集合总结

1 集合分类&#xff0c; collection和map两大类,Iterator接口是提供遍历任何Collection的接口&#xff0c;不是map 2 集合类的底层实现 hashset基于hashmap实现&#xff08;只不过HashSet里面的HashMap所有的value都是同一个Object而已&#xff09; treeset由红黑树实现 …

基于STC12C5A60S2系列1T 8051单片机通过单个按键单击次数实现开关机应用

基于STC12C5A60S2系列1T 8051单片机通过单个按键单击次数实现开关机应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍基于STC12C5A60S2系列1T 8051单片机通过单个按…