Spring是如何解决循环依赖的问题的?

循环依赖是指多个Bean之间互相依赖,形成一个闭环的情况。这种情况可能会导致系统无法正常初始化或运行,因此需要一种机制来解决循环依赖问题。

Spring解决循环依赖问题的核心思想是延迟注入三级缓存机制。下面详细介绍这些概念:

1. 延迟注入(Lazy Initialization):

Spring默认情况下是延迟注入Bean的属性的,这意味着Spring在实例化Bean时不会立即注入它们的属性引用。相反,它会先创建一个尚未完成初始化的Bean实例,然后将这个实例放入一个"早期单例"缓存中。这个Bean实例已经包含了对其他Bean的引用,但是属性还没有被注入。这样,Spring就避免了循环依赖的直接发生。

2. 三级缓存机制:

Spring引入了三级缓存机制,用于处理Bean的实例化和属性注入过程中的循环依赖。

  • singletonObjects:这是Spring容器的一级缓存,用于存储已经完成初始化和属性注入的单例Bean实例。这些Bean可以被其他Bean引用。
  • earlySingletonObjects:这是Spring容器的二级缓存,用于存储已经实例化但属性注入尚未完成的Bean实例。这个缓存是为了解决循环依赖问题而存在的。在这个缓存中,Bean实例包含了对其他Bean的引用,但属性注入还没有完成。
  • singletonFactories:这是Spring容器的三级缓存,用于存储Bean的工厂方法。当Bean需要被初始化时,Spring会使用工厂方法来创建Bean,并将Bean实例放入这个缓存中。这个缓存也是为了解决循环依赖问题而存在的。

整个循环依赖解决过程如下:

  1. 首先,Spring创建Bean的实例,但不进行属性注入,将Bean放入earlySingletonObjects缓存中。
  2. 接着,Spring开始属性注入过程,如果发现需要注入的属性是其他Bean的引用,它会尝试从singletonObjects缓存中获取已经初始化的Bean实例,如果没有找到,就会继续递归初始化该Bean,此时将使用singletonFactories缓存来创建Bean的实例,同时将Bean实例放入earlySingletonObjects缓存中。
  3. 当Bean的属性注入完成后,Bean实例将从earlySingletonObjects缓存中移除,然后放入singletonObjects缓存中,表示该Bean已经完成初始化,可以被其他Bean引用。
  4. 如果发现循环依赖,Spring会抛出异常,因为无法解决的循环依赖问题会导致无限递归。

总之,Spring的延迟注入和三级缓存机制有效地解决了循环依赖问题,允许程序员在配置依赖关系时更自由地引用其他Bean,同时保证了Bean的初始化顺序和完整性。但要注意,合理的Bean设计和依赖管理仍然是避免循环依赖问题的最佳实践。

示例:

我们将使用以下三个类:CustomerOrderProduct

public class Customer {private String name;private Order order;public Customer(String name) {this.name = name;}public void setOrder(Order order) {this.order = order;}public void printDetails() {System.out.println("Customer Name: " + name);System.out.println("Order Details:");order.printOrder();}
}

public class Order {private Product product;public void setProduct(Product product) {this.product = product;}public void printOrder() {System.out.println("Product Details:");product.printProduct();}
}
public class Product {private String name;public Product(String name) {this.name = name;}public void printProduct() {System.out.println("Product Name: " + name);}
}

现在,让我们看看如何在 Spring 中配置这些 Bean,以及如何解决潜在的循环依赖问题。

spring-config.xml

<bean id="customer" class="com.example.Customer"><constructor-arg value="John" />
</bean><bean id="order" class="com.example.Order"><property name="product" ref="product" />
</bean><bean id="product" class="com.example.Product"><constructor-arg value="Laptop" />
</bean>

在这个示例中,Customer 依赖于 OrderOrder 依赖于 Product,同时 Product 依赖于 Customer,形成了循环依赖。

