Spring 如何解决循环依赖

news2025/8/11 6:30:06

1.什么是循环依赖?

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。

我们这里以两个类A和B为例进行讲解,如下是A和B的声明:

@Component
public class A {
  private B b;
  public void setB(B b) {
    this.b = b;
  }
}
@Component
public class B {
  private A a;
  public void setA(A a) {
    this.a = a;
  }
}

结论先行:

1.构造器循环依赖----初始化失败

2.field属性注入循环依赖----初始化成功

3.field属性注入循环依赖(prototype)----初始化失败

现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。

2.Spring 如何解决循环依赖?

A、B两个类相互依赖,初始化A的时候,第一步实例化A完成(生成对象工厂实例放入三级缓存),注入依赖属性B,一级缓存查询B没有,二级缓存查询B没有,

初始化B(生成对象工厂实例放入三级缓存),注入依赖属性A,一级缓存查询A没有,二级缓存查询A没有,三级缓存查询到A的对象工厂,需要AOP增强则生成A的代理对象,没有则直接创建A实例对象,并将A放入到二级缓存,注入A的代理对象完成,生成代理对象B,B移入一级缓存。

继续A属性注入(B的代理对象),然后可能还会依赖注入C对象流程和B一致,所有依赖注入完成后A初始化,生成A的代理对象,发现A的代理对象已存在,则跳过,放入一级缓存。此时A的代理对象也是提前生成的,但是仅针对循环依赖提前生成。

下面为流程图:

12716a90a0c448fe90acd8a9e69ec803.png

3.spring中出现循环依赖的场景

spring中出现循环依赖主要有以下场景:

a977b74d7f644f6c817219024c632f1d.png

3.三级缓存

1.一级缓存

已经完成了bean的所有流程,即实例化、注入、初始化完成的bean实例,并放进了单列池中了。

一级缓存:
/** 保存所有的singletonBean的实例,已经完成了所有流程可以用的bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

2.二级缓存

保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入,但已经提前完成了AOP增强。

/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

3.三级缓存

singletonBean的生产工厂,即创建单列bean的工厂。

存一个函数接口, 函数接口实现 创建动态代理调用BeanPostProcessor 。 为了避免重复创建, 调用把返回的动态代理对象或者原实例存储在二级缓存中。

/** singletonBean的生产工厂,即创建单列bean的工厂*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

在取三级缓存并创建bean时,ObjectFactory调用getObject(),不仅会取到bean实例而且还会判断是否生成代理对象,并放到二级缓存中。

一级缓存:
/** 保存所有的singletonBean的实例,已经完成了所有流程可以用的bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

二级缓存:

/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

三级缓存:
/** singletonBean的生产工厂,即创建单列bean的工厂*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
/** 保存所有已经完成初始化的Bean的名字(name) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
 
/** 
标识指定name的Bean对象是否处于创建状态,这个状态非常重要。
如果创建完毕之后,会将其从 singletonsCurrentlyInCreation 列表中移除,
并且会将创建好的 bean 放到另外一个单例列表中,这个列表叫做 singletonObjects即一级缓存中。
*/
private final Set<String> singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));

思考

1.为什么不使用一级缓存?

如果只使用一级缓存:

1.A先从单列池中查询A,没有则创建A,A对象实例化后准备依赖注入B

2.B对象先从单列池中查询B,没有则创建B,B对象实例化后准备依赖注入A

3.A又从单列池中查询A,此时A由于没有完成创建,单列池中没有A,又回重复1过程,从而造成死循环。

可见只使用一级缓存是会造成循环依赖的,那么就需要把只完成了类的实例化还没有进行后面依赖注入,初始化等过程的对象缓存起来(三级缓存),这样从一级缓存取不到,则取三级缓存。

如果这样貌似使用两级缓存就可以了,那为什么还需要三级缓存?

2.为什么需要二级缓存?只用一级缓存和三级缓存可以吗?

先说结论,

1.如果该对象不涉及AOP代理,只用二级缓存就能解决循环依赖了。

2.如果A和B造成的循环依赖,二级缓存也能解决,但如果A不仅被B引用,还同时被C引用等,二级缓存也不能解决循环依赖。

看下图

在这里插入图片描述

即三级缓存获取到的对象工厂ObjectFactory,并取对象时,需要创建新的代理对象,为了防止创建新的代理对象违背单列原则,会将第一次创建的代理对象放到二级缓存中。

spring中为什么要三级缓存?二级不行吗?_知识浅谈的博客-CSDN博客_spring二级缓存与三级缓存

试想一下,如果出现以下这种情况,我们要如何处理?

@Service
public class TestService1 {

    @Autowired
    private TestService2 testService2;
    @Autowired
    private TestService3 testService3;

    public void test1() {
    }
}

@Service
public class TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

@Service
public class TestService3 {

    @Autowired
    private TestService1 testService1;

    public void test3() {
    }
}

TestService1依赖于TestService2和TestService3,而TestService2依赖于TestService1,同时TestService3也依赖于TestService1。

