可能导致堆栈溢出的spring ApplicationEvent 的一些细节

今天聊聊sping 的 ApplicationEvent,说实话,这个spring的事件驱动模式用的比较少,以前发现这个的时候也是在网上搜索一下怎么使用旧完事了。今天在代码中又发现了他的身影,就好好扒一扒这个功能。

首先是它的使用方法,在网上一搜一大堆,无非就是以下几个步骤:

1、定义一个事件类继承ApplicationEvent

2、监听该事件类型的消息(使用@EventListener模式)

3、发布事件

完事,点击启动,测试:

好了,使用方式就是上面3部曲,现在有个问题:从最后测试的图来看,左边红框中都是相同的线程名,说明消息消费与消息生产是在同一个线程中,也就是同步的。正常来说我们要是使用这个机制一般都是想使用异步的方式,不想要影响主流程,那怎么才能让消费者进行异步消费?

带着上面的问题,我们看下代码里是怎么执行的。

先在消费者那边打上断点(断点不要钱,使劲打):

从上面的堆栈信息可以看到消息推送到消息消费之间的完整的调用堆栈。

从上图的调用栈对应的代码中可以看出代码里面判断了事件消息是否是ApplicationEvent类型的,我们测试的例子继承了ApplicationEvent,所以走的是if的分支,但是下面还有个else分支,从代码逻辑来看,即使事件类没有继承ApplicationEvent也会给我们包装一下,按道理也是能够正常运行的。

测试一下:

从测试结果来看也是正常的,看来使用三部曲可以更精简点了,第一步继承ApplicationEvent也可以省略了。

再往上追下调用栈:

从上面这块代码看出是可以使用异步方式的,只不过当前executor为空导致的没有使用异步模式,那看看executor是从哪里设置的:

然后看下applicationEventMulticaster是在哪设置的:

从上面可以看出会从spring容器里面找有没有applicationEventMulticaster的bean,没有的话就走else分支new一个SimpleApplicationEventMulticaster出来,那这样的话,我们是不是可以自己配置一个SimpleApplicationEventMulticaster的bean,然后给它配置executor,这样不就实现了异步?

测试一下:

发现确实是使用到了异步线程。当然了,使用异步的方式还可以用@Async的模式,但是使用使用自定义SimpleApplicationEventMulticaster bean的方式逼格稍微高那么一点不是?而且使用这种模式还有一个好处,往下看:

我是不是可以在自己的SimpleApplicationEventMulticaster中设置一个errorHandler?然后异常后按照自己想要的方式进行额外的统一处理?这比在每个监听事件中写异常处理优雅多了吧?

然后我还发现一个有趣的地方:

监听事件是可以有返回值的,而且有返回值后还会再次推送消费。测试一下(增加了返回值):

从上面可以看出确实拿着结果再次推了。那我再建一个监听String类型的消费者看看效果:

看到没,根据返回结果的类型再次消费了。逼格是不是再次提升了一点?这个使用场景也应该有挺多的吧,比如注册功能,注册成功后需要赠送积分,赠送积分后还要发消息通知,那这是不是就可以使用这种方式了,推送积分事件进行异步处理赠送积分,赠送积分的方法再整一个Msg类型的返回值,然后再整一个监听Msg的事件。这样在主流程里面只需要推送一次积分事件就可以了(有点责任链的那味了)。这逼格,你就说高不高?

但是,盆友,这里需要提醒一下,使用返回值的时候需要慎重啊,看个例子:

将上面的那个方法调整了下,之前是void类型的,现在改成String类型的了。

死循环了!!!返回的类型又有对应的监听事件,可不就死循环了嘛。所以在使用返回值的监听事件的时候,需要慎重,多检查一下有没有死循环的风险!!!

总结一下:

1、异步消费方式可以使用@Async模式,也可以自定义名为SimpleApplicationEventMulticaster的bean,并且该bean中可以设置自定义的errorHandler进行统一的异常处理

2、@EventListener方法是可以有返回值的,如果有监听返回值类型的事件,spring还会进行监听该消息进行消费的,但是这个也有死循环的风险,需要小心使用。

数组越界(Array Index Out of Bounds)和堆栈溢出(Stack Overflow)是两种同的错误,但可能在某些情况下间接导致堆栈溢出。以下是具体分析: --- ### **1. 数组越界 vs. 堆栈溢出** - **数组越界**:访问数组时使用了非法索引(如 `arr[-1]` 或 `arr[100]` 当数组长度为 10)。 - 通常触发 **运行时错误**(如 C/C++ 的 `Segmentation Fault` 或 Java 的 `ArrayIndexOutOfBoundsException`)。 - 会直接导致堆栈溢出,但可能引发未定义行为(如内存损坏)。 - **堆栈溢出**:函数调用层次过深或局部变量占用过多栈空间,导致栈内存耗尽。 - 典型场景:无限递归(如 `void foo() { foo(); }`)。 - 与数组越界无直接关系,除非越界访问破坏了栈帧(如覆盖返回地址)。 --- ### **2. 数组越界可能间接导致堆栈溢出的情况** - **破坏栈帧结构**: 在 C/C++ 中,数组越界可能覆盖栈上的关键数据(如函数返回地址、局部变量),导致程序跳转到非法地址,进而引发无限递归或栈崩溃。 ```c #include <stdio.h> void vulnerable() { int arr[5]; arr[10] = 0xDEADBEEF; // 越界写入,可能覆盖栈上的返回地址 } int main() { vulnerable(); return 0; // 若返回地址被破坏,可能跳转到错误代码,导致堆栈混乱 } ``` - 这种情况下,越界访问本身直接导致堆栈溢出,但可能引发后续的栈破坏。 - **无限递归触发**: 若越界访问导致程序逻辑错误(如意外修改循环变量),可能间接引发无限递归: ```python def infinite_recursion(arr): try: print(arr[100]) # 越界访问可能抛出异常,但若未处理... except: pass infinite_recursion(arr) # 递归未终止条件,导致堆栈溢出 ``` --- ### **3. 关键结论** - **直接关系**:数组越界本身导致堆栈溢出。 - **间接关系**:越界访问可能通过破坏内存或逻辑错误,间接引发堆栈溢出(尤其在底层语言如 C/C++ 中)。 - **语言差异**: - 高级语言(如 Java/Python)会抛出异常而非直接崩溃。 - 低级语言(如 C/C++)可能导致未定义行为(如段错误或栈破坏)。 --- ### **示例代码:数组越界的安全处理** ```python # Python 中数组越界会抛出 IndexError,但堆栈溢出 arr = [1, 2, 3] try: print(arr[5]) # 抛出 IndexError except IndexError: print("数组越界!") # 安全的递归(有终止条件) def safe_recursion(n): if n <= 0: return safe_recursion(n - 1) # 正常终止 safe_recursion(1000) # 堆栈溢出(Python 默认递归深度约 1000) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值