spring三级缓存以及@Async产生循环引用
spring三级缓存以及@Async产生循环引用
- spring三级缓存介绍
- 三级缓存解除循环引用原理
- 源码对应
- 1、获取A,从三级缓存中获取,没有获取到
- 2、构造A,将A置入三级缓存
- 构造A(创建A实例)
- 置入缓存
- 3、注入属性,构造B
- 扫描缓存实例的相关信息
- 注入属性
- 4、获取B,从三级缓存中没有获取到
- 5、构造B,将B置入三级缓存
- 6、注入属性,构造A
- 7、获取A,三个缓存中三级缓存命中
- 8、创建B实例后续步骤
- 9、B构造完成,实例放入一级缓存,二级三级缓存移除
- 10、创建A实例后续步骤
- 11、A构造完成,实例放入一级缓存,二级三级缓存移除
- @Async导致三级缓存没有解除循环引用
- 复现
- 测试类
- 描述
- 备注
- 启动测试
- 原因分析
- 解决方法
- 参考
spring三级缓存介绍
三级缓存解除循环引用原理
A => B => A
一级缓存 singletonObjects
二级缓存 earlySingletonObjects
三级缓存 singletonFactories
1、获取A,从三级缓存中获取,没有获取到
2、构造A,将A置入三级缓存
3、注入属性,构造B
4、获取B,从三级缓存中没有获取到
5、构造B,将B置入三级缓存
6、注入属性,构造A
7、获取A,从三级缓存中获取,一级没有、二级没有、三级存在。此时通过从三级的BeanFactory构造实例对象,放入二级缓存,移除三级缓存
8、创建B实例后续步骤
9、B构造完成,实例放入一级缓存,二级三级缓存移除
10、创建A实例后续步骤
11、A构造完成,实例放入一级缓存,二级三级缓存移除
源码对应
springboot 2.5.6
1、获取A,从三级缓存中获取,没有获取到
// AbstractBeanFactory#doGetBean 256行
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;// 从三个缓存中依次获取,注意方法点进去allowEarlyReference是true
Object sharedInstance = getSingleton(beanName);...
2、构造A,将A置入三级缓存
构造A(创建A实例)
// AbstractAutowireCapableBeanFactory#doCreateBean 582行
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 这里会通过构造函数创建Bean实例instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//省略代码...
置入缓存
// AbstractAutowireCapableBeanFactory#doCreateBean 613行
//省略代码...
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");}// 这里会把生成beanName的BeanFactory置入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//省略代码...
3、注入属性,构造B
扫描缓存实例的相关信息
InitDestroyAnnotationBeanPostProcessor
会解析缓存类中@PostStruct
和@PreDestroy
等相关信息
CommonAnnotationBeanPostProcessor
会解析缓存类中@Recource
、@WebServiceRef
、@PostStruct
和@PreDestroy
等相关信息
AutowiredAnnotationBeanPostProcessor
会解析缓存类中@Autowired
、@Value
、@Inject
等相关信息
// AbstractAutowireCapableBeanFactory#doCreateBean 594行
//省略代码...
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;}
}
//省略代码...
注入属性
// AbstractAutowireCapableBeanFactory#doCreateBean 619行
//省略代码...
try {// 这里会将注入对应的属性populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//省略代码...// AbstractAutowireCapableBeanFactory#populateBean 1431行
//省略代码...
// 这里会依次遍历 如果是@Resource会在CommonAnnotationBeanPostProcessor中触发注入
// 如果是@Autowired则会在AutowiredAnnotationBeanPostProcessor中触发注入
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;
}
//省略代码...
4、获取B,从三级缓存中没有获取到
5、构造B,将B置入三级缓存
6、注入属性,构造A
上面三个其实类似,同前面三步骤差不多
7、获取A,三个缓存中三级缓存命中
// AbstractBeanFactory#doGetBean 256行 入口
// DefaultSingletonBeanRegistry#getSingleton 150
// 此时,一级缓存和二级缓存里面都没有,三级缓存在第二步里面放进去了,此时能拿到对应的ObjectFactory,
//通过它可以获取对应的实例。然后将实例放入二级缓存,将三级缓存中对应的BeanName移除
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}
8、创建B实例后续步骤
@Async循环引用触发点
这里属性注入完后,剩下bean增强、循环依赖校验、注册
DisposableBean
9、B构造完成,实例放入一级缓存,二级三级缓存移除
// DefaultSingletonBeanRegistry#getSingleton 259行
//省略代码...
// 这在执行链路的上面几层。这里判断是否是新的单例,如果是则置入缓存(一级缓存)中
if (newSingleton) {addSingleton(beanName, singletonObject);
}
//省略代码...protected void addSingleton(String beanName, Object singletonObject) {
// 放入一级缓存,从二级缓存和三级缓存中移除synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}
10、创建A实例后续步骤
11、A构造完成,实例放入一级缓存,二级三级缓存移除
逻辑基本同上
@Async导致三级缓存没有解除循环引用
复现
测试类
@Service
@Slf4j
public class AService {static {log.info("AService init===>");}@Resourceprivate BService bService;public String a2bService(){return bService.get();}@Async("async-2")public void exec(){log.info("aService exec");}}
@Service
@Slf4j
public class BService {static {log.info("BService init===>");}@Autowiredprivate AService aService;public String get(){return "BService 666";}public void exec(){log.info("BService exec");}}
描述
这里
AService
和BService
在同一个目录下,也没有被在别的类中注入(如果有一个类把两者都注入了,且Bservice
先注入,此时如果三个类中该类最先注入,就会导致Bservice
先初始化)。在这种情况下,默认就会Aservice
先初始化,然后在Bservice
初始化,如此,才能复现问题。
如果想要调整类加载顺序,可以通过@DependsOn(value = "AService")
或者@Lazy
备注
别开启懒加载,不然启动不会报错。
spring.main.lazy-initialization=true
启动测试
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] 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.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:649)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434)at org.springframework.boot.SpringApplication.run(SpringApplication.java:338)at com.yichen.casetest.CaseTestApplication.main(CaseTestApplication.java:47)
原因分析
在bean实例后续步骤中,会对bean
增强
// AbstractAutowireCapableBeanFactory#doCreateBean 620行
try {populateBean(beanName, mbd, instanceWrapper);// 初始化bean,对Bean增强// 1、invokeAwareMethods// 2、applyBeanPostProcessorsBeforeInitialization// 3、invokeInitMethods// 4、applyBeanPostProcessorsAfterInitializationexposedObject = initializeBean(beanName, exposedObject, mbd);
}
在
applyBeanPostProcessorsAfterInitialization
中,由于使用了异步线程池(用了@EnableAsync
),使得AsyncAnnotationBeanPostProcessor
注入了spring
容器,它会为原有的bean
实例创建CGLIB
代理,使得最初的bean
和实例化后暴露出去的bean
不是同一个,没有通过循环引用校验,抛出了异常。
// AsyncAnnotationBeanPostProcessor的子类
//AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization 86行
// 如果类中有@Async会创建代理
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);
}
// AbstractAutowireCapableBeanFactory#doCreateBean 632行
// 循环引用检测
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.");}}}
}
解决方法
1、不用异步线程池,自己实现线程池
2、通过@lazy
、@DependsOn
,调整类加载顺序,如果让Bservice
先加载就不会出错
3、梳理业务逻辑,调整技术实现,让AService
和Bservice
不循环引用
参考
Using @Async in a circular dependency will throw a BeanCurrentlyInCreationException
相关文章:

