Spring依赖注入(四):Bean的循环依赖是如何产生和解决的?

news2025/7/10 11:07:04

前言

其实这篇文章才是正主,前面几篇文章:Spring依赖注入(一):字段注入的方式是如何工作的?Sprng依赖注入(二):setter注入是如何工作的?Sprng依赖注入(三):构造方法注入是如何工作的?,都是是铺垫。从前面可以知道,Sping的Bean依赖注入大致有三种方法:字段注入、setter方法注入、构造方法注入,其中前两种与第三种是有明显区别的,这个区别很关键,和bean循环依赖的解决有着重大关系,而通过本篇文章,会和大家分享一下这其中的秘密:1、什么是循环依赖?2、循环依赖是如何解决的?3、有些循环依赖为什么是解决不了的?

什么是循环依赖?

类A依赖类B,类B依赖类A,像这样两个及以上的类彼此依赖形成了一个闭环,这种情况就叫作循环依赖。如下:Student类依赖了Teacher,Teacher类又反过来依赖了Student。

@Component
public class Student {
    private String name = "小明";
    @Autowired
    private Teacher teacher;
}
@Component
public class Teacher {
    private String name = "李老师";
    @Autowired
    private Student student;
}

怎么解决循环依赖?

依赖注入的方式从实现原理上来说,就三种,分别字段注入、setter方法注入、构造方法注入,且这三种方式都用到了反射技术。就循环依赖来说,如果是两个bean之间发生循环依赖,那么根据不同的注入方式进行组合,有9种组合;如果是三个bean发生循环依赖,那么可以使用注入方式的组合方式会更加复杂;以两个bean之间发生循环依赖为例,不同的注入方式组合后,结果如下:

目标bean

引用依赖bean

循环依赖是否可以解决

字段注入

字段注入

可以解决

字段注入

setter方法注入

可以解决

字段注入

构造方法注入

可以解决

setter方法注入

字段注入

可以解决

setter方法注入

setter方法注入

可以解决

setter方法注入

构造方法注入

可以解决

构造方法注入

字段注入

解决不了

构造方法注入

setter方法注入

解决不了

构造方法注入

构造方法注入

解决不了

从结果上分析来看,凡是目标bean,即最先开始实例化的student对象,是构造方法注入依赖,那么不管引用依赖bean对象(teacher)是什么方式注入的,都会解决不了循环依赖而报错。那么Spring的bean依赖注入其实就可以归纳两类:

1、字段注入、setter方法注入;

2、构造方法注入;

其实前面之所以我要用三篇文章来分别详细分析三种依赖注入方式的工作过程,就在于这里:通过对比,找出每种方式的不同点在哪?那么循环依赖能解决的原理?循环依赖不能解决的原因就再清晰不过了。下面分别来看一下:

字段注入和setter方法注入

在没有发生bean的循环依赖情况下,以示例Student、Teacher类而言,字段注入和setter方法注入方式的工作过程大概是这样的:

1、先实例化student;

2、student实例化完成后,把“半成品”的student放入Spring三级缓存中;

3、开始student依赖属性的注入;

4、这时发现依赖属性teacher未实例化,于是开始teacher的实例化、属性注入、初始化;

5、teacher创建完成后,继续student的属性注入,即使用java反射把teacher对象注入到student内,然后student的创建过程结束;

在Student、Teacher之间发生循环依赖的情况下工作流程是什么样的呢?

1、先实例化student;

2、student实例化完成后,把“半成品”的student放入Spring三级缓存中;

3、开始student依赖属性的注入;

4、这时发现依赖属性teacher未实例化,于是开始teacher的实例化、属性注入、初始化;但是开始Teacher的实例化完后,开始属性注入teacher的属性student对象,但是student此时还在等teacher创建完成,正常情况下,这么互相等对方,根本没法玩;然而Spring有三级缓存,缓存的key是beanName,但是value不是一个具体的对象,而用lambda表达式写的接口ObjectFactory,ObjectFactory接口里有一个抽象方法getObject(),这里是什么意思呢?ObjectFactory#getObject()方法被触发的时候,会执行lambda表达式的内容; 而lambda表达式里的getEarlyBeanReference()正是从三级缓存里取出完成实现化、未属性注入的半成品bean的引用;teacher对象拿到半成品的student对象引用,就可以完成teacher的属性注入、初始化;

5、teacher通过使用Spring三级缓存里的半成品student完成创建后,student就可以使用创建好的teacher完成依赖属性注入,即使用java反射把teacher对象注入到student内,然后student的创建过程结束;

构造方法注入

在没有发生bean的循环依赖情况下,以示例Student、Teacher类而言,字段注入和setter方法注入方式的工作过程大概是这样的:

1、调用Student的有参数构造方法开始bean的实例化;

2、Student的有参数构造方法需要注入Teacher,但是Teacher实际未开始实例化;

3、开始去实例化Teacher;

4、Teacher实例化、属性注入、初始化完成后,往Student的有参数构造方法里注入,student的依赖注入完成;

