Spring解决循环依赖

目录

什么是spring循环依赖

什么情况下循环依赖可以被处理?

spring 如何解决循环依赖

创建A这个Bean的流程

答疑

疑问:在给B注入的时候为什么要注入一个代理对象?

初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?

三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

假设我们在这里直接使用二级缓存的话,那么意味着所有的Bean在这一步都要完成AOP代理。这样做有必要吗?

总结


什么是spring循环依赖

对于一个对象的字段,在spring里面我们都是通过自动的依赖注入来完成该对象的初始化。

正常的spring创建流程是对象的创建->对象的属性填充->对象的初始化->放入IOC容器。

依赖注入就是a依赖于b,而b又依赖于a,a初始化的时候发现需要实例b,但是b没有创建,就会进入b的创建逻辑。

在b的创建逻辑里面,当b进行初始化的时候又发现a没有创建完毕(a正在创建中),然后又进入a的创建逻辑,如此循环往复。

循环依赖又分为:

  • 字段循环依赖
  • 构造函数循环依赖

//构造函数循环依赖
@Componentpublic class A {// A中注入了B@Autowiredprivate B b;public A(B b) {}
}@Component
public class B {// B中也注入了A@Autowiredprivate A a;public B(A a) {}
}
//当然,这是最常见的一种循环依赖,比较特殊的还有
// 自己依赖自己
@Component
public class A {// A中注入了A@Autowiredprivate A a;
}

什么情况下循环依赖可以被处理?

在回答这个问题之前首先要明确一点,Spring解决循环依赖是有前置条件的

1、出现循环依赖的Bean必须要是单例
2、依赖注入的方式不能全是构造器注入的方式
其中第一点应该很好理解,第二点:不能全是构造器注入是什么意思呢?

//构造函数循环依赖
@Componentpublic class A {public A(B b) {}
}@Component
public class B {public B(A a){}
}

在上面的例子中,A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是无法被解决,如果你的项目中有两个这样相互依赖的Bean,在启动时就出以下错误:Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference

spring 如何解决循环依赖

spring在创建Bean的过程中分为三步

实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法

属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法

初始化,对应方法:AbstractAutowireCapableBeanFactory的initializeBean

实例化,简单理解就是new了一个对象
属性注入,为实例化中new出来的对象填充属性
初始化,执行aware接口中的方法,初始化方法,完成AOP代理

场景驱动一下,还是a依赖于b,b依赖于a的情况。

a1.a的对象工厂通过构造函数实例化并提前暴露a(在三级缓存中存放a的对象工厂)

a2.a完成属性填充后,进行field的初始化,发现b没有创建,进入到b的创建逻辑。

b1.b的对象工厂通过构造函数实例化并提前暴露b(在三级缓存中存放b的对象工厂)

b2.b完成属性填充后,进行field的初始化。

先通过一级缓存(singletonObjects存储的是所有创建好了的单例Bean)去查看是否有已经完成bean创建的a实例,没有,然后再通过二级缓存(earlySingletonObjects完成实例化,但是还未进行属性注入及初始化的对象)去查看是否有提前暴露的a实例,没有,再通过三级缓存(singletonFactorys提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象)去查看有没有提前暴露的a实例。有,将a从三级缓存升级到二级缓存,并通过setter完成b对a的依赖注入。

a3.b完成创建,a可以直接通过一级缓存获得b的实例。

创建A这个Bean的流程

从上图中我们可以看到,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

答疑

疑问:在给B注入的时候为什么要注入一个代理对象?


答:当我们对A进行了AOP代理时,说明我们希望从容器中获取到的就是A代理后的对象而不是A本身,因此把A当作依赖进行注入时也要注入它的代理对象

初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?


答:不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化

三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?


答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

我们思考一种简单的情况,就以单独创建A为例,假设AB之间现在没有依赖关系,但是A被代理了,这个时候当A完成实例化后还是会进入下面这段代码:

// A是单例的,mbd.isSingleton()条件满足// allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足// isSingletonCurrentlyInCreation:正在在创建A,也满足// 所以earlySingletonExposure=trueboolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));// 还是会进入到这段代码中if (earlySingletonExposure) {// 还是会通过三级缓存提前暴露一个工厂对象addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}

看到了吧,即使没有循环依赖,也会将其添加到三级缓存中,而且是不得不添加到三级缓存中,因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖。

假设我们在这里直接使用二级缓存的话,那么意味着所有的Bean在这一步都要完成AOP代理。这样做有必要吗?

不仅没有必要,而且违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

总结

面试官:”Spring是如何解决的循环依赖?“

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

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

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

相关文章

Apache Apisix网关系统历史漏洞复现分析

文章目录 前言CVE-2020-13945默认api令牌CVE-2021-45232未授权接口2.1 默认账户密码导致RCE2.2 未授权访问api接口RCE CVE-2022-24112 地址限制绕过CVE-2022-29266 JWT令牌伪造4.1 漏洞源码简析与修复4.2 漏洞环境搭建与复现 总结 前言 Apache APISIX 是一个动态、实时、高性能…

qt for python创建UI界面

现在很多库都有用到python,又想使用QT creater创作界面,来使用。 1.使用的版本 使用虚拟机安装Ubuntu22.04,Ubuntu使用命令行安装qt,默认安装的是QT5,不用来回调了,就用系统默认的吧,不然安装工具都要费不少事情。pyt…

Docker 第十四章 : Docker 三剑客之 Machine

