Spring 循环依赖原理及解决方案

news/2024/9/18 14:59:26/ 标签: spring, 数据库, java

一、什么是循环依赖

循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
举例:

java">@Component
public class AService {// A中注入了B@Autowiredprivate BService bService;
}@Component
public class BService {// B中也注入了A@Autowiredprivate AService aService;
}

上述例子中 AService 依赖于 BService,BService 也依赖了 AService,这就是两个对象之间互相依赖。循环依赖还包括 身依赖、多个实例之间相互依赖(A依赖于B,B依赖于C,C又依赖于A)。

在普通Java环境下正常运行上面的代码调用 AService 对象并不会出现问题,也就是说普通对象就算出现循环依赖也不会存在问题,因为可以将属性设置为null,那么为什么被 Spring 容器管理后的对象有循环依赖的情况会出现问题呢?

二、Spring 可以解决哪些循环依赖问题

在Spring 中,只有同时满足以下两点才能解决循环依赖的问题:

  1. 依赖的 Bean 必须都是单例
  2. 依赖注入的方式,必须不全是构造器注入,且 beanName字母序在前的不能是构造器注入

1.为什么必须是单例

如果两个Bean都是原型模式的话,那么创建A1需要创建一个B1,创建B1的时候要创建一个A2,创建A2又要创建一个B,创建B2又要创建一个A3,创建A3又要创建—个B3…
就又卡BUG了。因为原型模式都需要创建新的对象,不能用以前的对象。

如果是单例的话,创建A需要创建B,而创建的B需要的是之前的那个A。也是基于这点,Spring 就能操作操作了。具体做法就是:先创建A,此时的A是不完整的(没有注入B),用个map保存这个不完整的A,再创建B,B需要A,所以从那个map得到不完整"的A,此时的B就完整了,然后A就可以注入B,然后A就完整了,B也完整了,且它们是相互依赖的。

2.为什么不能全是构造器注入

在 Spring 中创建 Bean 分三步(详细见Spring Bean 的生命周期):

  1. 实例化, createBeanInstance,就是 new了个对象
  2. 属性注入, populateBean,就是 set 一些属性值
  3. 初始化, initializeBean,执行一些 aware 接口中的方法,init-Method,AOP代理等

明确了上面这三点,再结合我上面说的“不完整的”,我们来理一下。
如果全是构造器注入,比如 A(B b),那表明在new A 的时候,就需要得到 B,此时需要new B,但是 B 也是要在构造的时候注入 A,即B(A a),这时候 B 需要在一个 map 中找到不完整的 A,发现找不到。为什么找不到? 因为 A 还没 new 完呢,所以找不到完整的A,因此如果全是构造器注入的话,那么 Spring 无法处理循环依赖。而如果 A 是通过 set 注入 B 的,那么 B 在属性注入时就能注入不完整的 A 了(因为 A 虽然没有进行属性注入,但是已经实例化了),因此 B 就能完整创建 Bean,B 创建完,A 也能进行属性注入了,因此就解决了循环依赖。

在这里插入图片描述

三、Spring 如何解决循环依赖问题

1、三级缓存

  1. 一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)
  2. 二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的Bean。
  3. 三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存。

2、三个缓存如何配合解决循环依赖问题?

关键就是提前暴露未完全创建完毕的Bean。

  1. 首先,获取单例 Bean 的时候会通过 BeanName 先去一级缓存查找完整的 Bean,如果找到则直接返回,否则进行步骤2。
  2. 看对应的 Bean 是否在创建中,如果不在直接返回找不到,如果是,则会去二级缓存查找 Bean,如果找到则返回,否则进行步骤3。
  3. 三级缓存通过 BeanName 查找到对应的工厂,如果存在工厂则通过工厂创建 Bean,并且放置到二级缓存中。
  4. 如果三个缓存都没找到,则返回null。

从上面的步骤我们可以得知,如果查询发现 Bean 还未创建,到第二步就直接返回 null,不会继续查二级和三级缓存。

返回 null 之后,说明这个 Bean 还未创建,这个时候会标记这个 Bean 正在创建中,然后再调用 createBean 来创建 Bean,而实际创建是调用方法 doCreateBean。
doCreateBean 这个方法就会执行上面我们说的三步骤:实例化、属性注入、初始化。

在实例化 Bean 之后,会往三级缓存塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个 Bean。

3、举例说明

