JetCache启动循环依赖分析
问题呈现
项目性能优化,需要将本地内存(JVM内存)替换为本地Redis(同一个Pod中的Container),降低JVM内存和GC的压力,同时引入了JetCache简化和统一使用(对JetCache也做了扩展,支持了本地Redis)。引入JetCache后,项目启动就会报循环依赖的错误,错误入口则是项目中使用@PostConstruct调用JetCache加载预热缓存的地方,错误堆栈如下:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'globalCacheConfig' defined in class path resource [cn/jojo/edu/jetcache/autoconfigure/JetCacheAutoConfiguration.class]: Circular depends-on relationship between 'globalCacheConfig' and 'redissonAutoInit'at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:305) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1155) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at cn.jojo.edu.jetcache.anno.field.LazyInitCache.init(LazyInitCache.java:83) ~[jetcache-anno-ss2.6.9-20241025.105436-2.jar:?]at cn.jojo.edu.jetcache.anno.field.LazyInitCache.checkInit(LazyInitCache.java:66) ~[jetcache-anno-ss2.6.9-20241025.105436-2.jar:?]at cn.jojo.edu.jetcache.anno.field.LazyInitCache.put(LazyInitCache.java:159) ~[jetcache-anno-ss2.6.9-20241025.105436-2.jar:?]at cn.jojo.edu.malacca.integration.cache.AbstractLocalRedisCacheService.put2Cache(AbstractLocalRedisCacheService.java:92) ~[classes/:?]at cn.jojo.edu.malacca.integration.cache.AbstractLocalRedisCacheService.forceRefreshCache(AbstractLocalRedisCacheService.java:140) ~[classes/:?]at cn.jojo.edu.malacca.api.server.config.PreheatInit.execute(PreheatInit.java:96) ~[classes/:?]at cn.jojo.edu.malacca.api.server.config.PreheatInit.lambda$init$0(PreheatInit.java:73) ~[classes/:?]at cn.jojo.infra.sdk.context.request.RequestContextHolder.setContext(RequestContextHolder.java:162) ~[microservice-sdk-1.7.29.jar:?]at cn.jojo.edu.malacca.api.server.config.PreheatInit.init(PreheatInit.java:71) ~[classes/:?]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_65]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_65]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_65]at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_65]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]... 18 more
看错误日志问题是出在预热调用LazyInitCache.put方法时,该方法内部首先会获取GlobalCacheConfig对象,让其优先初始化,就是在初始化该对象时报的循环依赖错误:
存在以下几个奇怪的问题:
- 错误提示的是globalCacheConfig和redissonAutoInit产生了循环依赖,但是看GlobalCacheConfig本身并没有依赖其它复杂的对象,只是在初始化时依赖了SpringConfigProvider、JetCacheProperties和AutoConfigureBeans,globalCacheConfig和redissonAutoInit的依赖是如何产生的呢?
- 为什么在@PostConstruct中预热会报错,但在@EventListener(ApplicationReadyEvent.class)中预热却不会报错?
- 为什么配置启用了多个缓存组件才会报错,只启用一个缓存组件却不会?
- Spring通过三级缓存解决了非构造函数注入产生的循环依赖,那为什么这个循环依赖没有被解决呢?
分析过程
依赖关系分析
RedissonAutoInit对GobalCacheConfig的依赖比较好发现,从图中可以看到是父类中引用了ConfigProvider,在ConfigProvider中又引用了GlobalCacheConfig,不过从图中也无法找出GobalCacheConfig对RedissonAutoInit的依赖从何而来。
于是查看报循环依赖错误的源码:
这部分源码位于AbstractBeanFactory.doGetBean方法中,在创建Bean之前会对有设置dependsOn属性的BeanDefinition(以下简称BD)校验是否有循环依赖,但是GlobalCacheConfig类中并没有标记@DependsOn注解,还有哪里可以设置呢?不难想到Spring本身提供的一些扩展点是可以修改BD属性的,比如BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,最终找到一个BeanFactoryPostProcessor的实现类BeanDependencyManager:
public class BeanDependencyManager implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {String[] autoInitBeanNames = beanFactory.getBeanNamesForType(AbstractCacheAutoInit.class, false, false);if (autoInitBeanNames != null) {BeanDefinition bd = beanFactory.getBeanDefinition(JetCacheAutoConfiguration.GLOBAL_CACHE_CONFIG_NAME);String[] dependsOn = bd.getDependsOn();if (dependsOn == null) {dependsOn = new String[0];}int oldLen = dependsOn.length;dependsOn = Arrays.copyOf(dependsOn, dependsOn.length + autoInitBeanNames.length);System.arraycopy(autoInitBeanNames,0, dependsOn, oldLen, autoInitBeanNames.length);bd.setDependsOn(dependsOn);}}}
在这里将获取到的AbstractCacheAutoInit的子类都设置到了GlobalCacheConfig的dependsOn属性中(题外话这里并不是所有实现了AbstractCacheAutoInit的子类都会设置进去,还要看是否满足条件,Jetache提供了JetCacheCondition,只有配置启用了的缓存组件对应的Init才会初始化并设置到dependsOn中)。
至此,循环依赖就产生了。
循环依赖错误根因分析
有了循环依赖,但为什么Spring的三级缓存没有解决该问题,以及为什么是在某些条件下才会报错呢?本节就对剩余几个问题进行分析。
上面的时序图是在启用了Caffeine和Redisson缓存且使用@PostConstruct预热缓存的前提下的启动初始化过程,首先BeanDependencyManager获取到CaffeineAutoInit和RedissonAutoInit对象并设置到GlobalCacheConfig BD的dependsOn属性中。接着在@PostConstruct标记的方法执行,调用put预热缓存,在put中会优先调用getBean初始化GlobalCacheConfig。
GlobalCacheConfig初始化时就会进入到这段代码,循环依次判断依赖的对象是否有循环依赖以及调用getBean对其进行初始化。
首先是CaffeineAutoInit,在判断无循环依赖后会注册CaffeineAutoInit -> GlobalCacheConfig的依赖关系,表示CaffeineAutoInit依赖GlobalCacheConfig对象(下文同理);注册依赖关系后调用getBean初始化CaffeineAutoInit时其实也会进入到这个方法,只不过该对象没有dependsOn,所以直接跳过了这段代码,进入到实例化的逻辑:
熟悉Spring初始化逻辑的就知道该方法中创建完Bean就会对其进行依赖注入,而CaffeineAutoInit的父类中依赖了SpringConfigProvider对象:
因此又会实例化SpringConfigProvider对象,同样的该对象中又依赖了GlobalCacheConfig,所以又再次触发GlobalCacheConfig的创建,所以会再一次判断GlobalCacheConfig depnedsOn的对象和自己是否有循环依赖。
因为CaffeineAutoInit在前面已经初始化完成了,所以这次只是简单判断一下,接着判断并初始化RedissonAutoInit,这里又会注册RedissonAutoInit -> GlobalCacheConfig的依赖关系,然后又依赖注入SpringConfigProvider,依赖注入完成后会注册SpringConfigProvider -> RedissAutoInit的依赖关系(这里就是导致报错的关键步骤)。
到这里,GlobalCacheConfig注入到SpringConfigProvider完成,进入到图中第六步操作,注册GlobalCacheConfig -> SpringConfigProvider依赖关系,紧接着SpringConfigProvider注入到CaffeineAutoInit完成,注册SpringConfigProvider -> CaffeineAutoInit依赖关系。然后又一次判断并初始化RedissonAutoInit:
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {if (alreadySeen != null && alreadySeen.contains(beanName)) {return false;}String canonicalName = canonicalName(beanName);Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);if (dependentBeans == null) {return false;}if (dependentBeans.contains(dependentBeanName)) {return true;}for (String transitiveDependency : dependentBeans) {if (alreadySeen == null) {alreadySeen = new HashSet<>();}alreadySeen.add(beanName);if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {return true;}}return false;}
dependentBeanMap就是存储依赖关系的容器,canonicalName则是globalCacheConfig,dependentBeanName是redissonAutoInit,所以dependentBeans就是springConfigProvider,递归再次判断就有了R -> S -> G - R的循环依赖。
看到这,对于剩余几个问题就很容易理解了,下面总结概括下:
当在@PostConstruct中调用put方法保存缓存时,会优先创建初始化GlobalCacheConfig对象,该对象存在dependsOn依赖的对象,Spring会对有dependsOn的BD在实例化之前判断是否有循环依赖,当dependsOn只有一个时,三级缓存帮我们解决了循环依赖的问题,不会报错;而当有多个时,实例化第一个依赖的对象CaffeineAutoInit时,会导致GlobalCacheConfig的嵌套创建,在第二次创建时,第二个依赖对象RedissonAutoInit会注册依赖关系,返回到首次创建的地方再次判断RedissonAutoInit的依赖关系,就得到循环依赖的结果。而当GlobalCacheConfig延迟主动实例化时(只要在它之前随便先创建一个Init对象),直接就从单例缓存中就获取到了对象(依赖注入会创建该对象并放入缓存),进入不到这个判断,所以就不会报错(依赖注入触发的GlobalCacheConfig实例化虽然会进入这个判断,但不会导致嵌套创建),感兴趣的可以自行画一下创建过程。
最后还有个问题,为什么Spring要在createBean之前对有dependsOn属性的BD判断是否有循环依赖,是为了应对什么场景?没有这个判断,针对这个场景三级缓存是否也能解决循环依赖的问题呢?
相关文章:
JetCache启动循环依赖分析
问题呈现 项目性能优化,需要将本地内存(JVM内存)替换为本地Redis(同一个Pod中的Container),降低JVM内存和GC的压力,同时引入了JetCache简化和统一使用(对JetCache也做了扩展&#x…...
【科研绘图】3DMAX管状图表生成插件TubeChart使用方法
3DMAX管状图表生成插件TubeChart,一款用于制作3D管状图表的工具。可以自定义切片的数量以及随机或指定切片颜色。 【版本要求】 3dMax 2008及更高版本 【安装方法】 TubeChart插件无需安装,使用时直接拖动插件脚本文件到3dMax视口中打开即可࿰…...
基于SSM土家风景文化管理系统的设计
管理员账户功能包括:系统首页,个人中心,用户管理,景点分类管理,热门景点管理,门票订单管理,旅游线路管理,系统管理 前提账号功能包括:系统首页,个人中心&…...
C++超强图片预览器
下载 文件打开关联 关键代码 uint32_t getSrcPx3(const cv::Mat& srcImg, int srcX, int srcY, int mainX, int mainY) const {cv::Vec3b srcPx = srcImg.at<cv::Vec3b>(srcY, srcX);intUnion ret = 255;if (curPar.zoomCur < curPar.ZOOM_BASE && src…...
网络搜索引擎Shodan(2)
声明:学习视频来自b站up主 泷羽sec,如涉及侵权马上删除文章 声明:本文主要用作技术分享,所有内容仅供参考。任何使用或依赖于本文信息所造成的法律后果均与本人无关。请读者自行判断风险,并遵循相关法律法规。 感谢泷…...
【Tableau】
Tableau 是一款强大且广泛使用的数据可视化和商业智能(BI)工具,用于帮助用户分析、探索和呈现数据。它通过直观的拖放界面,允许用户轻松创建动态仪表板和报告,而无需编写代码。Tableau 可处理多种数据源,如…...
分类与有序回归
分类问题 分类问题,例如分类猫、狗、猪时,使用数字进行表示为1,2,3。而1、2、3之间有大小,分类算法为了平衡标签之间的差异,使得损失公平,会使用one-hot编码。例如,分别使用&#x…...
Mac如何实现高效且干净的卸载应用程序
使用Mac卸载应用程序,你还在使用废纸篓这个办法吗,看不见卸载了什么,看不见清理了多少,真的不会有残留吗 XApp Mac上的卸载专家,强大的垃圾逻辑检测,垃圾扫描更全面,卸载更干净 使用简单&#…...
LaTex中的常用空格命令
【LaTex中的常用空格命令】 在 LaTeX 中,有几个常用的空格指令: ● \,:一个小空格,通常用于在数学公式中插入较小的间距。● \quad:一个等宽空格,相当于当前字体尺寸下的字符宽度。 ● \qquad:两…...
k8s 1.28.2 集群部署 Thanos 对接 MinIO 实现 Prometheus 数据长期存储
文章目录 [toc]什么是 ThanosThanos 的主要功能Thanos 的架构组件Thanos 部署架构SidecarReceive架构选择 开始部署部署架构创建 namespacenode-exporter 部署kube-state-metrics 部署Prometheus Thanos-Sidecar 部署固定节点创建 label生成 secretMinIO 配置etcd 证书 启动 P…...
域渗透AD渗透攻击利用 python脚本攻击之IPC连接 以及 python生成exe可执行程序讲解方式方法
Python脚本批量检测ipc连接 import os, timeips [192.168.1.121,192.168.1.8 ] users {administrator,hack,hack1,test, } passs {123qq.com,456qq.com,Admin12345 } for ip in ips:for user in users:for mima in passs:exec1 "net use \\" "\\" i…...
行为设计模式 -命令模式- JAVA
命令模式 一.简介二. 案例2.1 接收者(Receiver)2.2 命令接口实现对象(ConcreteCommand)2.3 调用者( invoker)2.4 获取Receiver对象2. 5 装配者客户端测试 三. 结论3.1 要点3.2 示例 前言 本设计模式专栏写了…...
使用redis实现发布订阅功能及问题
如何使用redis实现发布订阅及遇到的问题 使用背景: 服务A通过接口操作服务B,实现相应逻辑。生产环境上,服务A有两个pod,服务B有3个pod 通过接口调用时,请求只能打到服务B的一个pod上,而我们想要的是服务B的…...
Debug日程工作经验总结日程常用
数据库 db连接命令 kubectl exec -it -n de dbs-53-cdf57d8dd-l4l29 sh su - postgres psql psql -h 10.115.19.118 -p 12080 -U postgres -d clouddb SET search_path TO “h.com”; select * from ems_ice limit 1; 也可以不切换schema,直接sql查询 select * f…...
Apache Paimon主键表的一些最佳实践
今天我们说说Paimon主键表的一些使用上的注意事项。 一、主键表 主键表是Paimon的一种表类型。用户可以插入、更新或删除表中的记录。 说的直白点就是,允许你设置唯一主键,然后覆盖更新。 Bucket选择 无论分区表还是未分区表,Bucket都是最小的…...
React面试常见题目(基础-进阶)
React面试常见题目及详细回答讲解 基础题目(20个) 什么是React? 回答:React是一个用于构建用户界面的JavaScript库,它允许你将UI拆分成可复用的组件。React起源于Facebook的内部项目,用于构建高性能的Web应…...
AI赋能:开启你的副业创业之路
随着人工智能(AI)技术的迅猛发展,越来越多的人开始探索与之相关的副业机会。AI不仅深刻改变了我们的工作和生活方式,还为愿意学习和运用这项技术的人们打开了丰富的创业和增收之门。今天,我们就来盘点几条与AI相关的副…...
前端文件上传组件流程的封装
1. 前端文件上传流程 选择文件: 用户点击上传按钮,选择要上传的文件。使用 <input type"file"> 或 FileReader API 读取文件。 文件校验: 校验文件的大小、格式等信息,提前过滤掉不符合要求的文件,避免…...
图像篡改研究
使用生成对抗网络 (GAN) 来篡改已有的图片涉及生成和修改图像的技术。以下是如何使用GAN对现有图像进行篡改的详细步骤: 1. 选择合适的GAN模型 不同类型的GAN模型适用于不同的图像处理任务。以下是几个常见的GAN模型及其应用: CycleGAN:用…...
wlan的8种组网方式的区别
1)方式一:直连模式 二层组网(直接转发/ 集中转发) (2)方式二:直连模式 三层组网(集中转发) (3)方式三:旁挂模式 二层组网(…...
取消element-ui中账号和密码登录功能浏览器默认的填充色,element-ui登录账号密码输入框禁用浏览器默认填充色问题
标题 问题展示 修改后 <div class="loginForm"><el-formref="formB":model="formDataB":rules="rulesB"class="login-form"label-position="left"><el-form-item prop="userNo" clas…...
Postman:高效的API测试工具
在现代软件开发中,前后端分离的架构越来越普遍。前端开发者与后端开发者之间的协作需要一种高效的方式来测试和验证API接口。在这个背景下,Postman作为一款强大的API测试工具,受到了广泛的关注和使用。 今天将介绍什么是Postman、为什么要使用…...
设计模式-观察者模式(代码实现、源码级别应用、使用场景)
提示:观察者模式的代码实现、观察者模式的使用场景、观察者模式源码级别的应用、观察者模式的优点、 文章目录 前言一、定义二、类图三、代码实现四、应用场景五、源码级别的应用总结 前言 随着时间的推移,我现在越来越感觉自己的代码不够优雅了&#x…...
9种 Vuejs 常用事件修饰符与使用指南
前言 事件修饰符是 Vue.js 中一种特殊的语法标记,通过在事件名称后加上 . 和修饰符名称,可以轻松地修改事件的默认行为。这些修饰符不仅能够提升代码的清晰度,还能够避免一些常见的编程陷阱。Vue.js 提供了一系列事件修饰符,帮助…...
第十四题刮开有奖
这道题还是将我们下载好的附件先查壳 发现无壳且为32位 所以我们用32位的IDA打开 打开后ShftF12发现一串可疑的字符串 我们跟进看看 发现了这个函数 看这里有string数组 首先给了一串七v7 v8v9的数据 下面还有一个函数 我们再跟进一下 发现这大概是前面v7那堆数据的加密方式 我…...
vue3+vite使用dataV后项目运行报错、页面空白问题
Vue 大屏数据展示组件库官网:http://datav.jiaminghi.com/guide/ 我的版本是:“jiaminghi/data-view”: “^2.10.0” 一、dataV引入,看官网也可 // 安装 ( 我的安装版本 "jiaminghi/data-view": "^2.10.0" …...
PDF 【人工智能白皮书 】【大模型安全实践白皮书】【大模型白皮书】【大模型/深度学习/人工智能原理/心智学习】
【2024 中国人工智能发展白皮书 】【2023 中国人工智能白皮书】【大模型/深度学习/人工智能原理/心智学习】 前言下面所有涉及到的白皮书文件的总下载链接(网盘): 2024 人工智能发展白皮书 深圳市易行网数字科技有限公司2024 大模型训练数据白…...
【vue】13.深入理解递归组件
在Vue.js的开发实践中,组件是构建界面的核心概念。而递归组件则是一种特殊的组件,它能够自己调用自己,从而创造出无限嵌套的界面结构。本文将带你了解递归组件的应用,以及如何在Vue中实现和使用它。 一.什么是递归组件?…...
【OFDM】OFDM Radar Algorithms in Mobile Communication Networks
[1] Maximum Likelihood Speed and Distance Estimation for OFDM Radar 1-CRB估计 1-初步形式 首先单频信号频率估计的CRB,也就是原文Eq.(3.53) v a r [ ω ^ ] ≥ 6 σ N 2 ( N 2 − 1 ) N \mathrm{var}[\hat{\omega}]\geq\frac{6\sigma_N^2}{(N^2-1)N} var[ω^]≥(N2−…...
如何检测java中的内存泄露及溢出,并预防?
引言 在Java开发中,内存管理是一个至关重要的方面。尽管Java有垃圾回收机制,但开发者仍然需要关注内存泄漏和溢出的问题。 内存泄漏会导致应用程序的性能下降,甚至崩溃,而内存溢出则会导致Java虚拟机(JVM)抛出OutOfMemoryError异常。 本文将探讨如何检测Java项目中的内…...
邯郸市永年区做网站的公司/潍坊seo网络推广
蓝色关注,回复“6”获取技术、产品人必读10本书见字如面,我是军哥。马上金三银四,这文章你需要,拿到好 offer 记得来感谢军哥哈!你是不是,谈薪的时候总谈不过 HR?你是不是,明明不想接…...
外贸先做网站再开公司/如何注册域名及网站
a [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]目前通过a可以获取如下格式:[0,1,2,3][0,1,2,3][0,1,2,3][0,1,2,3]现在想要通过a得到如下格式:[0,0,0,0][1,1,1,1][2,2,2,2][3,3,3,3]如何实现上面的要求?转载于:https://blog.5…...
上海特种作业操作证查询/seo运营是什么意思
一、Widget设计步骤 需要修改三个XML,一个class:1.第一个xml是布局XML文件(如:main.xml),是这个widget的。一般来说如果用这个部件显示时间,那就只在这个布局XML中声明一个textview就OK了。2.第二个xml是widget_pro…...
祥云建站平台/营销型企业网站有哪些平台
Consignment 寄存一般是指卖方把货物存放在买方所属仓库,消耗后结帐。库存水平控制和货物的物理管理都由买方负责。这是目前很多大卖场通行的做法。典型的VMI一般也 是指卖方把货物存放在买方附近的仓库(可以是买方、卖方或者第三方仓库)&…...
怎么做网站教程html文本文档/在线刷seo
基于9款CSS3鼠标悬停相册预览特效里面包含九款不同方式的相册展开特效代码。效果图如下: 在线预览 源码下载 实现的代码。 html代码: <div class"albums"><div class"albums-inner"><div class"albums-tab&q…...
h5 和手机网站/磁力狗在线引擎
接着昨日的旅程,我们应该开始处理具体的子路径了:【fs/namei.c】sys_open->do_sys_open->do_filp_open->path_openat->link_path_walk点击(此处)折叠或打开 ... err walk_component(nd, &next, LOOKUP_FOLLOW); if (err 0) …...