Bug:引入Feign后触发了2次、4次ContextRefreshedEvent

Bug:引入Feign后发现监控onApplication中ContextRefreshedEvent事件触发了2次或者4次。
【原理】在Spring的文档注释中提示到: Event raised when an {@code ApplicationContext} gets initialized or refreshed.即当 ApplicationContext 进行初始化或者刷新时都会发送该事件。只有 finishRefresh() 方法里面会发送该事件,而该方法又只有 refresh() 方法调用,在 refresh() 方法中有这些注释: Load or refresh the persistent representation of the configuration, which ight be from Java-based configuration, an XML file, a properties file, a elational database schema, or some other format. As this is a startup method, it should destroy already created singletons if it fails, to avoid dangling resources. In other words, after invocation of this method, either all or no singletons at all should be instantiated.加载或者刷新配置文件,由于这是一个启动方法,如果失败,它应该销毁已经创建的单例,以避免悬空资源。换句话说,在调用这个方法前,要么全部实例化,要么根本不实例化。在初始化 openFeign 组件的最后,会调用 SubContext 的 refresh()操作,最终会触发 SubContext 发出ContextRefreshedEvent事件。
【 为啥出现多次触发ContextRefreshedEvent **】监控类
**的每个ApplicationContext都会加载两次。在 Spring 框架中,事件(Event)是会沿着 Context 层次向上传播。简单来说子 Context 发出初始化完成事件,进而引发父 Context 也发出相同事件,而父 Context 此时并没有真正初始化完成。

  • AnnotationConfigApplicationContext~A
  • AnnotationConfigServletWebServerApplicationContext~B
  • AnnotationConfigApplicationContext~C

监控类#1****每个ApplicationContext触发4次ContextRefreshEvent,下为父子关系:

  • A:bootstrap:@21947
    • B:fkgss-serviceproxy-1:@17804
      • C:FeignContext-dc-inboundaa-rooladc:@17795,触发2次
    • B**:fkgss-serviceproxy-1:@17804,触发2次**

监控类#2****只触发2次ContextRefreshEvent。

  • A:bootstrap:@21932
    • B:fkgss-serviceproxy-1:@17807
      • C:FeignContext-dc-inboundaa-rooladc:@17799,触发1次
    • B:fkgss-serviceproxy-1:@17807,触发1次

**【验证】新增一个Feign,会出现三次,验证是父子上下文导致。**监控类#2问题解决。

  • 空白项目接入监控类#1模块,验证是否因为外部SDK原因导致:触发1次。
  • 父子项目接入监控类#1:触发1次。
  • 接入OpenFeign的同时接入监控类#1:触发1次。

=========>XX项目问题<=========

可能因为kgs中引入多个监控类#1:检索后没有发现。打印Thread.dumpStack()后:

  • 在第一个堆栈中,onApplicationEvent 方法位于监控类#1类中,该方法是在 SimpleApplicationEventMulticaster 发布的事件中触发的。该事件通常在 finishRefresh 或 refresh 方法调用时触发,这些方法是在 Spring Boot 应用程序启动时执行的。
  • 在第二个堆栈中,onApplicationEvent 方法被通过 CGLIB 动态代理调用。CGLIB 代理的存在通常意味着 SeqNo 类被 Spring AOP 代理了,因此通过代理调用的 onApplicationEvent 可能触发了额外的逻辑,如切面增强、方法拦截等。

【触发条件】

  • Spring Event Multicasting:SimpleApplicationEventMulticaster 会为每个事件调用所有的事件监听器(如 SeqNo.onApplicationEvent)。因此,每当 ContextRefresh 事件发布时,监听器(如 SeqNo)会被触发一次。
  • CGLIB 代理的影响:由于 SeqNo 类可能是被 AOP 代理的,因此代理类的调用可能会导致同一方法被调用多次。具体来说,CGLIB 代理会生成一个新的类,并将对 SeqNo 方法的调用拦截和代理。这种代理逻辑可能导致事件监听方法被多次调用,导致 onApplicationEvent 被触发两次。

【验证监控类#1是否是代理类】

搜索日志发现:Bean '监控类#1' of type \*\*\*\*\*\* is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying);通常是由于 Spring 容器中的 Bean 在创建和初始化的过程中未能完全处理,导致该 Bean 无法被所有的 BeanPostProcessor 处理,尤其是无法进行自动代理。正是因为Feign提前触发ContextRefresh 。这也是为啥没办法通过BeanPostProcessor#postProcessAfterInitialization判断监控类#1是否被代理的原因。

【最后校验】

于是等【主上下文】加载完后,确定监控类#1被代理: 代理对象和原始 Bean 双重监听事件: 当事件发布时,Spring 容器会调用所有监听器的 onApplicationEvent 方法。由于 AOP 代理创建了一个新的 Bean 实例,该代理实例也会处理事件。因此,事件会被触发两次:

  • 一次是原始的 Bean 实例(即没有代理的实例)。
  • 另一次是代理实例。

**事件监听机制:**Spring 的事件监听机制是基于 Bean 的类型而不是 Bean 的实际实例。如果该 Bean 被 AOP 代理了,代理对象依然会注册为一个事件监听器,从而导致事件会被处理两次。

相关推荐
小豆庄1 天前
视频遮罩追踪退场后ID撞号的排查—多目标场景下的失效
opencv·算法·bug·视频·python3.11
sg_knight3 天前
Bug 定位实战:把报错丢给 Codex,它是怎么修的
chatgpt·llm·bug·openai·agent·ai编程·codex
8年车载测试4 天前
汽车五大域详解:动力、底盘、车身、座舱与智驾域
汽车·bug·jira·uds诊断·ecu刷写测试·canoe工具
ะัี潪ิื6 天前
Redisson Bug回顾
bug
猫咪很可爱8 天前
MySQL 5.7 备份恢复后函数与存储过程丢失:原因分析与正确备份姿势
数据库·mysql·bug·shell
李永奉8 天前
杰理可视化SDK开发-【BUG】AC7006F8芯片开启一拖二和双麦DNS通话算法后,大概率通话时蓝牙自动断连,RAM空间爆了问题处理
单片机·嵌入式硬件·mcu·物联网·电脑·bug
8年车载测试10 天前
车载测试中如何通过 Jira 平台提交 Bug:操作步骤与关键要点
bug·jira·uds诊断·hil测试·uds自动化测试·ecu刷写测试·canoe工具
硅基手札11 天前
SPI技术系列 13 · SPI 调试与排查
linux·运维·stm32·单片机·嵌入式硬件·bug
李永奉11 天前
杰理可视化SDK开发-【BUG】AC7006F8芯片开启一拖二和双麦通话语音算法后,设置触摸音在唤醒语音助手和通话时触摸音没作用问题
单片机·嵌入式硬件·mcu·物联网·电脑·bug
kyle~13 天前
机器人通信 --- FANUC TCP 并发请求-响应错位 Bug 复盘
tcp/ip·机器人·bug