spring 解决循环依赖

news2024/11/15 4:56:31

spring 解决循环依赖

1、什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

在这里插入图片描述

spring的单例对象的初始化主要分为三步:

1、实例化:其实也就是调用对象的构造方法实例化对象。
2、注入:填充属性,这一步主要是对bean的依赖属性进行填充。
3、初始化:属性注入后,执行自定义初始化。

2、Spring为了解决单例的循环依赖问题,使用了三级缓存

private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

这三级缓存分别指:
singletonObjects:单例对象的cache,一级缓存。

earlySingletonObjects :提前暴光的单例对象的Cache,二级缓存。

singletonFactories : 单例对象工厂的cache,三级缓存。
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就是:

 @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized(this.singletonObjects) {
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:
从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

在这里插入图片描述

3.1、单例的setter注入

@Service
public class ServiceA {

	@Autowired
	private ServiceB serviceB;
}

@Service
public class ServiceB {

	@Autowired
	private ServiceA serviceA;
}

下面用一张图告诉你,spring是如何解决循环依赖的:

在这里插入图片描述

3.2、多例的setter注入

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceA {

	@Autowired
	private ServiceB serviceB;
}

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceB {

	@Autowired
	private ServiceA serviceA;
}

可能你会认为这种情况spring容器启动会报错,其实是不会,为什么呢?其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了beanFactory.preInstantiateSingletons()方法:能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。如何让它提前初始化bean呢?只需要再定义一个单例的类,在它里面注入serviceA:

@Service
public class ServiceC {

    @Autowired
    private ServiceA serviceA;
}

重新启动程序,执行结果:果然出现了循环依赖。

注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

3.3、构造器注入

@Service
public class ServiceA {

	public ServiceA(ServiceB serviceB) {
	}
}

@Service
public class ServiceB {

	public ServiceB(ServiceA serviceA) {
	}
}

出现了循环依赖,为什么呢?看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

3.4、单例的代理对象setter注入

这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。

@Service
@EnableAsync
public class ServiceA {

	@Autowired
	private ServiceB serviceB;

	@Async
	public void test1() {
		System.out.println("async");
	}
}

@Service
public class ServiceB {

	@Autowired
	private ServiceA serviceA;
}

程序启动会报错,出现了循环依赖:

在这里插入图片描述

说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存是否存在, 代理对象和原始对象是否相等。到此发现第二级缓存存在并且代理对象和原始对象是不相等,因此抛出上面异常。如果这时候把ServiceA改个名字,改成:ServiceC,其他的都不变。

@Service
public class ServiceB {

	@Autowired
	private ServiceC serviceC;
}

@Service
@EnableAsync
public class ServiceC {

	@Autowired
	private ServiceB serviceB;

	@Async
	public void test1() {
		System.out.println("async");
	}
}


再重新启动一下程序,成功了,没有报错了!这又是为什么?
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以ServiceA比ServiceB先加载,而改了文件名称之后,ServiceB比ServiceC先加载。为什么ServiceB比ServiceC先加载就没问题呢?答案在下面这张图中:

在这里插入图片描述

这种情况ServiceC第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。

3.5、DependsOn循环依赖

@Service
@DependsOn("serviceB")
public class ServiceA {
	
}

@Service
@DependsOn("serviceA")
public class ServiceB {
	
}

这个例子中本来如果ServiceA和ServiceB都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。在AbstractBeanFactory类的doGetBean方法的这段代码中:初始化bean前,它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。

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

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

相关文章

DAC0832

芯片简介&#xff1a; DAC0832是采样频率为八位的D/A转换芯片&#xff0c;集成电路内有两级输入寄存器&#xff0c;使DAC0832芯片具备双缓冲、单缓冲和直通三种输入方式。D/A转换结果采用电流形式输出。若需要相应的模拟电压信号&#xff0c;可通过一个高输入阻抗的线性运算放…

Redmi Book Pro 15 2022 笔记本电脑更换硬盘总结

我的电脑型号是Redmi Book Pro 15 2022 锐龙版&#xff0c;早在6月15日磁盘空间就告急了&#xff0c;正好遇上618就打算更换硬盘&#xff0c;直到今天更换成功&#xff0c;特此来记录一下 目录 一、更换硬盘前准备工作二、更换硬盘三、硬盘迁移以及分区四、总结 本文的更换过程…

实验记录之——mac用visual studio code配置opencv

安装opencv 用Homebrew安装opencv brew install wget brew install cmake brew install opencv看来都安装过了hhh 通过下面命令再次检测安装的opencv版本 brew info opencv全部都打绿色的勾&#xff0c;应该就是都安装完了。 Vscode配置c/c环境 配置cpp项目可以看下面链接…

第一天,PyTorch张量的运算

文章目录 一、说明二、张量的常用运算1. 索引和切片&#xff08;与numpy类似&#xff09;2. 通过torch.cat来进行连接张量3. 矩阵运算和算数运算1. 在PyTorch中mul与matmul和的区别2. 矩阵运算和算数运算的示例 4. 聚合张量中的所有值5. 给所有元组增加1&#xff0c;使用add_&a…

Chrome 开发者调试常用工具

Chrome调试工具介绍 ①②③④⑤⑥⑦⑧⑨⑩ 一、Elements-元素 选项说明 styles filter搜索框&#xff1a;查找过滤样式&#xff0c; 包括class名当前选中的元素 查看其伪类效果已经样式给当前选中元素添加class名可以把当前元素class名或者id&#xff0c; 按照css层级添加一…

C语言:获得月份天数(多组输入)

题目&#xff1a; 描述 KiKi想获得某年某月有多少天&#xff0c;请帮他编程实现。输入年份和月份&#xff0c;计算这一年这个月有多少天。 输入描述&#xff1a; 多组输入&#xff0c;一行有两个整数&#xff0c;分别表示年份和月份&#xff0c;用空格分隔。 输出描述&#xff…

chatgpt赋能python:Python整除判断——一个不可缺少的技能

Python整除判断——一个不可缺少的技能 Python是一门广泛应用于科学计算、人工智能等多个领域的编程语言。在Python中&#xff0c;整除判断是一个非常基础但却不可或缺的技能。本篇文章将从基础开始介绍如何判断是否整除&#xff0c;以及如何在实际应用中使用整除判断。 整除…

PB9与Navicat Premium 16同时连接Oracle11g的连接问题

PB9与Navicat Premium 16同时连接Oracle11g的连接问题。 电脑安装的是win64_11gR2_database就是64的。 Navicat Premium 16其实是等oracle安装好后直接就能连了毫无问题。 pb9呢。因为需要32驱动的oci.dll&#xff0c;所以需要下载instantclient-basic-win32-11.1.0.7.0解压…

第七章 MobileNetv2网络详解

系列文章目录 第一章 AlexNet网络详解 第二章 VGG网络详解 第三章 GoogLeNet网络详解 第四章 ResNet网络详解 第五章 ResNeXt网络详解 第六章 MobileNetv1网络详解 第七章 MobileNetv2网络详解 第八章 MobileNetv3网络详解 第九章 ShuffleNetv1网络详解 第十章…

【C++篇】动态分配内存

友情链接&#xff1a;C/C系列系统学习目录 知识点内容正确性以C Primer&#xff08;中文版第五版&#xff09;、C Primer Plus&#xff08;中文版第六版&#xff09;为标准&#xff0c;同时参考其它各类书籍、优质文章等&#xff0c;总结归纳出个人认为较有逻辑的整体框架&…

EBU5476 Microprocessor System Design 知识点总结_6 Timer

Timer 想让程序定时运行&#xff0c;比如led 1s闪烁一次。如何做到&#xff1f; 第一种方法是愚蠢的delay延时&#xff0c;我自己估算一下&#xff1a;嗯&#xff0c;delay(2000)差不多1s。然后在程序中delay&#xff0c;点亮&#xff0c;delay&#xff0c;熄灭…… 太浪费资…

【C++篇】友元、运算符重载与多态

友情链接&#xff1a;C/C系列系统学习目录 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的…

【滤波】粒子滤波

%matplotlib inline#format the book import book_format book_format.set_style()动机 现在的问题是&#xff1a;我们要跟踪移动中的物体&#xff0c;也许这些物体是战斗机和导弹&#xff0c;或者是在场地里打球的人。但这都没有太大差别。那么&#xff0c;我们所学的滤波器中…

数据发布到WordPress网站,变为已计划、定时或Scheduled

采集数据发布到WordPress网站&#xff0c;但数据没有在前端页面显示&#xff0c;在wordpress后台查看变为已计划、定时或Scheduled。 这个是由于发布的时间变成未来时间导致的&#xff0c;先核实下设置的发布时间是否正确。 如果正确&#xff0c;那就是时区的问题导致时间变成…

Vue--》Vue3打造可扩展的项目管理系统后台的完整指南(六)

今天开始使用 vue3 ts 搭建一个项目管理的后台&#xff0c;因为文章会将项目的每一个地方代码的书写都会讲解到&#xff0c;所以本项目会分成好几篇文章进行讲解&#xff0c;我会在最后一篇文章中会将项目代码开源到我的GithHub上&#xff0c;大家可以自行去进行下载运行&…

翻过那座山——Gitlab流水线任务疑难之编译有子模块的项目指南

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不是…

【C/C++】详解 类和对象的概念、基本用法

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

2023-01-31 LightDB Agent安装.md

LightDB Agent安装 简介 LightDB Enterprise Manager(即 LightDB数据库监控管理平台&#xff0c;下文均简称为LightDB EM)是一个综合性的数据库监控和管理系统&#xff0c; 旨在满足数据库用户的需求&#xff0c;提供强大的图形界面&#xff0c;简化了对LightDB数据库的维护和使…

<C语言> 函数与递归

函数 1.函数的分类 库函数自定义函数 1.1 库函数 C语言提供了许多库函数&#xff08;library functions&#xff09;来简化开发过程并提供常用功能的实现。库函数是预先编写好的函数&#xff0c;可以通过调用这些函数来执行特定的任务。 为什么会有库函数&#xff1f; ​…

Docker中为RabbitMQ安装rabbitmq_delayed_message_exchange插件

Docker中为RabbitMQ安装rabbitmq_delayed_message_exchange插件 1、前言1、下载插件2、拷贝插件到RabbitMQ容器3、启用插件 1、前言 rabbitmq_delayed_message_exchange是一款向RabbitMQ添加延迟消息传递&#xff08;或计划消息传递&#xff09;的插件。 插件下载地址&#x…