(九)Spring之Bean的循环依赖问题

news2025/7/11 17:05:28

文章目录

  • 环境
  • 什么是Bean的循环依赖
  • singleton下的set注入产生的循环依赖
  • prototype下的set注入产生的循环依赖
  • 构造注入产生的循环依赖
    • singleton下的构造注入
    • prototype下的构造注入
  • Spring解决循环依赖的机理(底层实现)

上一篇:(八)Bean的生命周期

环境

spring6里程碑版本的仓库
依赖:spring context依赖、junit依赖、log4j2依赖
log4j2.xml文件放到类路径下。

什么是Bean的循环依赖

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。

singleton下的set注入产生的循环依赖

我们来编写程序,测试一下在singleton+setter的模式下产生的循环依赖,Spring是否能够解决?
创建Bean:丈夫类Husband

/**
 * 丈夫类
 */
public class Husband {
    private String name;
    private Wife wife;

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }
}

创建Bean:妻子类Wife

/**
 * 妻子类
 */
public class Wife {
    private String name;
    private Husband husband;

    public void setName(String name) {
        this.name = name;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

创建spring.xml配置:

    <bean id="husbandBean" class="com.circlar.dependency.bean.Husband" scope="singleton">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.circlar.dependency.bean.Wife" scope="singleton">
        <property name="name" value="李四"/>
        <property name="husband" ref="husbandBean"/>
    </bean>

测试程序:

    @Test
    public void testSingletonSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
    }

请添加图片描述
在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。、
怎么解决问题呢,先这样解释:(后面有底层解释,先这样理解)
Spring在解析xml文件的时候,遇见beanA,会先把beanA创建出来,然后发现beanA的属性需要beanB
然后Spring会去找beanB,发现beanB也是单例的,就会把对象先创建出来。
主要原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:

  • 第一阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化后,就马上进行 ”曝光“ 【不等待属性赋值就曝光】
  • 第二阶段:Bean ”曝光“ 之后,再进行属性赋值。

核心的原因就是:实例化对象和对象的属性赋值分为两个阶段完成。
在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。

prototype下的set注入产生的循环依赖

我们再来测试一下:prototype+set注入的方式下,循环依赖会不会出现问题?
还是那两个夫妻Bean
然后创建一个新的spring配置文件:spring1.xml

    <bean id="husbandBean" class="com.circlar.dependency.bean.Husband" scope="prototype">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.circlar.dependency.bean.Wife" scope="prototype">
        <property name="name" value="李四"/>
        <property name="husband" ref="husbandBean"/>
    </bean>

测试程序:

    @Test
    public void testPrototypeSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring1.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
    }

请添加图片描述
翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
会出现异常BeanCreationException,当前bean正处于创建中异常,
也就是说bean处于第二步(populateBean:填充属性)就卡住了
如果我们把其中一个Bean改为单例的试试

    <bean id="husbandBean" class="com.circlar.dependency.bean.Husband" scope="prototype">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.circlar.dependency.bean.Wife" scope="singleton">
        <property name="name" value="李四"/>
        <property name="husband" ref="husbandBean"/>
    </bean>

再次运行,发现运行正常。
请添加图片描述

构造注入产生的循环依赖

我们再来测试一下构造注入的方式下,spring是否能够解决这种循环依赖。
修改丈夫类:

/**
 * 丈夫类
 */
public class Husband {
    private String name;
    private Wife wife;

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }

    public String getName() {
        return name;
    }

    public Husband(String name, Wife wife) {
        this.name = name;
        this.wife = wife;
    }
}

修改妻子类:

/**
 * 妻子类
 */
public class Wife {
    private String name;
    private Husband husband;


    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

singleton下的构造注入

创建spring2.xml配置:

    <bean id="husband" class="com.circlar.dependency.bean2.Husband" scope="singleton">
        <constructor-arg index="0" value="张三"/>
        <constructor-arg index="1" ref="wife"/>
    </bean>

    <bean id="wife" class="com.circlar.dependency.bean2.Wife" scope="singleton">
        <constructor-arg index="0" value="李四"/>
        <constructor-arg index="1" ref="husband"/>
    </bean>

测试程序:

    @Test
    public void testSingletonConstructor(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
        Husband husband = applicationContext.getBean("husband", Husband.class);
        Wife wife = applicationContext.getBean("wife", Wife.class);
        System.out.println(husband);
        System.out.println(wife);
    }

出现异常
请添加图片描述

prototype下的构造注入

修改spring2.xml:

    <bean id="husband" class="com.circlar.dependency.bean2.Husband" scope="prototype">
        <constructor-arg index="0" value="张三"/>
        <constructor-arg index="1" ref="wife"/>
    </bean>

    <bean id="wife" class="com.circlar.dependency.bean2.Wife" scope="prototype">
        <constructor-arg index="0" value="李四"/>
        <constructor-arg index="1" ref="husband"/>
    </bean>

再次运行测试程序,发现出现异常:
请添加图片描述

主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。

Spring解决循环依赖的机理(底层实现)

Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,实例化Bean的时候:调用无参数构造方法来完成。对象的属性是可以延后设置的。
通过Bean的生命周期我们知道,Spring的单例对象完成初始化主要分为三步:

