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;
}

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

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

相关文章

应用软件安全编程--17预防基于 DOM 的 XSS

DOM型XSS从效果上来说也属于反射型XSS,由于形成的原因比较特殊所以进行单独划分。在网站页面中有许多页面的元素&#xff0c;当页面到达浏览器时浏览器会为页面创建一个顶级的Document object 文档对象&#xff0c;接着生成各个子文档对象&#xff0c;每个页面元素对应一个文档…

11.16 知识总结(模型层更多内容)

一、 多表查询&#xff08;跨表查询&#xff09; <br class"Apple-interchange-newline"><div></div> 子查询&#xff1a;分步查询 链表查询&#xff1a;把多个有关系的表拼接成一个大表(虚拟表) inner join left join right join 1.1 基于双下划…

【2016年数据结构真题】

已知由n&#xff08;M>2&#xff09;个正整数构成的集合A{a<k<n},将其划分为两个不相交的子集A1 和A2&#xff0c;元素个数分别是n1和n2&#xff0c;A1和A2中的元素之和分别为S1和S2。设计一个尽可能高效的划分算法&#xff0c;满足|n1-n2|最小且|s1-s2|最大。要求…

Ubuntu16.04上安装Docker

Ubuntu16.04上安装Docker 更新 apt 包索引: sudo apt-get update安装依赖包,以便使用 HTTPS 仓库 sudo apt-get install apt-transport-https ca-certificates curl software-properties-common添加 Docker GPG 密钥 curl -fsSL https://download.docker.com/linux/ubuntu…

JVM——运行时数据区(堆+方法区+直接内存)

目录 1.Java堆2.方法区**方法区&#xff08;Method Area&#xff09;溢出**方法区&#xff08;Method Area&#xff09;字符串常量池静态变量的存储 3.直接内存(Direct Memory) 1.Java堆 ⚫ 一般Java程序中堆内存是空间最大的一块内存区域。创建出来的对象都存在于堆上。 ⚫ 栈…

matlab二维曲面散点图插值方法

在 MATLAB 中&#xff0c;你可以使用以下函数进行二维曲面散点插值&#xff1a; griddata: 该函数可以在散点数据上进行二维插值&#xff0c;生成平滑的曲面。它支持多种插值方法&#xff0c;包括三次样条插值、最近邻插值、线性插值和自然邻近法插值。 scatteredInterpolant:…

update与merge基本语法规则、异同点、使用场景