Spring 会采用类似前面描述的方式来解决循环依赖:

  1. 首先,Spring 实例化 Customer,但不注入 Order
  2. 然后,Spring 实例化 Order,但不注入 Product
  3. 接下来,Spring 实例化 Product
  4. 现在,Spring 开始属性注入过程。它尝试为 Customer 注入 Order,从缓存中找不到,因此继续实例化 Order。这时,Order 已经实例化,但还未注入 Product
  5. Spring 为 Order 注入 Product,从缓存中找不到,继续实例化 Product
  6. 此时,Spring 为 Product 注入 Customer,从缓存中找到已经实例化的 Customer
  7. 属性注入完成后,所有 Bean 都会从早期缓存中移除,放入单例缓存中。

最终,Spring 成功解决了这个复杂的循环依赖问题,并且可以正常打印 CustomerOrderProduct 的详细信息。

需要注意的是,虽然 Spring 可以解决循环依赖,但在设计应用程序时,仍然建议尽量避免复杂的循环依赖关系,以保持代码的清晰性和可维护性。

三级缓存,提前暴露对象,aop

:什么是循环依赖问题,A依赖B,B依赖C,C依赖A

:先说明bean得创建过程:实例化,初始化(填充属性)

1.先创建A对象,实例化A对象,此时A对象中的b属性为空

2.从容器中查找B对象,如果找到了,直接赋值不存在循环依赖问题(不通),找不到直接创建B对象

3.实例化B对象,此时B对象中的a属性为空,填充属性a

4.从容器中查找A对象,找不到,直接创建

此时,如果仔细琢磨的话,会发现A对象,是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但是未完成初始化,如果在程序调用过程中,拥有了某个对象的引用,能否在后期给他完成赋值操作,可以优先把非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖问题的关键,

当所有的对象都完成实例化和初始化操作之后,还要把完整对象放到容器中,此时在容器中存在对象的几种状态,完成实例化=但未完成初始化,完整状态,因为都在容器中,所以要使用不同的map结构来进行存储,此时就有了一级缓存和二级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1,2,3这样的方式来查找的。一级缓存中放的是完整对象,二级缓存中放的是非完整对象,

为什么需要三级缓存?

三级缓存的value类型是ObjectFactory,是一个函数式接口,存在的意义是保证在整个容器的运行过程中同名的bean对象只能有一个。

如果一个对象需要被代理,或者说需要生成代理对象,那么要不要优先生成一个普通对象?要

普通对象和代理对象是不能同时出现在容器中的,因此当一个对象需要被代理的时候,就要使用代理对象覆盖掉之前的普通对象,在实际的调用过程中,是没有办法确定什么时候对象被使用,所以就要求某个对象被调用的时候,优先判断此对象是否需要被代理,类似于一种回调机制的实现,因此传入lambda表达式的时候,可以通过lambda表达式来执行对象的覆盖过程,getEarlyBeanReference()

因此,所有的bean对象在创建的时候要优先放到三级缓存中,在后续的使用过程中,如果需要被代理则返回代理对象,如果不需要被代理,则直接返回普通对象

以下是一个简单的Java代码示例,演示了Spring中的循环依赖以及Spring是如何解决它的。这个示例包含两个类:PersonAddressPerson 类依赖于 Address,而 Address 类也依赖于 Person

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class Person {private String name;private Address address;@Autowiredpublic Person(Address address) {this.address = address;}public void setName(String name) {this.name = name;}public String getName() {return name;}public Address getAddress() {return address;}
}@Component
public class Address {private String city;private Person person;@Autowiredpublic Address(Person person) {this.person = person;}public void setCity(String city) {this.city = city;}public String getCity() {return city;}public Person getPerson() {return person;}
}