  • 第一步:实例化Bean,其实也就是调用对象的构造方法实例化对象
  • 第二步:Bean属性赋值 ,这一步主要是多bean的依赖属性进行填充
  • 第三步:Bean属性赋值 ,调用spring xml中的init 方法。

循环依赖主要发生在第一、第二步。
对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存)。
Spring为了解决单例的循环依赖问题,使用了三级缓存。
Spring在DefaultSingletonBeanRegistry这个类设置了三级缓存:

	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);

这三个Map的key都是存储Bean的id,
这三级缓存分别指:

  • 三级缓存:singletonFactories :存储单例对象工厂,每一个单例Bean对象都会对应一个单例工厂对象。存储创建该单例对象时对应的那个单例工厂对象。
  • 二级缓存:earlySingletonObjects :存储提前曝光的单例Bean对象,这个Bean只是完成了初始化第一步。
  • 一级缓存:singletonObjects:存储完整的单例Bean对象,这个Bean完成了初始化1、2、3步了。

首先在创建BeanA对象的时候,会把BeanA对象的工厂存入(曝光)在三级缓存里面。

解决循环依赖底层的一个代码是DefaultSingletonBeanRegistry的getSingleton方法:

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

Spring使用三个判断,分别从缓存的一级读取到三级:

  • 首先一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中。
  • 就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories获取
  • 就从三级缓存singletonFactory(三级缓存)获取

如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

那么BeanA和BeanB的循环依赖完整的解决是:

     beanA首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象beanB
     此时就尝试去获取beanB,发现beanB还没有被创建,所以走beanB创建流程,beanB在初始化第一步的时候发现自己依赖了对象beanA,
     于是尝试获取beanA,尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全)尝试二级缓存earlySingletonObjects(也没有),
     尝试三级缓存singletonFactories,由于beanA通过ObjectFactory将自己提前曝光了,所以beanB能够通过ObjectFactory拿到beanA对象
     beanB拿到beanA对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。
     此时返回beanA中,beanA此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终beanA也完成了初始化,进去了一级缓存singletonObjects中,
     于是beanB拿到了beanA的对象引用,所以beanB的beanA对象完成了初始化。

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

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

相关文章

注释写的好,文档不潦草.

大家好&#xff0c;long time no see&#xff01;这次聊一聊「注释」。写「注释」的好处众所周知&#xff0c;但有时在实现一些「公共代码」后&#xff0c;需要编写「文档」&#xff0c;其中「注释」和「文档」的内容是大致相同的&#xff0c;比如param和returns等(相信有不少同…

Java_抽象类和接口(一)

作者&#xff1a;爱塔居的博客_CSDN博客-JavaSE领域博主 专栏&#xff1a;JavaSE 作者简介&#xff1a;大三学生&#xff0c;希望跟大家一起进步&#xff01; 文章目录 目录 文章目录 一、抽象类 1.1 抽象类概念 1.2 抽象类语句 1.3 抽象类特性 1.4 抽象类和普通类的区别 1.5 抽…

赞不绝口!仅靠阿里P9分享的 Redis 工作手册,拿到60W年薪Offer

昨晚有六七位小伙伴告诉我说&#xff1a;“大佬&#xff0c;有没有Redis的面试教程啊&#xff0c;最近面试被问到好多” 这就帮小伙伴们专门整理了一份资料&#xff08;不仅仅是面试题&#xff09;&#xff0c;从Redis核心原理到Redis设计与源码帮助大家梳理体系&#xff0c;快…

教你自己写Arcpy批处理程序

自己写Arcpy批处理栅格和矢量 先上代码&#xff0c;讲解各行代码的意思&#xff0c;从而达到自己写代码的目的 #....Edit by Longhao Wang.... import arcpy from arcpy import env from arcpy.sa import * import os import os.path import sys arcpy.env.workspace"D:…

Vue项目开发经验

