Spring的循环依赖

news2025/2/28 15:22:52

什么是循环依赖?

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

 

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

 

Spring 中循环依赖场景有: 

(1)构造器的循环依赖 

(2)field 属性的循环依赖

其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring 采用的是提前暴露对象的方法。

怎么检测是否存在循环依赖

检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

Spring 怎么解决循环依赖

Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。(但是构造器必须是在获取引用之前)。

完整实例 bean,需要通过上面三个步骤:

1.  createBeanInstance 方法: 得到一个实例 bean,但是没有进行属性值的注入

2.  populateBean 方法:就是对 bean 进行属性值注入。

3.  initializeBean 方法:如果配置文件里面有 init 方法,需要执行 init 方法。

可以看出第一步和第二步比较重要,这里面就是解决 bean 依赖的关键。

我们可以看出进行 createBeanInstance 方法,得到了 bean 实例对象,但是没有属性注入。把没有完全实例化的 bean,放到 addSinletonFactory 方法里面去,这样相当于就是提前暴露 bean,接下来 addSinletonFactory 方法,这里面使用三个 Map 类型的,及三级缓存来解决循环依赖。接下我们看一下 addSingletonFactory 方法实现。

singleObjects--》单例对象的 cache:一级缓存

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

singletonFactories --》单例对象工厂的 cache :三级缓存

首先解释两个参数:

  • isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中,当单例对象没有被初始化完全 (例如 A 定义的构造函数依赖了 B 对象,得先去创建 B 对象,或者在 populatebean 过程中依赖了 B 对象,得先去创建 B 对象,此时 A 处于创建中)
  • allowEarlyReference 是否允许从 singletonFactories 中通过 getObject 拿到对象

在创建 bean 的时候,首先想到的是从 cache 中获取这个单例的 bean,这个缓存就是 singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存 earlySingletonObjects 中获取。如果还是获取不到且允许 singletonFactories 通过 getObject () 获取,就从三级缓存 singletonFactory.getObject ()(三级缓存) 获取,如果获取到了则:从 singletonFactories 中移除,并放入 earlySingletonObjects 中。其实也就是从三级缓存移动到了二级缓存。

Spring 解决循环依赖的诀窍就在于 singletonFactories 这个 cache,这个 cache 中存的是类型为 ObjectFactory,其定义如下

public interface ObjectFactory<T> {
    T getObject() throws BeansException;}

在 bean 创建过程中,有两处比较重要的匿名内部类实现了该接口。一处是

