您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

当我准备用SpringEvent优雅的解耦时,连续两个bug把我搞懵了

时间:2022-12-08 13:51:23  来源:今日头条  作者:程序员拾山

日常开发中,我们有时会使用SpringEvent对业务解耦,使我们的代码更加高内聚低耦合,不过如果对其运行原理不清楚,那么在使用的过程中,一不留神就会出现一些bug。

今天我们回顾一下SpringEvent使用的基本原理,需要优化的点,以及非常常见的两种错误。

1,基本原理

 

Spring的事件模式其实很简单,我们创建一个Event事件,当Event发生时,广播器对事件进行发布,然后对应的Listener进行处理即可。

Spring的事件一共有三个组件:

1,Event:用于定于我们的事件,比如ApplicationEvent或者通过继承ApplicationEvent定义我们自己的事件。

2,广播器Multicaster:当事件发生时,将事件广播出去。

3,监听器Listener:监听和处理广播器广播的事件。

2,基本用法

第一步,首先定义一个Event事件,

@Getter
@Setter
public class MessageEvent extends ApplicationEvent {

    private String content;

    public MessageEvent(String content) {
        super(new Object());
        this.content = content;
    }
}

第二步,定义一个Listener对事件进行监听,

@Component
public class MessageListener {
    
    @EventListener
    public void listen(MessageEvent messageEvent) {
        System.out.println("收到消息:" + messageEvent.getContent());
    }
    
}

最后在我们的业务逻辑需要的地方,就可以发布事件了。

@RestController
@RequestMapping(value = "/demo")
public class DemoController {

    @Resource
    private ApplicationContext applicationContext;

    @PostMapping(value = "/send")
    public ResponseEntity sendMessage() {
        //.....
        //处理一些业务逻辑之后,发送通知消息
        MessageEvent messageEvent = new MessageEvent("发布一条测试消息");
        this.applicationContext.publishEvent(messageEvent);
        return ResponseEntity.ok().build();
    }
}

3,需要注意的点

一,对于同一个Event,我们可以定义多个Listener,多个Listener之间可以通过@Order来指定顺序,order的Value值越小,执行的优先级就越高。

二,我们可以使用@EventListener轻松标记一个方法作为监听器,但是默认情况下,它是同步执行的,所以如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交。

有些情况下,我们希望事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交。

这时我们可以使用@
TransactionalEventListener来定义一个监听器。

@Component
public class MessageListener {

    //上层事务执行完毕之后再执行
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
    public void listen(MessageEvent messageEvent) {
        System.out.println(Thread.currentThread().getName());
        System.out.println("收到消息:" + messageEvent.getContent());
    }
}

三,默认情况下,@EventListener定义的方法是同步执行的,如果我们想通过异步的方式执行一个监听器的方法,可以在方法上加上@Async注解(记得在启动类上加上@EnableAsync开启异步执行配置)。

需要注意的是,使用@Async时,必须为其配置线程池,否则用的还是默认的线程。

如@Async(value = "taskExecutor"),此时Listener就会被分配到taskExecutor的线程池中执行。

使用@Async异步执行的同时,还会带来另外两个问题,需要大家注意:

1,如果Listener执行过程中抛出了异常,由于是异步执行,异常并不会被事件发布方捕获。

2,异步执行时,方法的返回值不能用来发布后续事件,如果需要处理结果去发布另一个事件,需要我们手动去发布。

4,常见错误一:错误的监听一个并不会抛出的事件

有时我们希望监听Spring的启动事件,做一些初始化操作。于是有的同学可能定义了这样一个Listener:

@Component
public class MessageListener {
    
    @EventListener
    public void listen2(ContextStartedEvent event) {
        System.out.println("Spring启动了," + event.toString());
    }
}

不过,虽然名字看起来似乎是一个上下文启动时的事件,但是Spring启动时并不会发布这个事件,我们启动项目看下控制台是否会打印日志:

 

可以看到,Spring项目启动后,并没有打印任何日志。

其实Spring项目启动后发布的真正Event是ContextRefreshedEvent,我们修改下代码再看一下结果:

 

这时,控制台打印出了我们想要的日志。

在创建监听事件时,一定要确保监听的Event是正确的,否则就会监听不到对应的事件。

5,常见错误二:由于异常导致的事件传播丢失

即使我们保证事件会被监听器真正的捕获,但是某些情况下,事件会因为异常导致传播丢失。

 

如上图所示,我们定义了两个Listener,原本期望按照order定义的顺序,将消息传播依次传播。然而因为一些原因,Listener1中的方法抛出了异常,导致Listener2无法接收到消息了。

这是因为:默认情况下处理器的执行是顺序执行的,在执行过程中,如果一个监听器执行抛出了异常,则后续监听器就得不到被执行的机会了。

我们可以通过
SimpleApplicationEventMulticaster中的multicastEvent方法看一下事件是如何传播的,

 

 

 

通过上图可以看出,如果在没有定义线程池的情况下,在invokeListener方法中会调用doInvokeListener方法去执行真正的逻辑,在doInvokeListener方法中,如果抛出了异常,会导致后的Listener失效。

针对异常这种情况又该如何处理我们的代码呢?

有三种方法:

1,使用try catch捕获异常,只要Listener方法不抛出异常,自然每个Listener都可以收到广播的消息。

2,使用@Async异步执行,通过上面的源码可以看到,如果定义的线程池,那么每一个Listener都会在一个线程中执行,每个线程之后是相互独立的,自然不会影响别人。

3,通过ErrorHandler处理掉异常,保证后面的Listener不受影响。

 

总结

本文主要讲解了SpringEvent基本的使用方法,和平常开发中可能会遇到的一些问题。总的来说,Spring为了让大家用的更轻松,考虑了各种可能发生的情况,但是如果大家不了解背后的实现原理,就可能发生一些本不该出现的bug。



Tags:SpringEvent   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
当我准备用SpringEvent优雅的解耦时,连续两个bug把我搞懵了
日常开发中,我们有时会使用SpringEvent对业务解耦,使我们的代码更加高内聚低耦合,不过如果对其运行原理不清楚,那么在使用的过程中,一不留神就会出现一些bug。今天我们回顾一下Sp...【详细内容】
2022-12-08  Search: SpringEvent  点击:(460)  评论:(0)  加入收藏
SpringEvent的应用
前言该文章是我学习使用SpirngEvent的过程,现在只写了SpringEvent的应用,后续会写一篇从源码关注SpringEvent的实现过程。SpringEvent的介绍SpringEvent在我认为是一个解决业...【详细内容】
2022-08-08  Search: SpringEvent  点击:(429)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(1)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(19)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(55)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(51)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(88)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
相关文章
站内最新
站内热门
站内头条