spring三级缓存以及@Async产生循环引用
spring三级缓存以及Async产生循环引用spring三级缓存介绍三级缓存解除循环引用原理源码对应1、获取A,从三级缓存中获取,没有获取到2、构造A,将A置入三级缓存构造A(创建A实例)置入缓存3、注入属性,构造B扫描缓存实例的相关信息注入…...

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(5)
目录 写在前面: 题目:P2036 [COCI2008-2009#2] PERKET - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述: 输入格式: 输出格式: 输入样例: 输出样例: 解题思路: 代码…...

【Unity3D】Unity3D中在创建完项目后自动创建文件夹列表
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 随着项目开发的体量增大,要导入大量的素材、UI、模…...

如何设计一个锂电池充电电路(TP4056)
这个是个单节18650锂电池的充电模块,这个是个18650的锂电池,18指的是它的直径是18mm,65指的是它的高度为65mm。这个18650电池的标称电压是3.7V,电池充满时电压为4.2V,一般电池电压越高也就代表它所剩的电量越大。这种锂…...

Spark了解
目录 1 概述 2 发展 3 Spark和Hadoop 4 Spark核心模块 1 概述 Apache Spark是一个快速、通用、可扩展的分布式计算系统,最初由加州大学伯克利分校的AMPLab开发。 Spark可以处理大规模数据处理任务,包括批处理、迭代式算法、交互式查询和流处理等。Spa…...

c++STL急急急
文章目录cSTL急急急vector头文件扩容过程用法:size/emptyclear迭代器begin/endfront/backpush_back() 和 pop_back()queue头文件用法循环队列 queue用法优先队列 priority_queue用法stack头文件deque头文件deque中控器:用法set头文件用法迭代器begin/end…...

【C++学习】模板进阶——非类型模板参数 | 模板的特化 | 分离编译
🐱作者:一只大喵咪1201 🐱专栏:《C学习》 🔥格言:你只管努力,剩下的交给时间! 模板我们之前一直都在使用,尤其是在模拟STL容器的时候,可以说,模板…...

【C++】C++11新特性——可变参数模板|function|bind
文章目录一、可变参数模板1.1 可变参数的函数模板1.2 递归函数方式展开参数包1.3 逗号表达式展开参数包1.4 empalce相关接口函数二、包装器function2.1 function用法2.2 例题:逆波兰表达式求值2.3 验证三、绑定函数bind3.1 调整参数顺序3.2 固定绑定参数一、可变参数…...

ssm框架之spring:浅聊事务--JdbcTemplate
简介 JdbcTemplate 是 Spring 对 JDBC 的封装,目的是使JDBC更加易于使用,JdbcTemplate是Spring的一部分。JdbcTemplate 处理了资源的建立和释放,它帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流&…...

盘点Python那些简单实用的第三方库
文章目录前言关于本文使用 pip 命令下载第三方库1、phone 库(获取手机号码信息)2、geoip2 库(IP 检测功能)3、freegames 库(免费小游戏)4、jionlp 库(解析地址信息)5、pyqrcode 库&a…...

leetCode热题21-26 解题代码,调试代码和思路
前言 本文属于特定的六道题目题解和调试代码。 1 ✔ [160]相交链表 Easy 2023-03-17 171 2 ✔ [54]螺旋矩阵 Medium 2023-03-17 169 3 ✔ [23]合并K个排序链表 Hard 2022-12-08 158 4 ✔ [92]反转链表 II Medium 2023-03-01 155 5 ✔ [415]字符串相加 Easy 2023-03-14 150 6 …...

ChatGPT推出第四代GPT-4!不仅能聊天,还可以图片创作!
3月15日凌晨,OpenAI震撼发布了多模态预训练大模型 GPT-4。 根据官网发布的通告可以知道,GPT-4 实现了以下几个方面的飞跃式提升:强大的AI创作识图能力;文字输入限制提升至 2.5 万字;回答准确性显著提高;能够…...

二叉搜索树:AVL平衡
文章目录一、 二叉搜索树1.1 概念1.2 操作1.3 代码实现二、二叉搜索树的应用K模型和KV模型三、二叉搜索树的性能分析四、AVL树4.1 AVL树的概念4.2 AVL树的实现原理4.3 旋转4.4 AVL树最终代码一、 二叉搜索树 1.1 概念 二叉搜索树( Binary Search Tree,…...

数据结构和算法(1):数组
目录概述动态数组二维数组局部性原理越界检查概述 定义 在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a col…...

python+django+vue全家桶鲜花商城售卖系统
重点: (1) 网上花店网站中各模块功能之间的的串联。 (2) 网上花店网站前台与后台的连接与同步。 (3) 鲜花信息管理模块中鲜花的发布、更新与删除。 (4) 订单…...

一文带你领略 WPA3-SAE 的 “安全感”
引入 WPA3-SAE也是针对四次握手的协议。 四次握手是 AP (authenticator) 和 (supplicant)进行四次信息交互,生成一个用于加密无线数据的秘钥。 这个过程发生在 WIFI 连接 的 过程。 为了更好的阐述 WPA3-SAE 的作用 …...
Python解题 - CSDN周赛第38期
又来拯救公主了。。。本期四道题还是都考过,而且后面两道问哥在以前写的题解里给出了详细的代码(当然是python版),直接复制粘贴就可以过了——尽管这样显得有失公允,考虑到以后还会出现重复的考题,所以现在…...

Android绘制——自定义view之onLayout
简介 在自定义view的时候,其实很简单,只需要知道3步骤: 测量——onMeasure():决定View的大小,关于此请阅读《Android自定义控件之onMeasure》布局——onLayout():决定View在ViewGroup中的位置绘制——onD…...

用Qt画一个温度计
示例1 以下是用Qt绘制一个简单的温度计的示例代码: #include <QPainter> #include <QWidget> #include <QApplication> class Thermometer : public QWidget { public:Thermometer(QWidget *parent 0); protected:void paintEvent(QPaintEvent …...

Java设计模式 04-建造者模式
建造者模式 一、 盖房项目需求 1)需要建房子:这一过程为打桩、砌墙、封顶 2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的. 3)请编写程序,完成需求. …...