举例:A、B 两个类循环依赖,Spring 结合三级缓存这样解决:

  1. 一开始创造 A 时候查询—级缓存(里面存成品),发现没找到则看二级缓存是否在创建中(有没有半成品)。都没有则需要创建 A 的 bean,调用的是 createBean,过程分别是实例化、属性注入、初始化。
  2. A 实例化之后往三级缓存加入一个 A 的 getObject 方法,这个就是解决循环依赖的关键。
  3. 到了属性注入,因为 A 依赖 B 因此需要创建 B 。同样的路线 B 也要 createBean 。不一样的也是解决循环依赖的一环:到了属性注入,查询二级缓存的 A 为创建中,则调用三级缓存的工厂 getObject 创建一个半成品的 A,放入到二级缓存中,并完成 B 的第二步属性注入。
  4. 后面初始化 initializeBean,完成 B 的 Bean 创建,放到—级缓存。
    回到 A 刚刚卡在属性注入,现在可以成功注入 B,然后初始化,A 也完成了 Bean 创建。(注︰成品和半成品就是没有注入所需的依赖)

为什么 A 中注入的 B 是构造器注入就不能解决?

再次结合实例解释一下:
如果 A 是构造器注入,B 是 set 注入。则说明 A 需要 B 的时刻提前了,在实例化new A(B b) 的时候就需要 B。此时 A 没有往三级缓存放 getObject,因此到了创建依赖 B 的时候,无法获取 A 的 getObject 工厂方法,只能继续 new A,造成循环依赖的死循环。

4、三级缓存有必要吗?

在上述的步骤中,三级缓存的作用就是直接返回实例化的 A,去掉三级缓存,直接从二级缓存中取出 A 也可以。所以理论上通过二级缓存就能解决循环依赖。那么三级缓存的作用是什么?

三级缓存的作用体现在:在 A 被代理的情况下,三级缓存里的 Bean 工厂有返回代理对象的能力,可以控制:1)当没有循环依赖时,会按照 Bean 的生命周期来创建 Bean(即不会用到三级缓存,在初始化后的后处理器中才会进行代理)2)当有循环依赖时,通过三级缓存提前将代理对象返回给 B。(此时的代理对象是还未进行属性注入的代理对象)


http://www.ppmy.cn/news/1524436.html

相关文章

得物超级品质保障中心,助力电商品质保障迈向新高度

近期记者走进国内知名潮流电商平台——得物App的超级品质保障中心。该中心位于上海市嘉定区,总建筑面积达到约12万平方米,是集查验鉴别、鉴别研究、质量管理、仓储流转等功能于一体的综合性服务设施,全面覆盖了服装、配饰、奢侈品等多个业务品…

APACHE-ATLAS-2.1.0 - 基础运维

(一)SOLR相关 1. 如何创建/删除集合? # 1. 删除 solr/bin/solr delete -c vertex_index solr/bin/solr delete -c edge_index solr/bin/solr delete -c fulltext_index# 2. 创建 solr/bin/solr create -c vertex_index -force -d conf/solr…

从零开始学cv-0:图像处理基础知识

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一,图像分类1.1、模拟图像1.2、数字图像 二、颜色模式(颜色存储)2.1、RGB模式(发光模式)2.2、CMYK模…

pptp解说

PPTP(Point-to-Point Tunneling Protocol)是一种网络协议,用于在互联网上创建虚拟专用网络(VPN)连接。 PPTP 允许远程用户通过公共网络(如互联网)安全地连接到私有网络。它是一种较早的VPN技术…

鸿萌数据恢复服务:Mac 文件系统是如何影响 Mac 数据恢复的?

天津鸿萌科贸发展有限公司从事数据安全服务二十余年,致力于为各领域客户提供专业的数据备份、数据恢复解决方案与服务,并针对企业面临的数据安全风险,提供专业的相关数据安全培训。 公司是多款国际主流数据恢复软件的授权代理商,为…

C++设计模式——State状态模式

一,状态模式的定义 状态模式是一种行为型设计模式,状态模式允许对象在内部状态发生切换时改变它自身的行为。 状态模式的主要目的是将复杂的状态切换逻辑抽象化为一组离散的状态类,使代码结构更加清晰和易于维护。 状态模式将对象的行为封…

继收购西门子物流自动化后,丰田又投资一家AGV公司,智能物流版图已极其夸张...

导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 继成功将西门子物流自动化(机场物流业务)纳入麾下后,丰田并未停下其征伐的步伐,而是再度出手,与新兴科技巨头Gideon携手,共同绘制了一幅…

AI 产品经理:2024 年职场新航标 ——AI 产品经理的未来与契机

前言 这两年,AI 骤然“火”了起来,可谓出现了重大“转折”。就在这短短两年间,全球各大“大厂”几乎在同一时间争先恐后地跟进 AI 技术。从 ChatGPT 发布起,谷歌、Facebook、亚马逊等纷纷紧跟其后,国内的百度、腾讯、…