第十四章 : Docker 三剑客之 Machine 本章知识点: Docker Machine 是 Docker 三剑客之一,它是一个工具,允许用户在本地或远程机器上创建 Docker 主机。它简化了 Docker 环境的设置,特别是在不同的操作系统和云平台上。通过 Docker Machine,用户可以轻松地在虚拟机或物理…

人为物累,心为形役

一、人是什么 你是你,他是他,我是我,有什么区别吗,直到自我发现我与你不同时,不同是什么,身体结构?人生经历?所拥有的一切?暂时搁置这些的话,抽离我们的意识…

在VS里使用C#制作窗口应用

新建项目 创建项目的时候搜索net,选择这个。 打开应该是这样 第一个控件 选择公共控件 - PictureBox - 拖入Form 在Image处选择上传本地资源,建议上传一个小一点的图片。 修改一下尺寸。 ctrls 保存 从“属性”切换到“事件” 双击Click事件…

09、全文检索 -- Solr -- SpringBoot 整合 Spring Data Solr (生成DAO组件 和 实现自定义查询方法)

目录 SpringBoot 整合 Spring Data SolrSpring Data Solr的功能(生成DAO组件):Spring Data Solr大致包括如下几方面功能:Query查询(属于半自动)代码演示:1、演示通过dao组件来保存文档1、实体类…

⭐北邮复试刷题429. N 叉树的层序遍历(按层入队出队BFS)(力扣每日一题)

429. N 叉树的层序遍历 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。 示例 1:输入&a…

Java基于SpringBoot+Vue的图书管理系统

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

第六十四天 服务攻防-框架安全CVE复现Apache shiroApache Solr

第六十四天 服务攻防-框架安全&CVE复现Apache shiro&Apache Solr 知识点: 中间件及框架列表: IIS,Apache,Nginx,Tomcat,Docker,K8s,Weblogic.JBoos,WebSphere, Jenkins,GlassFish,Jetty,Jira,Struts2,Laravel,Solr,Shiro,Thinkphp,Spring, Flask,jQuery等 1、开发框…

USART(串口发送接受单字节)

一、硬件 差分信号不需要太大的压差。在相同的电磁干扰的环境下,因为是双扭线,两根线受干扰的程度是一样的,所以压差相对不变。提高抗干扰能力。485是双绞线传输取的是两线的压差。一般来说受干扰后同步变化,比如都升0.5V或都降5…

OpenAI 发布文生视频模型 Sora,普通人应该怎么做才能利益最大化?

原文链接: OpenAI 发布文生视频模型 Sora,普通人应该怎么做才能利益最大化? 自从 2022 年 11 月 30 日 ChatGPT 发布之后,每次 OpenAI 再发布新功能都跟过年一样,那叫一个热闹。 包括 GPT 4.0,GPT Store&…

SG3225EAN规格书

SG3225EAN 晶体振荡器利用先进的锁相环技术和AT切割晶体单元,提供了宽频率范围和高性能LV-PECL输出,73.5 MHz至700 MHz的宽频率范围,能够保证高稳定性和宽频率调整的能力,适应于多样化的应用需求。2.5V和3.3V两种供电电压&#xf…

压缩感知常用的重建算法

重建算法的基本概念 在压缩感知(Compressed Sensing, CS)框架中,重建算法是指将从原始信号中以低于奈奎斯特率采集得到的压缩测量值恢复成完整信号的数学和计算过程。由于信号在采集过程中被压缩,因此重建算法的目标是找到最符合…

强化学习策略梯度推导

本文主角: ∇ θ J ( θ ) ∝ ∑ s ∈ S μ π θ ( s ) ∑ a ∈ A Q π θ ( s , a ) ∇ θ π θ ( a ∣ s ) \nabla_{\theta}J(\theta) \propto \sum_{s \in \mathcal{S}} \mu^{\pi_{\theta}}(s) \sum_{a \in \mathcal{A}} Q^{\pi_{\theta}}(s, a) \nabla_{\thet…

Unity UGUI的DrawCall优化

Unity UGUI是一种强大的用户界面设计工具,它可以帮助开发者快速创建各种界面元素,从按钮和文本到滑块和面板等。然而,在使用UGUI时,一个常见的性能瓶颈就是DrawCall过多导致的性能下降。在本文中,我们将深入探讨UGUI的…

华为配置直连三层组网直接转发示例

华为配置直连三层组网直接转发示例 组网图形 图1 配置直连三层组网直接转发示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件扩展阅读 业务需求 企业用户接入WLAN网络,以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff…

springboot206基于SpringBoot的农商对接系统的设计与实现

基于Spring Boot的农商对接系统的设计与实现 Design and implementation of agricultural business docking system based on Spring Boot 摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离…

Vue 实现当前页的刷新

Vue 在缓存的基础上实现当前页的刷新 前进刷新,后退不刷新 一、Bus的实现 Bus.js 二、利用Bus实现不同页面的事件传播 1.引入Bus.js(传递)例如:A页面 2.引入Bus.js(接收)例如:B页面 3.路由组件设…

appium实现自动化测试原理

目录 1、Appium原理 1.1、Android Appium原理图文解析 1.1.2、原理详解 1.1.2.1、脚本端 1.1.2.2、appium-server 1.1.2.3、中间件bootstrap.jar 1.1.2.4、驱动引擎uiautomator 1.2、 IOS Appium原理 1、Appium原理 1.1、Android Appium原理图文解析 执行测试脚本全过…

OpenGL学习——17.模型

前情提要:本文代码源自Github上的学习文档“LearnOpenGL”,我仅在源码的基础上加上中文注释。本文章不以该学习文档做任何商业盈利活动,一切著作权归原作者所有,本文仅供学习交流,如有侵权,请联系我删除。L…