安语未公告于2023年3月20日发布
因一些特殊原因,凡事都是有开始,高潮和结束三大过程,做出以下决定: 所有对 安语未文章 为之热爱、鞭策、奉献,和支持过的开发者: 注:所有资源以及资料都会正常下载和查看 如需联系࿱…...

进销存是什么?如何选择进销存系统?
什么是进销存?进销存软件概念起源于上世纪80年代,由于电算化的普及,计算机管理的推广,不少企业对于仓库货品的进货,存货,出货管理,有了强烈的需求,进销存软件的发展从此开始。 进入…...

基于BP神经网络的图像跟踪,基于BP神经网络的细胞追踪识别
目录 摘要 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络激活函数及公式 基于BP神经网络的细胞识别追踪 matab编程代码 效果 结果分析 展望 摘要 智能驾驶,智能出行是现代社会发展的趋势之一,其中,客量预测对智能出行至关重要,…...

Java面试总结篇
引用介绍 1.线程安全不安全的概念 线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏…...

100天精通Python(可视化篇)——第80天:matplotlib绘制不同种类炫酷柱状图代码实战(簇状、堆积、横向、百分比、3D柱状图)
文章目录0. 专栏导读1. 普通柱状图2. 簇状柱形图3. 堆积柱形图4. 横向柱状图5. 横向双向柱状图6. 百分比堆积柱形图7. 3D柱形图8. 3D堆积柱形图9. 3D百分比堆积柱形图0. 专栏导读 🏆🏆作者介绍:Python领域优质创作者、CSDN/华为云/阿里云/掘金…...