【笔记】一维动态规划DP

文章目录 动态规划DPDP解题步骤例子1lanqiao3367 破损的楼梯题目描述输入格式输出格式解题思路代码 lanqiao3423 安全序列题目描述输入格式输出格式解题思路代码 动态规划DP 动态规划用于解决具有重叠子问题、最优子结构特征的问题。 重叠子问题:子问题是原问题的…

安装与pytorch不同cuda版本的bitsandbytes

由于历史遗留问题,虽然我的cuda版本是11.8,但是我的torch对应cuda版本是12.1,安装bitsandbytes后,就会抛出以下报错: Could not load bitsandbytes native library: libcusparse.so.12: cannot open shared object fi…

Flask如何传递URL参数

在Flask中,传递URL参数是一种常见且强大的功能,它允许你的Web应用根据URL中的不同部分来动态地生成内容或执行不同的操作。虽然直接撰写5000字来详细解释这一功能可能过于冗长,但我可以提供一个简明而全面的概述,包括基本概念、使…

ISAC: Toward Dual-Functional Wireless Networks for 6G and Beyond【论文阅读笔记】

此系列是本人阅读论文过程中的简单笔记,比较随意且具有严重的偏向性(偏向自己研究方向和感兴趣的),随缘分享,共同进步~ Integrated Sensing and Communications: Toward Dual-Functional Wireless Networks for 6G and…

AI客服机器人开启企业客户服务新纪元

随着人工智能(AI)技术的迅猛发展,使得AI客服机器人走进了我们的视野,成为提高客户满意度和业务效率的不二法宝。这些智能机器人不仅能够处理海量信息,还能为客户提供个性化的服务体验。 一、AI客服机器人的基本原理 AI客服机器人是基于人工智…

探索非局部均值滤波在椒盐噪声去除中的应用

在图像处理领域,椒盐噪声是一种常见的干扰,它会导致图像出现随机的黑白像素点,严重影响图像质量。为了解决这一问题,本文将介绍一种有效的去噪技术——非局部均值滤波(NLM)的改进版本,即NAMF&am…

Java面试篇基础部分-JVM详细介绍

JVM的运行机制 JVM(Java Virtual Machine)是用于运行Java字节码的虚拟计算机,其中包括一套字节码的指令集、程序寄存器、虚拟机栈、虚拟机堆、本地方法区、垃圾回收器。JVM运行在操作系统上层,它不跟底层硬件直接进行交互。如下图所示   Java源代码通过了编译器编译成响…

电商品牌假货要怎么处理

在电商蓬勃发展的今日,假货问题如影随形,严重威胁着品牌的声誉与市场的健康。力维网络以专业打假服务,为品牌保驾护航。 一、精准监测,揪出假货端倪 力维网络的数据监测系统犹如一张严密的大网,覆盖全网。通过全面采集…

Linux中限制服务如mysql的最大cpu使用率

1、cpu占用测试&#xff1a; DELIMITER // DROP PROCEDURE IF EXISTS intensive_calculations; CREATE PROCEDURE intensive_calculations() BEGINDECLARE v INT DEFAULT 0;DECLARE i INT DEFAULT 0;WHILE i < 1000000 DOSET v SQRT(i * i (RAND() * 10000));SET i i 1…

(11)(2.1.1) PWM、OneShot和OneShot125 ESC(二)

文章目录 前言 3 OneShot 125 4 混合ESC协议 5 参数说明 前言 大多数 ArduPilot 飞行器使用由无刷电机 ESC 控制的无刷电机。这些 ESC 使用的最常见协议是PWM、OneShot、OneShot125 和 DShot。本页介绍前三种&#xff08;PWM、OneShot 和OneShot125&#xff09;。 3 OneSh…

OpenCV-模板匹配

文章目录 一、简介1.定义与原理2.算法与方法3.参数解释 二、代码实现1.读取数据2.检查图像是否成功加载3.获取模板的高度和宽度4.模板匹配5.计算匹配区域坐标6.显示图像7.全部代码 三、总结 一、简介 在OpenCV中&#xff0c;模型匹配&#xff08;或称为模板匹配&#xff09;是…

一文读懂:如何将广告融入大型语言模型(LLM)输出

本文是我翻译过来的&#xff0c;讨论了在线广告行业的现状以及如何将大型语言模型&#xff08;LLM&#xff09;应用于在线广告。 原文请参见”阅读原文“。 在2024年&#xff0c;预计全球媒体广告支出的69%将流向数字广告市场。这个数字预计到2029年将增长到79%。在Meta的2024…