文章目录前言网页组件echarts使用打包后显示包体积安装tensorflow和anaconda可能出现的错误![在这里插入图片描述](https://img-blog.csdnimg.cn/c1facd95a7f645c5af3e8dc1237913a3.png)总结前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&…

《调试九法》阅读笔记

1. 理解系统 阅读手册、仔细阅读每个细节、掌握基础知识、了解工作流程、了解工具。 2. 制造失败 制造失败、从头开始、引发失败、但不要模拟失败、查找不受你控制的条件、记录每件事情&#xff0c;并找到间歇性bug特征、不要过于相信统计数据、要认识到“那”是可能会发生的…

[附源码]java毕业设计商场日常维修管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【云原生】k8s 中的 hostNetwork 和 NetworkPolicy(网络策略)讲解与实战操作

文章目录一、hostNetwork 介绍二、k8s 网络策略 NetworkPolicy三、Pod 隔离的两种类型四、NetworkPolicy 资源1&#xff09;NetworkPolicy 示例演示2&#xff09;选择器 to 和 from 的行为五、总结一、hostNetwork 介绍 在k8s中&#xff0c;若pod使用主机网络&#xff0c;也就是…

UE5笔记【四】UE5主材质Master Materials和材质实例MI

上一篇我们讲解了关于鹅卵石的纹理材质。 假设&#xff1a;如果我们在关卡中每个材质都这么连接的话&#xff0c;那么将使得整个世界非常复杂&#xff0c;并且将浪费大量的时间。对此&#xff0c;解决方案是&#xff1a;主材质&#xff1a;master Materials。 新建一个新关卡…

springboot基于java的个性化推荐的电商购物商城平台设计与实现

本文主要探讨了个性化推荐的电商平台的设计与实现。并对其详细的设计方案、实现技术和运行情况做了分析和研究&#xff0c;最后对未来的工作做了研究与探讨。本文重点研究了以下几个方面&#xff1a; &#xff08;1&#xff09;系统的体系结构、主要功能模块、主要数据的工作流…

次元裂缝已打开,AI绘画突飞猛进,其潜力究竟有多大

目录 次元裂缝已打开 AI绘画 起源 人工智能画的画在美术比赛得第一名 原理 关键的CLIP 总结 次元裂缝已打开 #次元裂缝已打开#的一个话题火了~大量新人老玩家共赴无界AI 上面是AI绘画根据真实图片合成的图片与真图的对比&#xff0c;可以看出还原度还是很高的&#xff…

JUC学习笔记——共享模型之无锁

在本系列内容中我们会对JUC做一个系统的学习&#xff0c;本片将会介绍JUC的无锁 我们会分为以下几部分进行介绍&#xff1a; 无锁操作CAS与Volatile原子类型原理篇Unsafe 并发无锁操作 这一小节我们将讲解如何用无锁操作完成并发操作 问题展现 我们给出一段之前并发展示代…

机器人学优质资源

引言 实验室闲着查资料&#xff0c;发现西北大学有个很好的机器人学线上资源课程&#xff0c;Coursera上也有&#xff0c;记录一下&#xff0c;感觉还挺全的&#xff0c;而且GitHub上也开放了很多相应的学习资源。 Coursera的视频&#xff1a; Modern Robotics: Mechanics, Pla…

GitHub标星75k,阿里15W字的Spring高级文档(全彩版),真的太香了

随着 Spring 使用越来越广泛&#xff0c;Spring 已经成为 Java 程序员面试的必问知识点&#xff0c;很多同学对于Spring理解不是那么的深刻&#xff0c;经常就会被几个连环追问给干趴了&#xff01; 今天小编整理了一下一线架构师的Spring源码高级文档&#xff1a;SpringSprin…

Java三大特征之一——继承

继承继承概述、使用继承的好处继承得儿设计规范&#xff0c;内存运行原理继承的特点继承后&#xff1a;成员变量、成员方法的访问特点继承后&#xff1a;方法重写继承后&#xff1a;子类构造器的特点继承后&#xff1a;子类构造器访问父类有参数构造器this、super总结继承概述、…

挂耳耳机十大品牌排行榜哪个好,目前排行靠前的五款耳机推荐

耳机作为生活的必需品&#xff0c;随着我们生活水平的提高&#xff0c;对于耳机的需求也随之加强&#xff0c;既需要在运动中使用&#xff0c;又要能够在日常佩戴照常无误&#xff0c;那么在此我的建议是对于防水性能一定要高&#xff0c;毕竟高的防水能够抵挡运动中的汗水&…

WPF MVVM

WPF MVVM MVVMModelViewViewModel Model:现实世界中对象抽象的结果&#xff0c;也就是实体模型View:UI界面ViewModel:为UI界面服务的模型&#xff0c;可以理解为数据传输对象&#xff08;DTO&#xff09; ViewModel和View的沟通有两个方面&#xff1a;数据和操作传递数据–使…

“综合”web项目编写------手把手0基础教学(二)

上一节介绍了编写综合项目的基本流程 “综合”web项目编写------手把手0基础教学&#xff08;一&#xff09; 这里将继续介绍项目的编写&#xff0c;一个一个功能挨个实现。 目录 实现用户的登录功能 一 . 编写工具包---封装数据库链接 二 . 构建数据模型 三 . 构建功能…

ASEMI代理艾赛斯IGBT管IXYB82N120C3H1

编辑-Z 艾赛斯IGBT管IXYB82N120C3H1参数&#xff1a; 型号&#xff1a;IXYB82N120C3H1 漏极-源极电压&#xff08;VCES&#xff09;&#xff1a;1200V 连续漏电流&#xff08;IC&#xff09;&#xff1a;82A 功耗&#xff08;PC&#xff09;&#xff1a;1040W 工作结温度…

spring cloud在bootstrap.properties配置spring.profiles.active无效

bootstrap.properties 配置 bootstrap.properties spring.profiles.activeprofiles.active bootstrap-dev.properties / bootstrap-test.properties #服务器地址 spring.cloud.nacos.config.server-addr127.0.0.1:8848 #项目的命名空间的ID spring.cloud.nacos.config.name…