按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。

假设不用第二级缓存,TestService1注入到TestService3的流程如图:

92c1652fb3d8489faf9c5d436b53dd00.png

TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象(主要是代理aop对象,如果没有aop对象则不会存在问题)每次可能都不一样的。

这样ObjectFactory就不一样了,违反的单一实例原则,这样不是有问题吗?

为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。

 3049efb9dfdc48eb84e343c7c687b088.png

3.为什么需要提前进行aop?按照spring生命周期初始化完成之后进行AOP代理对象创建不可以吗?

我们都知道Bean的aop动态代理创建时在初始化之后,但是循环依赖的Bean如果使用了AOP。 那无法等到解决完循环依赖再创建动态代理, 因为这个时候已经注入属性了,比如A已经注入到B对象的属性中了,此时B中的A对象就不是代理对象了。

所以如果循环依赖的Bean使用了aop. 需要提前创建aop。

并且还要提前判断是否需要进行提前AOP。

4.如何判断需要提前进行aop?

创建本身的时候是没法判断自己是不是循环依赖, 只有在B 引用A (不同bean的引用)下才能判断是不是循环依赖(比如B引用A,A正在创建中,那说明是循环依赖), 所以判断要写在getSingleton中。

假如A是proxy:
    
A创建Bean -->注入属性B-->getBean(B)-->创建B-->注入属性A---->getSingleton("a")之后写如下代码
==================================================================================================
public object getSingleton(beanName){
    先从一级缓存拿 省略code...

    if(二级缓存有吗?){  
        return 二级缓存中拿;
    }
    if(A是否还在创建中){
        说明是循环依赖.
        二级缓存=调用创建动态代理BeanPostProcessor(判断是否使用aop,没有依然返回原实例);
    }

}

所以说二级缓存确实完全可以解决循环依赖的任何情况:包括扩展能力(因为也可以在这里调用BeanPostProcessor, 当然AOP也是基于BeanPostProcessor)

5.第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?

答:不行,同二级缓存存在的原因一样。

因为假如你想判断是否对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的,假如都是创建代理对象那违反单一原则,都不创建,则无法进行增强。所以得有一个中间态来判断是否需要加AOP加强以及存储加强后的结果避免重复创建。

针对这种场景spring是怎么做的呢?

答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:

它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
 


版权声明:本文为CSDN博主「蜀州凯哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_72088858/article/details/126698574

参考资料


1.高频面试题:Spring 如何解决循环依赖?https://zhuanlan.zhihu.com/p/84267654
2.Spring 如何解决循环依赖的问题
https://www.jianshu.com/p/8bb67ca11831

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

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

相关文章

MCE | 丙型肝炎病毒的终结之路

Harvey J. Alter 对输血相关性肝炎的系统研究表明&#xff0c;一种未知病毒是慢性肝炎的常见病因&#xff1b;Michael Houghton 使用了一种未经验证的策略&#xff0c;分离了新病毒丙型肝炎病毒 (Hepatitis C virus) 的基因组&#xff1b;Charles M. Rice 提供了最终的证据&…

C语言源代码系列-管理系统之会员计费系统

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过…

【附源码】计算机毕业设计JAVA疫情期间物资分派管理系统

【附源码】计算机毕业设计JAVA疫情期间物资分派管理系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; …

FEDformer里面的CZ1d

这里的x是传入的value&#xff0c;是&#xff08;1,24,128,8&#xff09;维度的。 然后&#xff0c;他提取的extra_x的维度是&#xff08;1,8,128,8&#xff09;这个维度的。 然后&#xff0c;下一步&#xff0c;将这两个xcat起来&#xff0c;不知道在干啥。 因为本来的x是&…

赵本山最有钱徒弟,曾经入股宾利投资吴京,如今又盯上歌手腾格尔

有句话叫作&#xff1a;过了山海关&#xff0c;都找赵本山。说明本山大叔在东北影响很大&#xff0c;其实他的徒弟也不弱。赵本山老师作为央视春晚小品王&#xff0c;手下弟子没有三千也有八百&#xff0c;个个都是出来拔萃身怀绝技。 这些人之所以拜本山大叔为师&#xff0c;无…

[附源码]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…

Teracloud+GoodSync

TeracloudGoodSync 前言 Teracloud除了存文献外&#xff0c;它还是个云盘&#xff0c;可以搭配各种同步软件使用&#xff0c;之前在博客多终端云同步文献管理&#xff1a;ZoteroTeraCloud&#xff08;WindowsAndroid&#xff09;里提到用同步软件搭配Teracloud使用&#xff0…

使用SSM搭建图书商城管理系统(完整过程介绍、售后服务哈哈哈)

经过几位下载同学的反应、大部分运行未成功的原因有以下几点、特此记录以下。代码是完全没有问题的 项目地址&#xff1a;https://download.csdn.net/download/weixin_43304253/85811914 代码运行环境&#xff1a; tomcat&#xff1a;8 IDEA&#xff1a;2020 JDK&#xff1a;1…

