当前位置: 首页 > news >正文

Spring解决循环依赖:三级缓存

1.什么是循环依赖

通俗来讲,循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
在这里插入图片描述

2.Spring如何解决循环依赖

首先,先介绍Spring是如何创建Bean的。

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:初始化,调用spring xml中的init方法。
循环依赖主要发生在第二步填充属性:在创建对象A时需要填充成员变量B,可是在这时候对象B还没有进行创建。

三级缓存

Spring为了解决单例的循环依赖问题,使用了三级缓存,也就是下文中的三个Map对象。一级缓存用于存放初始化完毕的Bean对象,二级缓存用于存放不完整(仅进行实例化,还没有填充属性)Bean对象,三级缓存用于存放对象工厂(用于创建AOP实例)。

/** Cache of singleton objects: bean name --> bean instance *///单例对象的cacheprivate final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);/** Cache of early singleton objects: bean name --> bean instance *///提前暴光的单例对象的Cacheprivate final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);/** Cache of singleton factories: bean name --> ObjectFactory *///单例对象工厂的cacheprivate final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

步骤:

1.Spring首先从一级缓存singletonObjects中获取。

2.如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。

3.如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.

4.如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

Spring解决循环依赖的步骤如下图所示,以创建A、B对象为例。
在这里插入图片描述

  1. 实例化A对象,将未初始化的A对象实例加入三级缓存,构建A对象工厂。
  2. 进行A对象的属性填充,发现A对象引用了未创建的B对象,进行B对象的创建。
  3. 实例化B对象,B对象属性填充时,发现B对象引用了A对象,按照三级缓存查询的步骤查到了A对象在第三级缓存中有对象工厂,于是将A对象创建出来并将A对象的三级缓存移除,加入到第二级缓存。这时的A对象没有填值,仅包含一个引用。
  4. 完成B对象的属性填充、初始化过程,B对象加入到一级缓存中。
  5. 返回到A对象的属性填充过程,填充B对象到A中,再接着初始化、将A对象加入一级缓存中,最终完成A、B对象的创建。

AOP的实现就是在三级缓存的对象工厂上返回代理对象。
在这里插入图片描述

3.Spring无法解决哪些情况下的循环依赖?

  1. 使用构造方法注入,因为加入singletonFactories三级缓存的前提是执行了构造器来创建半成品的对象,所以构造器的循环依赖没法解决。因此,Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!
  2. Bean的作用于为prototype(原型),因为对于原型bean,spring容器只有在需要时才会实例化,初始化它。spring容器不缓存prototype类型的bean,使得无法提前暴露出一个创建中的bean。

4.面试问题

1.第三级缓存是没有实际作用的,为什么不使用一级缓存和二级缓存能解决循环依赖?
答:如果只用一级缓存来解决循环依赖,那么一级缓存中会在某个时间段存在不完整的bean,这是不安全的。

使用二级缓存确实可以解决循环依赖,但是这要求每个原始对象创建出来后就立即生成动态代理对象(如果有的AOP代理增强话),然后将这个动态代理对象放入二级缓存,这就打破了Spring对AOP的设计原则,即:在对象初始化完毕后,再去创建代理对象。所以引入三级缓存,并且在三级缓存中存放一个对象的ObjectFactory,目的就是:延迟代理对象的创建,这里延迟到啥时候创建呢,有两种情况:第一种就是确实存在循环依赖,那么没办法,只能在需要的时候就创建出来代理对象然后放到二级缓存中,第二种就是不存在循环依赖,那就应该正常地在初始化的后置处理器中创建。
因此不直接使用一级缓存和二级缓存来解决循环依赖的原因就是:希望在不存在循环依赖的情况下不破坏Spring对AOP的设计原则

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

2.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再完成它的整个生命周期。至此,循环依赖结束!

简单点说,Spring解决循环依赖的思路就是:当A的bean需要B的bean的时候,提前将A的bean放在缓存中(实际是将A的ObjectFactory放到三级缓存),然后再去创建B的bean,但是B的bean也需要A的bean,那么这个时候就去缓存中拿A的bean,B的bean创建完毕后,再回来继续创建A的bean,最终完成循环依赖的解决。Spring 利用 三级缓存 巧妙地将出现 循环依赖 时的 AOP 操作 提前到了 属性注入 之前(通过第三级缓存实现的),解决了循环依赖问题。

引用:https://blog.csdn.net/cy973071263/article/details/132676795

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 17-3 向量数据库之野望3 - SingleStoreDB 实践教程
  • MongoDB教程(六):mongoDB复制副本集
  • ant design form动态增减表单项Form.List如何进行动态校验规则
  • AI安全系列——[第五空间 2022]AI(持续更新)
  • 使用 Apache Pulsar 构建弹性可扩展的事件驱动应用
  • 【学习笔记】无人机(UAV)在3GPP系统中的增强支持(十)-服务体验保证的用例
  • 用虚拟机,可以在x86的电脑上虚拟出arm的电脑吗
  • 【轻松拿捏】Java-final关键字(面试)
  • Jmeter-单用户单表查询千条以上数据,前端页面分页怎么做
  • 【Git 学习笔记】第四章 git rebase 变基操作与相关示例(上)
  • 利用OSMnx进行城市路网数据的速度与通行时间推算及分析
  • 【.NET全栈】ASP.NET开发web应用——ASP.NET中的样式、主题和母版页
  • 今天此文堪比出师表最后一句话
  • Redis的中BitMap的应用
  • leetcode算法题(反转链表)
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • Django 博客开发教程 8 - 博客文章详情页
  • JavaScript DOM 10 - 滚动
  • JavaWeb(学习笔记二)
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • pdf文件如何在线转换为jpg图片
  • scala基础语法(二)
  • webpack4 一点通
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 缓存与缓冲
  • 如何学习JavaEE,项目又该如何做?
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 小而合理的前端理论:rscss和rsjs
  • 如何用纯 CSS 创作一个货车 loader
  • #if和#ifdef区别
  • #Linux(Source Insight安装及工程建立)
  • (2020)Java后端开发----(面试题和笔试题)
  • (7)摄像机和云台
  • (C++17) optional的使用
  • (MATLAB)第五章-矩阵运算
  • (pycharm)安装python库函数Matplotlib步骤
  • (二)十分简易快速 自己训练样本 opencv级联lbp分类器 车牌识别
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (附源码)计算机毕业设计大学生兼职系统
  • (三)docker:Dockerfile构建容器运行jar包
  • (十一)手动添加用户和文件的特殊权限
  • (转)JAVA中的堆栈
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • *Django中的Ajax 纯js的书写样式1
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .mysql secret在哪_MySQL如何使用索引
  • .net 8 发布了,试下微软最近强推的MAUI
  • .net core Redis 使用有序集合实现延迟队列
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇
  • .NET 材料检测系统崩溃分析
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题