在这个示例中,PersonAddress 类都使用了构造函数注入,它们互相依赖对方。现在,让我们分析一下Spring是如何解决这个循环依赖的:

  1. 当Spring容器启动时,它首先创建一个 Person 对象并放入一级缓存(singletonObjects缓存),但尚未完成初始化。
  2. 接下来,Spring创建一个 Address 对象,并将 Person 对象注入到 Address 对象中。在注入时,它发现 Person 对象已经在一级缓存中,但尚未完成初始化。因此,它创建一个 Person 的代理对象并将它注入到 Address 对象中,然后将 Address 对象放入二级缓存(earlySingletonObjects缓存)。
  3. Person 对象的初始化过程继续,它的构造函数完成并将 Address 对象注入。在注入时,Spring发现 Address 对象已经在二级缓存中,但尚未完成初始化。因此,它创建一个 Address 的代理对象并将它注入到 Person 对象中。
  4. Address 对象的初始化过程继续,它的构造函数完成。此时,Address 对象和 Person 对象都已完成初始化。
  5. 最终,Spring将 PersonAddress 对象放回一级缓存,以供其他Bean使用。代理对象被替换为真正的对象。

这样,Spring成功解决了 PersonAddress 之间的循环依赖,通过创建代理对象,使得初始化过程可以顺利完成。

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

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

相关文章

Python的pandas库来实现将Excel文件转换为JSON格式的操作

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

物联网应用中蓝牙模块怎么选?_蓝牙模块厂家

在蓝牙模块选型前期&#xff0c;一定要了解应用场景以及需要实现的功能&#xff08;应用框图&#xff09;&#xff0c;以及功能实现过程中所能提供调用的接口&#xff08;主从设备&#xff0c;功能&#xff09;&#xff0c;考虑模块供电&#xff0c;尺寸&#xff0c;接收灵敏度…

【已更新建模代码】2023数学建模国赛B题matlab代码--多波束测线问题

一、 问题重述 1.1问题背景 海洋测深是测定水体深度与海底地形的重要任务&#xff0c;有两种主要技术&#xff1a;单波束测 深与多波束测深。单波束适用于简单任务&#xff0c;但多波束可提供更精确的地形数据。多 波束系统的关键在于覆盖宽度与重叠率的设计&#xff0c;以确保…

【JavaSE】面试01

文章目录 1. JDK、JRE、JVM之间的关系2. 补充3. 面试题&#xff1a;重载和重写的区别&#xff1f;4. super和this5. &#xff08;重点&#xff01;&#xff01;&#xff09;若父类和子类均有静态代码块、实例代码块以及无参构造方法&#xff0c;则继承关系上的执行顺序&#xf…

RK3588平台产测之ArmSoM-W3 DDR压力测试

1. 简介 RK3588从入门到精通 ArmSoM团队在产品量产之前都会对产品做几次专业化的功能测试以及性能压力测试&#xff0c;以此来保证产品的质量以及稳定性 优秀的产品都要进行多次全方位的功能测试以及性能压力测试才能够经得起市场的检验 2. 环境介绍 硬件环境&#xff1a; …

Hadoop生态之hive

一 概述与特点 之所以把Hive放在Hadoop生态里面去写,是因为它本身依赖Hadoop。Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类 SQL 查询功能。 其本质是将 SQL 转换为 MapReduce/Spark 的任务进行运算,底层由 HDFS 来提供…

JavaExcel:自动生成数据表并插入数据

故事背景 出于好奇&#xff0c;当下扫描excel读取数据进数据库 or 导出数据库数据组成excel的功能层出不穷&#xff0c;代码也是前篇一律&#xff0c;poi或者easy excel两种SDK的二次利用带来了各种封装方法。 那么为何不能直接扫描excel后根据列的属性名与行数据的属性建立S…

UDP攻击是什么?

UDP是一个简单的面向数据报的运输层协议&#xff0c;也是最常见的作为流量攻击最多的一种协议&#xff0c;需要用到UDP的主要都是视频通讯&#xff0c;枪战类实时通讯的游戏类。UDP不提供可靠性&#xff0c;它只是把应用程序传给IP层的数据报发送出去&#xff0c;但并不保证它们…

备战计算机二级公共基础知识(五)----数据库设计基础

数据库设计基础 目录 数据库设计基础 数据库的基本概念&#xff1a;数据库&#xff0c;数据库管理系统&#xff0c;数据库系统 数据模型&#xff0c;实体联系模型及 &#xff25;&#xff0d;&#xff32; 图&#xff0c;从 &#xff25;&#xff0d;&#xff32; 图导出关系…

基于JAVA SSM框架和JSP的超市小卖部管理系统设计