5、student对象会被放入到Spring的三级缓存;

6、student对象其他的依赖属性注入;

7、student对象初始化,然后student创建过程结束;

1、调用Student的有参数构造方法开始bean的实例化;

2、Student的有参数构造方法需要注入Teacher,但是Teacher实际未开始实例化;

3、实例化Teacher完后,进行依赖属性注入时,发现teacher对象又依赖了student对象,而此时student对象还未实例化完成,一级、二级、三级缓存里还空空如也呢,就这样student对象等着teacher创建完成后再用构造方法注入,teacher也等student创建完成后再注入;如图红色部分,这种情况,bean的循环依赖无法解决,原因就在于构造方法注入这种方式是把bean实例化、属性注入合成了一步,没办法使用到Spring的三级缓存;

小结

循环依赖的解决原理

字段注入和setter方法注入,这两种方式下的循环依赖之所以能够解决,在于bean在实例化、属性注入分成了两步,且bean实例化后、未开始属性注入前,提前暴露在了Spring的三级缓存中,如果发生循环依赖,那么可以通过三级缓存提前拿到未实例化完成半成品bean的引用,互相完成bean属性注入后,半成品的bean就变成了一个完整bean。

循环依赖不能解决的原因

构造方法注入,这种方式不能解决的原因,在于bean的实例化、属性注入合成了一步,循环依赖发生时,两个bean都没有实例化完成,且不能提前暴露Spring的三级缓存中,所以就会发生异常;

Spring中bean创建核心逻辑都在AbstractAutowireCapableBeanFactory#doCreateBean(),建议反复研究研究。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {
 
   // ------start-----bean实例化-------------------
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   Object bean = instanceWrapper.getWrappedInstance();
   Class<?> beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }
    // ------end-----bean实例化-------------------
   // ------start----bean后置处理器-------------------
   synchronized (mbd.postProcessingLock) {
      if (!mbd.postProcessed) {
         try {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
         }
         catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Post-processing of merged bean definition failed", ex);
         }
         mbd.postProcessed = true;
      }
   }
    //------end----bean后置处理器-------------------
    //------start----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }
    //------end----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
   Object exposedObject = bean;
   try {
       //bean的属性注入
      populateBean(beanName, mbd, instanceWrapper);
      //bean的初始化
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
         throw (BeanCreationException) ex;
      }
      else {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
      }
   }
   if (earlySingletonExposure) {
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }
   // Register bean as disposable.
   try {
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }
 
   return exposedObject;
}

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

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

相关文章

easyExcel与poi版本不兼容导致的后台报错问题

1、背景&#xff1a;最新接手公司系统excel导入解析模块&#xff0c;点击批量导入&#xff0c;后台报错如下 com.alibaba.excel.exception.ExcelAnalysisException: java.lang.NoClassDefFoundError: org/apache/poi/poifs/filesystem/FileMagicat com.alibaba.excel.analysis.…

CycleGAN代码使用入门

以下内容为本人亲测使用过程&#xff0c;完成了橘子到苹果的AI转化效果&#xff0c;先上效果&#xff1a; 1、下载数据集 Index of /cyclegan/datasets 本次做的是苹果和橘子相互转化的实验&#xff0c;所以下载apple2orange.zip数据集 2、下载代码 github地址为&#xff1a;…

ChatGPT从下游应用“火”到了上游芯片厂,国内谁将受益?

因库存陷入低迷周期的半导体市场近日因ChatGPT的火热而重新受到外界关注。 原文链接&#xff1a;ChatGPT从下游应用“火”到了上游芯片厂&#xff0c;国内谁将受益&#xff1f; 由于ChatGPT属于生成式AI&#xff0c;被誉为“AI芯片”第一股的英伟达应声而涨。2月13日收盘&#…

Go高质量编程与性能调优-学习笔记

1 高质量编程 1.1 简介 1.1.1 高质量代码 高质量代码即正确可靠、简洁清晰的代码 1.1.2 编程原则 简单性可读性生产力1.2 编码规范 1.2.1 代码格式 推荐gofmt自动格式化代码&#xff01; 推荐goimports实现gofmt依赖包管理&#xff01; 1.2.2 注释 注释要解释代码作用、…

5个设计师常用素材库

推荐5个设计素材网站&#xff0c;免费下载&#xff01; 1、菜鸟图库 菜鸟图库-免费设计素材下载 菜鸟图库是一个素材量非常丰富的网站&#xff0c;该网站聚合了平面、UI、淘宝电商、高清背景图、图片、插画等高质量素材。平面设计模板非常多&#xff0c;很多都能免费下载&…

springmvc实现controller接口

springmvc实现controller接口 前置配置 基础环境 springmvc 环境 jdk1.8 tomcat8.5 集成环境 ideasmart-tomcat (idea 中 tomcat插件) 实现controller接口 import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Control…

UNIAPP实战项目笔记59 NodeJS后端生成token 和 修复一些bug