vue.js毕业设计,基于vue.js前后端分离在线考试系统设计与实现(H5移动项目)

功能介绍 用户首次登陆系统需要注册一个用户或直接使用微信作为账号&#xff0c;用户在登录平台后&#xff0c;可以进行平台的操作。主要模块包括以下几点&#xff1a; 注册登录功能&#xff1a;注册普通账号登录&#xff1b;也可以直接使用微信登录&#xff1b;登录后可以修改…

【MySQL进阶】单表访问方法

【MySQL进阶】单表访问方法 文章目录【MySQL进阶】单表访问方法一&#xff1a;访问方法&#xff08;access method&#xff09;1&#xff1a;const2&#xff1a;ref3&#xff1a;ref_or_null4&#xff1a;range5&#xff1a;index6&#xff1a;all二&#xff1a;注意事项1&…

黑马点评--Redis消息队列

Redis消息队列 Redis消息队列实现异步秒杀 消息队列&#xff08;Message Queue&#xff09;&#xff0c;字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色&#xff1a; 消息队列&#xff1a;存储和管理消息&#xff0c;也被称为消息代理&#xff08;Message Br…

这就是你了解的指针吗?

小叮当的任意门——指针1. 指针是什么&#xff1f;2. 指针和指针类型1. 指针-整数2. 指针的解引用3. 野指针1. 野指针的成因未初始指针越界访问指针指向的空间释放2. 如何规避野指针4. 指针运算指针减指针指针的关系运算5. 二级指针6. 指针数组1. 指针是什么&#xff1f; 在讲指…

内核的架构 --- 宏内核与微内核

宏内核 宏内核就是把进程管理代码、 内存管理代码、 设备管理代码、 文件管理代码、 各种设备驱动程序代码以及其 他功能模块的代码经过编译&#xff0c; 最后链接在一起&#xff0c; 形成一个大的可执行程序。 这个大程序里有实现支持这些功能的所有代码&#xff0c; 向用户应…

Spring Cloud Nacos 2021使用LoadBalancer做负载均衡

项目源码&#xff1a;https://download.csdn.net/download/weixin_42950079/87150709 Spring Cloud Nacos 2021 移除了 Ribbon&#xff0c;在Spring Cloud Commons 项目中添加了 Spring Cloud LoadBalancer 作为新的负载均衡器 <dependency><groupId>com.alibaba.…

html实现好看的导航主页(附源码)

文章目录1.设计来源1.1 主界面1.2 底部导航1.3 屏幕保护2.效果和源码2.1 动态效果2.2 源代码源码下载作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/128028326 html实现好看的导航主页(附源码) html实现好看的导航主页&…

印度富士康的iPhone产能在扩产,对中国制造将产生深远影响

郑州富士康生产iPhone再次受到影响&#xff0c;这让业界想起当下在国内越来越多见的印度制造的iPhone14&#xff0c;业界猜测苹果和富士康将加快印度产能扩张的进程&#xff0c;推动印度制造的iPhone占比加速提升。目前苹果的三大代工厂中的纬创和富士康都已在印度设厂生产iPho…

关于我给dumi2.0提pr的完整记录

前言 博主最近一年时间在工作业余都在写开源组件库 concis &#xff0c;其中文档站点生成框架采取了 dumi&#xff0c;前几天不久dumi2.0正式发布&#xff0c;博主也是顺势而为直接把项目升级&#xff08;dumi1 -> dumi2&#xff09; 由于dumi2 的站点设计比原来好看太多了…

nios烧写到EPCS的问题处理

原理图如下图&#xff0c;板卡FPGA同时使用2片flash配置芯片&#xff0c;左侧M25P64即EPCS64。2片flash配置芯片使用相同的SPI总线。 在不使用nios的quartus工程中&#xff0c;使用jtag烧写jic的方式固化程序到EPCS64&#xff0c;始终正常。 近期使用含有nios的quartus工程&am…

生物素标记试剂:Alykne-Biotin-DADPS,2241685-22-1,DADPS-生物素-炔

【中文名称】DADPS-生物素-炔&#xff0c;炔基-生物素-二苯基硅烷 【英文名称】 DADPS Biotin Alykne&#xff0c;Alykne-Biotin-DADPS 【结 构 式】 【CAS号】2241685-22-1 【分子式】C42H62N4O9SSi 【分子量】827.12 【基团部分】Alykne基团 【纯度标准】95% 【包装规格】1g&…

小米手机用什么耳机音质好?发烧级音质蓝牙耳机推荐

小米品牌在我们日常生活中经常见到&#xff0c;蓝牙耳机作为现代人的必需品&#xff0c;使用人数一直都是递增的&#xff0c;市面上的蓝牙耳机品牌众多&#xff0c;但很多人不知道哪个牌子音质更好&#xff0c;作为一位耳机发烧友&#xff0c;近几天也是整理了几款音质表现出色…