摘要 随着时代的发展&#xff0c;传统的超市购物方式已经不能满足人们的需求&#xff0c;对于顾客来说&#xff0c;排队购物和支付购物费用的问题亟待解决。对于实体超市来说&#xff0c;他们面临着网上购物的竞争压力&#xff0c;作为超市经理&#xff0c;他们要降低成本&…

如何自启动MySQL服务与解决MySQL字符集问题

1、自启动mysql服务 &#xff08;1&#xff09;查看mysql是否自启动&#xff08;默认自启动&#xff09; systemctl list-unit-files|grep mysqld.service &#xff08;2&#xff09;如不是enabled可以运行如下命令设置自启动 systemctl enable mysqld.sercice2、字符集…

SpringBoot 博客网站

SpringBoot 博客网站 系统功能 登录注册 博客列表展示 搜索 分类 个人中心 文章分类管理 我的文章管理 发布文章 开发环境和技术 开发语言&#xff1a;Java 使用框架: SpringBoot jpa H2 Spring Boot是一个用于构建Java应用程序的开源框架&#xff0c;它是Spring框架的一…

Android WebView 使用指南强化版

Android WebView介绍、优势和使用教程 WebView是Android平台中一个非常重要的控件&#xff0c;它可以用来在Android应用中显示网页。WebView使用WebKit引擎来渲染网页&#xff0c;因此可以很好地兼容Web标准。 WebView的介绍 WebView是一个Android控件&#xff0c;它可以用来…

微信小程序视频播放

微信小程序视频播放 官方地址&#xff1a; https://developers.weixin.qq.com/miniprogram/dev/component/video.html binderror 视频错误信息 show-play-btn 是否显示视频底部控制栏的播放按钮 show-fullscreen-btn 是否显示全屏按钮 controls 是否显示默认播放控件 enable-p…

Mysql和Oracle的语法区别?

Mysql和Oracle是两种不同的关系型数据库。 MySQL通常在中小型应用程序、Web应用程序和小型企业中广泛使用&#xff0c;因为它易于学习和部署&#xff0c;而且成本较低。 Oracle数据库通常用于大型企业和复杂的企业级应用程序&#xff0c;因为它提供了高度可扩展性、高可用性…

Unity——脚本与导航系统

Unity内置了一个比较完善的导航系统&#xff0c;一般称为Nav Mesh&#xff08;导航网格&#xff09;&#xff0c;用它可以满足大多数游戏中角色自动导航的需求。 一、导航系统相关组件 Unity的导航系统由以下几个部分组成&#xff1a; Nav Mesh。Nav Mesh与具体的场景关联&…

终端登录github两种方式

第一种方式 添加token&#xff0c;Setting->Developer Setting 第二种方式SSH 用下面命令查看远程仓库格式 git remote -v 用下面命令更改远程仓库格式 git remote set-url origin gitgithub.com:用户名/仓库名.git 然后用下面命令生成新的SSH秘钥 ssh-keygen -t ed2…

Java基础学习笔记-1

前言 Java 是一门强大而广泛应用的编程语言&#xff0c;它的灵活性和跨平台特性使其成为许多开发者的首选。无论您是刚刚入门编程&#xff0c;还是已经有一些编程经验&#xff0c;掌握 Java 的基础知识都是构建更复杂程序的关键。 本学习笔记旨在帮助您深入了解 Java 编程语言…

代码随想录算法训练营Day56 || ● 583. 两个字符串的删除操作 ● 72. 编辑距离

今天接触到了真正的距离&#xff0c;但可以通过增删改操作来逼近。 问题1&#xff1a;583. 两个字符串的删除操作 - 力扣&#xff08;LeetCode&#xff09; 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字…

Mysql学习之——增删改查语句

Mysql的增删改查 一、数据库操作 1.查询所有数据库 show databases&#xff1b;2.使用某个数据库 如果我已经通过show databases知道有a、b、c三个数据库&#xff0c;那我想用数据库a怎么办呢? use 数据库名&#xff1b; eg:use a;3.查询当前使用的数据库 如果我不知道当…