【Java】UDP网络编程
文章目录前言DatagramSocketDatagramPacket注意事项与区别代码演示前言 UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。 UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对…...

Springboot源代码总结
前言 编写微服务,巩固知识 文章目录 前言springboot原理springboot启动流程SpringBoot自动配置底层源码解析自动配置到底配了什么?自动配置类条件注解Starter机制@ConditionalOnMissingBeanSpringBoot启动过程源码解析构造SpringApplication对象SpringBoot完整的配置优先级s…...

JVM监控搭建
文章目录JVM监控搭建整体架构JolokiaTelegrafInfluxdbGrafanaJVM监控搭建 整体架构 JVM 的各种内存信息,会通过 JMX 接口进行暴露。 Jolokia 组件负责把 JMX 信息翻译成容易读取的 HTTP 请求。Telegraf 组件作为一个通用的监控 agent,和 JVM 进程部署在…...

java中如何优化大量的if...else...
目录 策略模式(Strategy Pattern) 工厂模式(Factory Pattern) 映射表(Map) 数据驱动设计(Data-Driven Design) 策略模式(Strategy Pattern) 将每个条件分…...

24. linux系统基础
两个进程间想通讯,必须要通过内核,今天讲的信号其实本质也是讲进程间通讯的意思,那么你为什么可以在shell环境下,可以和一个进程发kill-9啊? shell是不是相当于一个进程?你自己运行的那个进程是不是也相当于…...