UNIAPP实战项目笔记59 NodeJS后端生成token 和 修复一些bug 后端保持数据时往数据库写入token 修复一些前面遗留的问题bug 实际案例图片 后端接口文件 index.js var express require(express); var router express.Router(); var connection require(../db/sql.js); var us…

11、STM32通用定时器输出PWM

目录 1.通用定时器输出PWM 2.PWM的工作原理 3.PWM的内部运作机制 4.PWM的模式 41.边沿对齐模式 5.自动加载的预加载寄存器 6.定时器输出PWM结构体讲解 7.定时器输出PWM库函数讲解 8.定时器输出PWM----实战驱动SG90舵机 1.通用定时器输出PWM 以TIM3为例&#xff0c;STM…

makdown模版参考

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

一次性打包学透 Spring

不知从何时开始&#xff0c;Spring 这个词开始频繁地出现在 Java 服务端开发者的日常工作中&#xff0c;很多 Java 开发者从工作的第一天开始就在使用 Spring Framework&#xff0c;甚至有人调侃“不会 Spring 都不好意思自称是个 Java 开发者”。 之所以出现这种局面&#xf…

危害肠道健康的两大敌人:诺如病毒和轮状病毒

谷禾健康 // 近日&#xff0c;多地发生诺如病毒感染事件&#xff0c;诺如病毒引起的急性感染性腹泻进入发病高峰期。那么什么是诺如病毒&#xff1f;我们又该如何预防&#xff1f; 诺如病毒和轮状病毒都是传染性很强的肠道病毒&#xff0c;是导致急性胃肠炎的最重要原因之一。会…

如何自己搭建一个ai画图系统? 从0开始云服务器部署novelai

如何自己搭建一个ai画图系统&#xff1f; 从0开始云服务器部署novelai ​ 上面两张图都是通过ai生成的&#xff0c;是不是有以假乱真的感觉。 本教程提供的是自己搭建一个可以外网访问的ai系统的方法&#xff0c;需要采购gpu服务器&#xff08;后续会出白嫖的方式&#xff09;&…

Java俄罗斯方块游戏

技术&#xff1a;Java等摘要&#xff1a;俄罗斯方块是一款十分经典的游戏&#xff0c;它的主要运行规律为对系统随机产生的图形进行上下左右移动、旋转等操纵&#xff0c;使之排列成完整的一行或多行并且消除得分。它上手容易&#xff0c;难度循序渐进&#xff0c;老少皆宜&…

Android 架构 MVC MVP MVVM,这一波你应该了然于心

MVC&#xff0c;MVP和MVVM是软件比较常用的三种软件架构&#xff0c;这三种架构的目的都是分离&#xff0c;避免将过多的逻辑全部堆积在一个类中。在Android中&#xff0c;Activity中既有UI的相关处理逻辑&#xff0c;又有数据获取逻辑&#xff0c;从而导致Activity逻辑复杂不单…

Android入门第66天-使用AOP

开篇这篇恐怕又是一篇补足网上超9成关于这个领域实际都是错的、用不起来的一个知识点了。网上太多太多教程和案例用的是一个叫hujiang的AOP组件-com.hujiang.aspectjx:gradle-android-plugin-aspectjx。首先这些错的文章我不知道是怎么来的&#xff0c;其次那些案例真的运行成功…

数据库浅谈之 Bloom Filter

数据库浅谈之 Bloom Filter HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是数据库浅谈系列&#xff0c;收录在专栏 DATABASE 中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列阿呆将记录一些数据库领域相关的知识 &am…

场景扩展,体验升级 | DBMotion新增无公网数据库迁移、支持监控报警等多项功能

丝滑的零停机数据库在线迁移工具——DBMotion&#xff0c;又双叒叕发新版&#xff1a;新增的网关、数据源功能&#xff0c;让你无公网IP的数据库也可以迁移&#xff1b;新增的监控功能&#xff0c;让你对迁移性能一目了然&#xff1b;新增的报警功能&#xff0c;让你及时获得同…

什么是SSL端口?HTTPS配置技术指南

安全套接字层&#xff08;SSL&#xff09;是负责互联网连接的数据身份验证和加密的技术。它加密在两个系统之间&#xff08;通常在服务器和客户端之间&#xff09;之间通过互联网发送的数据&#xff0c;使其保持私密。随着在线隐私的重要性日益增加&#xff0c;您应该熟悉SSL端…

【C语言】指针的定义和使用

指针一、什么是指针二、指针类型三、指针和数组的关系四、空指针五、野指针一、什么是指针 指针&#xff08;Pointer&#xff09;是编程语言中的一个对象&#xff0c;通过地址直接指向内存中该地址的值。由于通过地址能够找到所需的变量存储单元&#xff0c;可以说地址指向该变…

小样本学习

机器学习就是从数据中学习&#xff0c;从而使完成任务的表现越来越好。小样本学习是具有有限监督数据的机器学习。类似的&#xff0c;其他的机器学习定义也都是在机器学习定义的基础上加上不同的限制条件衍生出来。例如&#xff0c;弱监督学习是强调在不完整、不准确、有噪声、…