– UPDATE更新数据SQL UPDATE /append parallel(A,8) nologging/ CRSMAK.M_CM_GS_BILL_DISH_INFO A --票据贴现信息 SET A.YEAR_PAR_AMT (SELECT /parallel(B,8)/ YEAR_PAR_AMT FROM (SELECT BIZ_ID, SUM(NVL(PAR_AMT, 0)) YEAR_PAR_AMT --年累计票面金额 FROM CRSMAK.M_CM_G…

存钱虚拟计划,嘚

存钱计划—虚拟 2024年 (第一年) 1月 2月 3月 4月 5月 6 月 7月 8月 9月 10月 11月 12月 1w 1w 1w 1w 1w 1w 1w 1w 1w 1w 1w 1w 2025年(第二年) 1w1w*102.5% A 懒得写A代替 A A A A A A A A A A 2026年(第三年) (1w1w*10…

Centos7.9用rancher来快速部署K8S

什么是 Rancher&#xff1f; Rancher 是一个 Kubernetes 管理工具&#xff0c;让你能在任何地方和任何提供商上部署和运行集群。 Rancher 可以创建来自 Kubernetes 托管服务提供商的集群&#xff0c;创建节点并安装 Kubernetes&#xff0c;或者导入在任何地方运行的现有 Kube…

React路由跳转但是页面未发生改变,刷新后正常显示

react项目&#xff0c;配置好路由后&#xff0c;触发跳转发现url已经改变但页面内容未改变&#xff0c;刷新后才能正常显示。 发现是被react的严格模式影响&#xff0c;将BrwserRouter放到其外层即可 const root ReactDOM.createRoot(document.getElementById("root&qu…

OpenCV入门5——OpenCV的算术与位运算

文章目录 图像的加法运算图像的减法运算图像的乘除运算图像的融合OpenCV位运算-非操作OpenCV位操作-与运算OpenCV位操作-或与异或为图像添加水印 图像的加法运算 # -*- coding: utf-8 -*- import cv2 import numpy as npimg cv2.imread(E://pic//4.jpg)# 图的加法运算就是矩阵…

EasyCVR视频监控+AI智能分析网关如何助力木材厂安全生产?

旭帆科技有很多工厂的视频监管方案&#xff0c;小编也经常分享出来供大家参考。近期&#xff0c;又有伙伴后台私信我们想要关于木材厂的方案。针对木材厂的生产过程与特性以及安全风险等&#xff0c;我们来分享一下相关的监管方案&#xff1a; 1&#xff09;温湿度监测&#xf…

技巧篇:Mac 环境PyCharm 配置 python Anaconda

Mac 中 PyCharm 配置 python Anaconda环境 在 python 开发中我们最常用的IDE就是PyCharm&#xff0c;有关PyCharm的优点这里就不在赘述。在项目开发中我们经常用到许多第三方库&#xff0c;用的最多的命令就是pip install 第三方库名 进行安装。现在你可以使用一个工具来帮你解…

three.js相机调用reset方法复原但无法完全复原

目录 一、问题 二、原因 三、总结 一、问题 需求&#xff1a;经过多次旋转、平移等变换后&#xff0c;希望恢复到初次渲染的角度、位置。结果发现&#xff1a; three.js oribtControls调用 一次reset方法 无法完全复原;需要多次调用后才能复原。 this.orbitControls.reset…

前端开发学习 (一) 搭建Vue基础环境

一、环境搭建 1、安装nodejs #下载地址 https://nodejs.org/dist/v20.9.0/node-v20.9.0-x64.msi 2、配置环境变量 上面下载完安装包后自行安装&#xff0c;安装完成后安装下图操作添加环境变量 #查看版本 node --version v20.9.0# npm --version 10.1.03、配置npm加速源 np…

2.c++基础语法

文章目录 1.c 程序结构关键字标识符、操作符、标点预处理指令注释main 主函数命名空间 2.c 变量和常量变量 3.c 数组和容器4.c 程序流程5.c字符和字符串 1.c 程序结构 关键字 关键字事程序保留的&#xff0c;程序员不能使用&#xff0c;c的常见关键字如下图&#xff1a; 标识…

机器人导航+OPENCV透视变换示例代码

透视变换又称四点变换&#xff0c;所以不能用于5边形这样的图形变换&#xff0c;不是真正的透视变换&#xff0c;但是这个方法可以把机器人看到的图像转换为俯视图&#xff0c;这样就可以建立地图&#xff0c;要不然怎么建立地图呢。 void CrelaxMyFriendDlg::OnBnClickedOk()…

Python如何调用ixchariot进行吞吐量测试

Python如何调用ixchariot进行吞吐量测试 要使用Python调用IxChariot进行吞吐量测试&#xff0c;您可以使用 subprocess 模块来执行IxChariot的TCL命令行。下面是一个简单的示例代码&#xff1a; import subprocess# 定义IxChariot的安装路径和测试脚本路径 ixchariot_path &q…

Ubuntu 18.04无网络连接的n种可能办法

文章目录 网络图标消失&#xff0c;Ubuntu无网络连接VMware上Ubuntu18.04&#xff0c;桥接了多个网卡&#xff0c;其中一个用来上网&#xff0c;均设置为静态ip网络桥接链路没有接对路由不对 网络图标消失&#xff0c;Ubuntu无网络连接 sudo service network-manager stop sud…