spring为什么要使用三级缓存来解决循环依赖
出现循环依赖的原因
AService依赖BService
@Service("aService")
public class AService {@AutowiredBService bService;
}
BService依赖AService
@Service("bService")
public class BService {@AutowiredAService aService;
}
此时就出现了循环依赖
想要搞明白循环依赖,首先要先搞清楚bean的生命周期:SpringBean生命周期-CSDN博客
AService Bean的创建流程
AService创建(doCreateBean方法)的生命周期
0. creatingSet("aService") // 将aService放入 this.singletonsCurrentlyInCreation中 表示aService正在创建中
1.创建一个AService普通对象--->singletonFactories<aService,lambda> // 通过反射创建普通对象 然后将普通对象和BeanDefinition通过一个lambda放入singletonFactories三级缓存中(该lambda会去判断是否需要进行aop等代理操作,如果没有出现循环依赖就不需要执行lambda中的内容),用于在出现循环依赖的情况下便于二级缓存拿到普通对象
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));}
2.填充bService属性--->去单例池中找BService对象--->创建BService的Bean对象
3. 填充其他属性
4.其他操怍 (完成属性依赖后,继续执行初始化前(被@PostConstruct注解修饰的方法)和初始化中(实现了InitializingBean接口并重写afterPropertiesSet())的相关操作)
5.初始化后(AOP)--->如何判断之前是否有过aop呢? earlyProxyReferences这个map会存储执行过aop操作的bean
5.5 在earlySingleonObjects中找到aService对象,让该对象引用完整的AService
if (earlySingletonExposure) {// 会从earlyProxyReferences中获取之前创建的aService对象Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {// 赋值exposedObject = earlySingletonReference;}
6.放入单例池
BService Bean的创建流程
1.创建一个BService普通对象
2.填充aService属性--->去单例池中找AService对象-->creatingSet-->出现了循环依赖-去二级缓存中找->earlySingletonObjects--没找着,接着去三级缓存中去找->singletonFactories--找到后调用存放的lambda表达式->lambda-在lambda中会进行判断AService对象是否使用了aop,如果使用了则会执行代理相关操作,如果没有则不执行->AService代理对象-然后将代理的对象或者实例对象放入二级缓存中,并且删除三级缓存中的该实例->earlySingletonObjects
3.填充其他属性
4.其他操作
5.初始化后
6.放入单例池
循环依赖情况下AService如何感知是否已经进行了aop代理操作
在出现了循环依赖的情况下如果aService需要进行aop操作,在bService已经对aService进行了aop操作的情况下,aService在走到初始化后的aop操作时不应该进行aop操作,那么aService是如何知道bService已经对aService进行了aop操作的呢?
// 两个方法都在AbstractAutoProxyCreator类中,比较两个方法可以// bService去三级缓存中调用lambda中存放的方法,在对aService执行aop相关操作之前会先把该对象存入earlyProxyReferences中@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);// wrapIfNecessary执行aop相关操作return wrapIfNecessary(bean, beanName, cacheKey);}// aService初始化后 ,在执行aop相关操作之前会先判断earlyProxyReferences中是否有该bean,如果有表示执行过aop,就不需要在执行了@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// wrapIfNecessary执行aop相关操作return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
三级缓存总结
1.单例池 singleonObjects:
作用获取已经创建好的bean对象不需要重复生成
2.第二级缓存 earlySingleonObjects:
作用,再出现循环依赖的情况下,如果AService不仅依赖了BService还依赖了CService,而且CService也依赖了AService,
如果没有二级缓存只有三级缓存时,如果是没有aop相关操作的话就没有问题,如果aop操作的话就会出现在BService获取了AService的代理对象后,CService还要重新获取AService代理对象,就会出现B和C依赖的AService不是同一个的情况,为了保证不重复去生产bean保证单例所有需要earlySingleonObjects二级缓存
3.第三级缓存 singletonFactories:
三级缓存的作用是让spring解决循环依赖变得更见方便一点,如果没有第三级缓存,那么当出现循环依赖时,无法拿到被依赖的对象实例。
如果三级缓存不是使用lambda的形式,没有存入普通对象和BeanDefinition,只存了普通实例对象的话,如果该普通对象初始化后需要进行aop(需要aop就需要代理操作,进行代理操作就会产生代理对象,那么就会发生不是同一个对象的情况)操作的话又会出现问题,相似的如果存入的都是代理的对象不仅浪费极大的性能,而且bean对象也不都是需要进行aop操作的,又会出现不是同一个对象的情况
spring三级缓存中最终打破循环的缓存就是第三级缓存
@Async注解对循环循环依赖的影响
@Service("aService")
public class AService {@AutowiredBService bService;@Asyncpublic void test(){System.out.println(bService.toString); }
}
如果AService存在循环依赖和aop相关操作的情况下,AService中的方法还使用了@Async异步注解,那么就会报错
Error creating bean with name 'testBeanLifecycle': Unsatisfied dependency expressed through field 'testBeanLifecycleServiceImpl'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testBeanLifecycleServiceImpl': Bean with name 'testBeanLifecycleServiceImpl' has been injected into other beans [BServiceImpl] 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.
原因是被@Async注解注释的方法和aop切入的方法相同,都有一个对应的BeanPostProcessor来处理
@Overridepublic Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {// Bean初始化后会执行所有相关的BeanPostProcessor 包括aop和@AsyncObject current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;}
但是和aop的postProcessAfterInitialization方法不同,@Async对应的BeanPostProcessor.postProcessAfterInitialization()并不会判断之前是否执行过代理操作,
// @Async相关的BeanPostProcessor @Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {if (this.advisor == null || bean instanceof AopInfrastructureBean) {// Ignore AOP infrastructure such as scoped proxies.return bean;}if (bean instanceof Advised) {Advised advised = (Advised) bean;if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {// Add our local Advisor to the existing proxy's Advisor chain...if (this.beforeExistingAdvisors) {advised.addAdvisor(0, this.advisor);}else {advised.addAdvisor(this.advisor);}return bean;}}if (isEligible(bean, beanName)) {ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);if (!proxyFactory.isProxyTargetClass()) {evaluateProxyInterfaces(bean.getClass(), proxyFactory);}proxyFactory.addAdvisor(this.advisor);customizeProxyFactory(proxyFactory);// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);}// No proxy needed.return bean;}
所以在执行到Async对应的postProcessAfterInitialization时就会又创建一个代理对象,此时代理对象不止一个,Bean判断不是同一个Bean,并且有别的Bean依赖该Bean,抛出异常。
if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {// 和之前三级缓存中使用lambda表达式创建的普通bean对象不是同一个,进入elseif (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()) {// 有依赖该Bean的Bean,但是Bean不相同,抛出异常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.");}}}}
解决方式
@Service("aService")
public class AService {@Autowired@LazyBService bService;@Asyncpublic void test(){System.out.println(bService.toString); }
}
给BService加上@Lazy注解,被@Lazy注解修饰的变量不会立即开始依赖注入,而是直接创建一个代理对象,在该实例被真正调用时才进行注入操作。但是此时AService已经完成了创建,不再有循环依赖的情况发生。
构造方法注入的循环依赖
@Service("aService")
public class AService {BService bService;public AService(BService bService){this.bService = bService;}
}
@Service("bService")
public class BService {AService aService;public BService (AService aService){this.aService= aService;}
}
使用构造器注入发生的循环依赖,spring本身是没有办法处理的
因为spring Bean的创建第一步就是通过构造器来创建普通对象的,此时构造器发生了循环依赖,就没办法创建普通对象了 也就没有后续的将普通对象存入缓存的操作了。
解决方法
解决方法和@Async的解决方案一致,给其中一个构造器上加上@Lazy注解,直接创建代理对象就可以跳出循环依赖。
非单例模式的循环依赖问题
非单例模式下的循环依赖由于创建Bean的过程中不再有三级缓存,没有存放普通对象的地方,所以非单例模式下的循环依赖Spring没有办法解决。
解决方法
和之前一样加上@Lazy注解即可。
@Service("aService")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AService {@AutowiredBService bService;
}
@Service("bService")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BService {@Autowired@LazyAService aService;
}
相关文章:
spring为什么要使用三级缓存来解决循环依赖
出现循环依赖的原因 AService依赖BService Service("aService") public class AService {AutowiredBService bService; } BService依赖AService Service("bService") public class BService {AutowiredAService aService; } 此时就出现了循环依赖 想…...

【自留地】前端 - uniapp - Vue - React - Flutter
uniapp uniapp自用速查表 - 我的常用组件 uniapp自用速查表 - 我的常用组件_uniapp static/customicons.css-CSDN博客文章浏览阅读1.8k次。uniapp项目登录退出、全局变量与状态、本地存储、Tabbar标签栏、顶部导航栏、下拉刷新、触底刷新、Ajax交互、内置组件样式修改、自定义…...
深度学习损失函数
Loss 是深度学习算法中重要的一部分,它的主要功能是评价网络预测的准确性和指导权重更新。合适 Loss 可以让网络收敛更快,预测更准。这个项目介绍了损失函数的基本概念以及7种常用损失函数的形式,性质,参数,使用场景及…...

百度智能云正式上线Python SDK版本并全面开源
文章目录 前言一、SDK的优势二、千帆SDK:快速落地LLM应用三、如何快速上手千帆SDK3.1、SDK快速启动3.2. SDK进阶指引 3.3. 通过Langchain接入千帆SDK4、开源社区 前言 百度智能云千帆大模型平台再次升级!在原有API基础上,百度智能云正式上线…...

Elasticsearch的配置学习笔记
文/朱季谦 Elasticsearch是一个基于Lucene的搜索服务器。它提供一个分布式多用户能力的全文搜索引擎,基于RESTful web接口,Elasticsearch是用Java语言开发的。 关于Elasticsearch系列笔记,主要从Elasticsearch的配置、核心组件、架构设计、使…...

LeetCode(25)验证回文串【双指针】【简单】
目录 1.题目2.答案3.提交结果截图 链接: 验证回文串 1.题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&…...

Android设计模式--工厂模式
一,定义 工厂模式与Android 设计模式--单例模式-CSDN博客,Android设计模式--Builder建造者模式-CSDN博客,Android设计模式--原型模式-CSDN博客 一样,都是创建型设计模式。 工厂模式就是定义一个用于创建对象的接口,让…...

EasyExcel入门使用教程
文章目录 简介一、工程创建🎑二、读操作🎊二、写操作🎄总结 简介 数据导入导出意义 后台管理系统是管理、处理企业业务数据的重要工具,在这样的系统中,数据的导入和导出功能是非常重要的,其主要意义包括以下…...

Golang实现一个一维结构体,根据某个字段排序
package mainimport ("fmt""sort" )type Person struct {Name stringAge int }func main() {// 创建一个一维结构体切片people : []Person{{"Alice", 25},{"Bob", 30},{"Charlie", 20},{"David", 35},{"Eve…...
python语言实现背包问题动态规划
背包问题是一个经典的动态规划问题,实现方式如下: 假设有一个背包,容量为 W,有 n 个物品,每个物品有两个属性:体积 v 和价值 w。要求在不超过背包容量的情况下,选取一些物品放入背包࿰…...

将Python程序(.py)转换为Windows可执行文件(.exe)
python开发者向普通windows用户分享程序,要给程序加图形化的界面(传送门:这可能是最好玩的python GUI入门实例! http://www.jianshu.com/p/8abcf73adba3),并要将软件打包为可执行文件(.exe结尾),那如何将.py转为.exe ? 将.py转为.exe 第一步:安装pyinstaller(临时调用了国内豆…...
Oracle 查找非系统用户结合了10,11,12,19
oracle 12开始有了INHERITEDYES;字段来区分系统用户 select username from dba_users where INHERITEDYES; 对于12以下的版本,按created日期字段筛选会发现创建时间间隔比较大,好区分。 本人当前有个需求需要找出所有数据库的非系统用户,来…...

c++虚函数纯虚函数详解加代码解释
c虚函数纯虚函数详解加代码解释 一.概念:二.虚函数示例及解析:三.纯虚函数示例及解析:四.验证和实际使用及解析:1.子类没有对父类的函数重载,mian()函数调用,是直接返回父类的值2.子类对父类的函数重载&…...
kotlin retrofit
参考博客 【Android】【Kotlin】使用【Retrofit】基本使用 如何在kotlin中正确使用retrofit 将kotlin协程用于网络请求—完整实例,看这一篇就够了 Kotlin协程Retorfit网络请求框架封装...
Web 开发中 route 和 router 有什么区别?
什么是路由? 在 Web 开发中,会经常和路由打交道,可能有的同学并没有仔细思考过到底什么是路由。路由是根据用户请求的 URL 来确定返回给用户的内容或页面的技术,即将 HTTP 请求映射到相应的处理代码,使得用户能够通过…...

VBA技术资料MF83:将Word文档批量另存为PDF文件
我给VBA的定义:VBA是个人小型自动化处理的有效工具。利用好了,可以大大提高自己的工作效率,而且可以提高数据的准确度。我的教程一共九套,分为初级、中级、高级三大部分。是对VBA的系统讲解,从简单的入门,到…...

通信原理板块——脉冲编码调制(PCM)
微信公众号上线,搜索公众号小灰灰的FPGA,关注可获取相关源码,定期更新有关FPGA的项目以及开源项目源码,包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、脉冲编码调制PCM原理 将模拟信号…...

绕过类安全问题分析方法
什么是绕过 逻辑漏洞是指程序设计中逻辑不严密,使攻击者能篡改、绕过或中断程序,令其偏离开发人员预期的执行。 常见表现形式 1、接口(功能类)绕过:即接口或功能中通过某参数,绕过程序校验 2、流程类绕…...

基于STC12C5A60S2系列1T 8051单片的IIC总线器件数模芯片PCF8591实现数模转换应用
基于STC12C5A60S2系列1T 8051单片的IIC总线器件数模芯片PCF8591实现数模转换应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍IIC总线器件数模芯片PCF8591介绍通过按…...

2023年中国骨质疏松治疗仪发展趋势分析:小型且智能将成为产品优化方向[图]
骨质疏松治疗仪利用磁场镇静止痛、消肿消炎的治疗作用迅速缓解患者腰背疼痛等骨质疏松临床症状。同时利用磁场的磁-电效应产生的感生电势和感生电流,改善骨的代谢和骨重建,通过抑制破骨细胞、促进成骨细胞的活性来阻止骨量丢失、提高骨密度。 骨质疏松治…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...