new ObjectFactory<Object>() {
   @Override   public Object getObject() throws BeansException {
      try {
         return createBean(beanName, mbd, args);
      }      catch (BeansException ex) {
         destroySingleton(beanName);
         throw ex;
      }   }

另一处就是

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这里就是解决循环依赖的关键,这段代码发生在 createBeanInstance 之后,也就是说单例对象此时已经被创建出来 (调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以 Spring 此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下 “A 的某个 field 或者 setter 依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 依赖了 A 的实例对象” 这种循环依赖的情况。A 首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中,此时进行初始化的第二步,发现自己依赖对象 B,此时就尝试去 get (B),发现 B 还没有被 create,所以走 create 流程,B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get (A),尝试一级缓存 singletonObjects (肯定没有,因为 A 还没初始化完全),尝试二级缓存 earlySingletonObjects(也没有),尝试三级缓存 singletonFactories,由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象 (虽然 A 还没有初始化完全,但是总比没有好呀),B 拿到 A 对象后顺利完成了初始化阶段 1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。此时返回 A 中,A 此时能拿到 B 的对象顺利完成自己的初始化阶段 2、3,最终 A 也完成了初始化,进去了一级缓存 singletonObjects 中,而且更加幸运的是,由于 B 拿到了 A 的对象引用,所以 B 现在 hold 住的 A 对象完成了初始化。

知道了这个原理时候,肯定就知道为啥 Spring 不能解决 “A 的构造方法中依赖了 B 的实例对象,同时 B 的构造方法中依赖了 A 的实例对象” 这类问题了!因为加入 singletonFactories 三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

为啥要三级缓存

必须使用三级缓存吗 两级不行吗 提前暴露时直接放到二级缓存 earlySingletonObjects 里会有什么问题呢?

answer:如果不存在循环依赖,那么就没必要提早曝光放到 earlySingletonObjects 里。如果像你说的只有两级缓存,那简单的 A 依赖 B,A 在第一步初始化未注入 field 的时候就放到了 earlySingletonObjects,显然不符合常理,只能说放到三级缓存中预防循环依赖的产生。singletonFactories 并没有提早曝光的意思,只是为了缓存,earlySingletonObjects 才为了提早曝光,所以才要三级的。

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

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

相关文章

金陵科技学院五年一贯制专转本计算机应用基础考试大纲

金陵科技学院五年一贯制专转本计算机应用基础考试大纲 一、参考书目&#xff1a; 《大学计算机信息技术教程》&#xff0c;张福炎&#xff0c;孙志挥&#xff0c;南京大学出版社&#xff0c;《全国计算机等级考试一级教程计算机基础及MS Office应用》&#xff0c;教育部考试中…

书写我的人生回忆录-这应该是给子女和父母最好的礼物

作为一个业余的软件开发爱好者&#xff0c;我又捣鼓了一个有意思的小东西 &#xff0c;使用完全免费哈 您可以是以子女的身份帮助父母来完成这份回忆录&#xff1a; 《书写我的人生回忆录》是一款软件&#xff0c;其中包含70个问题&#xff0c;涵盖了父母的个人喜好、家庭、工…

关于图床使用

安装windows终端 Windows 终端安装 | Microsoft Learn 安装oh-my-posh 安装scoop &#xff08;后续都使用PowerShell 7.2.10&#xff09;&#xff1a; > Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # Optional: Needed to run a remote script the first ti…

代谢组学-南京医科大学胡志斌团队绘制心脏发育全周期多组学图谱

文章标题&#xff1a;Multi-omics profifiling visualizes dynamics of cardiac development and functions 发表期刊&#xff1a;Cell Reports 影响因子&#xff1a;9.995 作者单位&#xff1a;南京医科大学 百趣提供服务&#xff1a;发现代谢组学HD-MIX版 百趣代谢组学分…

教你用Python破解WiFi,流量自由不再是梦!(附完整源码)

目录 一、准备工作二、编写代码三、展示测试结果四、生成密码本&#xff08;建议自己找一个密码本&#xff09; 一、准备工作 工具&#xff1a;python 二、编写代码 在桌面新建一个文件 如果你新建的文件没有后缀名.txt&#xff0c;请进行如下设置 打开刚刚新建的文件&#…

pg数据库安装

pg数据库安装 下载对应的安装包 安装服务 使用安装包直接安装 rpm -ivh postgresql13-libs-13.1-3PGDG.rhel7.x86_64.rpm rpm -ivh postgresql13-13.1-3PGDG.rhel7.x86_64.rpm rpm -ivh postgresql13-server-13.1-3PGDG.rhel7.x86_64.rpm rpm -ivh postgresql13-contrib-…

NEUQACM双周赛(五)

文章目录 L1-1 计算摄氏温度&#xff08;C&#xff09;输入格式:输出格式:解题思路&#xff1a; L1-2 查验身份证&#xff08;C&#xff0c;模拟&#xff09;输入格式&#xff1a;输出格式&#xff1a;输入样例1&#xff1a;输出样例1&#xff1a;输入样例2&#xff1a;输出样例…

如何与TSC建立 EDI 连接?

Tractor Supply Co.&#xff08;以下简称TSC&#xff09;是美国一家农业和家畜用品零售公司&#xff0c;在美国各地拥有超过1,900家门店&#xff0c;提供各种农业、家畜、园艺和家居产品&#xff0c;包括动物饲料、草坪和花园用品、农业机械、工具和配件等。 通过EDI&#xff…

Linux用户权限——sudoers的深入剖析

一、sudo权限的配置 root账号登录系统不会记录root账号做了什么操作。 su虽然不记录以root执行了哪些命令&#xff0c;但会创建一条日志记录谁在什么时候变成了root。而su切换为root身份&#xff0c;仍然有很大的无法受控的权限&#xff0c;因此sudo是一个更好的选择。 sudo命令…

【面试题】JavaScript 的 8 种数据类型总结

JavaScript 数据类型 本文将介绍 JavaScript 的 8 种数据类型&#xff0c;结合代码对每种数据类型进行总结归纳。 一、数据类型 最新的 ECMAScript 标准定义了 8 种数据类型&#xff1a; 1.七种基本数据类型&#xff1a; 布尔值&#xff08;Boolean&#xff09;&#xff0…

Apache配置ssl常见错误问题汇总

对于站长来说&#xff0c;部署SSL证书来迁移到HTTPS是一个现实和重要的问题&#xff0c;那么&#xff0c;对于Apache系统来说&#xff0c;如何部署SSL证书实现HTTPS协议呢&#xff1f;下面小编为大家总结了几个比较常见的Apache配置ssl错误问题汇总。 Apache配置ssl常见错误一…

LEAP模型应用于工业、交通、建筑、电力、煤炭、炼油、经济、林业等各领域碳排放预测及建模分析

查看原文>>>LEAP软件&#xff08;使用说明LEAP的模拟练习碳排放相关模板&#xff09;IPCC收录的各种燃料CO2排放系数 目录 第一章 &#xff1a;LEAP建模理论基础 第二章&#xff1a;基于LEAP模型的能源需求预测模型构建 第三章&#xff1a;基于LEAP模型的能源供应…

基于Chatbot UI 实现ChatGPT对话-V1.2

基于Chatbot UI 实现ChatGPT对话-V1.0 前端基于开源项目&#xff1a;chatbot-ui进行二次开发&#xff0c;感兴趣的小伙伴可以自行研究。 本项目搭建初衷&#xff1a;在无法科学上网的情况下&#xff0c;实现ChatGPT对话。还有规避官方聊天时&#xff0c;长时间无链接导致的问题…

Linux网络服务----DNS

文章目录 一 、DNS 概念1.1 DNS的简介1.2 DNS 数据结构分布1.3 DNS 域名解析方式1.4 DNS的查询方式 二 、操作实验2.1 主dns服务器配置正向和反向解析2.2 设置从dns服务器正向和反向解析2.3 配置DHCP 一 、DNS 概念 1.1 DNS的简介 域名解析&#xff1a;是互联网的一项服务。它…

fisco bcos Caliper部署错误

fisco bcos Caliper部署错误 错误1、Depolying error: Error: Cannot convert string to buffer. toBuffer only supports 0x-prefixed hex strings and this string was given错误2&#xff1a;Depolying error: TypeError: secp256k1.sign is not a function 错误1、Depolyin…

中国传统的家庭观念,家庭养老一直是中国式养老的主要组成部分

哈喽大家好&#xff0c;本期小编为大家带来中国式养老的发展分析。中国的养老产业在不断的变革和进步&#xff0c;养老需求增加迅速。相关部门大力促进养老服务发展的同时&#xff0c;一些创新型企业也正在推出更加智能化和个性化的养老产品和服务。与此同时&#xff0c;养老行…

CCF-CSP-4-5

csp 2013-12-4 有趣的数2013-12-5 Im stuck10%2014-3-4 无线网络2014-3-5任务调度2014-9-4最优配餐2014-9-5拼图2014-12-4 最优灌溉2014-12-5货物调度2015-3-1 网络延时2015-3-5 最小花费2015-9-4 高速公路2015-9-5 最佳文章2015-12-4 送货2015-12-5 矩阵2013-12-4 有趣的数

简单介绍十几款常用的画架构图流程图的软件

简单介绍十几款常用的画架构图流程图的软件 draw.io draw.io是开源免费的在线画图工具&#xff0c;还提供桌面版本。 特性&#xff1a; 实时协作&#xff1b;支持在线离线版本&#xff1b;存储支持多种方式&#xff1a;Google Drive, OneDrive, GitHub, GitLab, Dropbox等&…

通识哈夫曼树及其应用,一起来构造属于自己的哈夫曼树

1.哈夫曼树的背景 哈夫曼&#xff08;霍夫曼、赫夫曼&#xff09;David Albert Huffman(August9,1925-October7,1999)。计算机科学的先驱&#xff0c;以他的哈夫曼编码闻名&#xff0c;在他的一生中&#xff0c;对于有限状态自动机&#xff0c;开关电路&#xff0c;异步过程和信…

ML | Python中的数据预处理

预处理是指在将数据提供给算法之前对数据进行的转换。数据预处理是一种用于将原始数据转换为干净数据集的技术。换句话说,无论何时从不同来源收集数据,它都是以原始格式收集的,这对于分析是不可行的。 数据预处理的需要 为了从机器学习项目中的应用模型中获得更好的结果,数…