SpringEvent优雅解耦时连续两个bug的解决方案

目录
  • 1,基本原理
  • 2,基本用法
  • 3,需要注意的点
  • 4,常见错误一:错误的监听一个并不会抛出的事件
  • 5,常见错误二:由于异常导致的事件传播丢失
  • 总结

1,基本原理

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

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

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。

以上就是SpringEvent优雅解耦时连续两个bug的解决方案的详细内容,更多关于SpringEvent解耦bug解决的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring超详细讲解IOC与解耦合

    目录 前言 一.所谓耦合 二.Spring 三.核心IOC理解 1.容器 2.控制反转 3.依赖注入 四.Bean的实例化 1.无参构造 2.工厂静态方法 3.工厂实例方法(常用) 五.Bean的依赖注入 1.set注入 2.有参构造 六.第一个Spring案例 前言 回想写过的图书管理系统.租房系统.电影院卖票系统都是基于原生的JavaSE.OOP,没有用到任何框架,在层与层的关系中一个类要想获得与其他类的联系主要的方式还是靠new,这就导致层与层之间.对象与对象之间的依赖性强“动一发而迁全身

  • Spring你不知道的一种解耦模式

    目录 前言 一个例子入门 应用Service Locator Pattern 剖析Service Locator Pattern 总结 前言 不知道大家在项目中有没有遇到过这样的场景,根据传入的类型,调用接口不同的实现类或者说服务,比如根据文件的类型使用 CSV解析器或者JSON解析器,在调用的客户端一般都是用if else去做判断,比如类型等于JSON,我就用JSON解析器,那如果新加一个类型的解析器,是不是调用的客户端还要修改呢?这显然太耦合了,本文就介绍一种方法,服务定位模式Service

  • 如何基于Spring使用工厂模式实现程序解耦

    这篇文章主要介绍了如何基于Spring使用工厂模式实现程序解耦,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1. 啥是耦合.解耦? 既然是程序解耦,那我们必须要先知道啥是耦合,耦合简单来说就是程序的依赖关系,而依赖关系则主要包括 1. 类之间的依赖 2. 方法间的依赖 比如下面这段代码: public class A{ public int i; } public class B{ public void put(A a){ System.o

  • Spring Boot小型项目如何使用异步任务管理器实现不同业务间的解耦

    目录 前言 一.异步任务管理器是什么? 二.实现步骤 1.自定义线程池 2. 新建异步任务管理器类 3. 新建异步工厂类 4. 调用 5. 实现效果 总结 前言 在有些业务场景中,系统对于响应时间有一定的要求,而一个方法里面同步执行的业务逻辑太多势必会影响响应速度,带来不好的用户体验.比如登录时记录登录用户的访问记录.注册时发送邮件.短信通知等等场景,不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行.

  • 详谈fastjson将对象格式化成json时的两个问题

     1. 关于继承 类的继承结构为 class Base{ private int id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } } class User extends Base{ private String name; public String getName() { return name; } public void setName(String name) { t

  • javascript设置连续两次点击按钮时间间隔的方法

    本文实例讲述了javascript设置连续两次点击按钮时间间隔的方法,分享给大家供大家参考.具体实现方法如下: 很多时候我们在实际应用中,可能并不希望按钮联系被不间断的点击,所以要限定一定的时间间隔才能够再次点击按钮,下面就通过代码实例介绍一下如何实现此功能,代码如下: 复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="a

  • vue计算属性时v-for处理数组时遇到的一个bug问题

    问题 bug: You may have an infinite update loop in a component render function 无限循环 1.需要处理的数组(在 ** ssq **里): bonus_code: ['01', '19', '25', '26', '27', '33', '10'] 2.计算属性 computed: ssqRed: function() { return this.ssq.bonus_code.splice(0, 6) }, ssqBlue:

  • mysql查询字段类型为json时的两种查询方式

    表结构如下: id varchar(32) info json 数据: id = 1 info = {"age": "18","disname":"小明"} -------------------------------------------- 现在我需要获取info中disanme的值,查询方法有: 1. select t.id,JSON_EXTRACT(t.info,'$.disname') as disname fro

  • Android Studio 3.5格式化布局代码时错位、错乱bug的解决

    更新到3.5版本后,格式化布局文件代码,会自动给排序元素,导致界面布局错乱 解决办法: 设置 > code style > XML 右上角 Set from然后选择Predefined Style...>Android即可 补充知识:Android Studio:Reformat Code格式化Xml布局代码后控件顺序错乱 Android Studio升级3.5之后,遇到个奇葩问题,在布局xml文件中格式化代码后,控件的顺序都变了,这不是我们想要的结果,网上搜了一下,确实是AS3.5的锅

  • PHP 中 DOMDocument保存xml时中文出现乱码问题的解决方案

    php中DOMDocument对于xml操作我们只要是英文是没有问题了,但如果是中文字体就会有乱码问题了,下面我们就此问题给各位介绍一些解决办法吧. PHP的DOM内部是utf8机制的,在loadHTML时,是通过检查字符中meta的charset来设置编码的,如果没有charset,就当iso8859进行处理了,而这种情况下进行saveXML时,输出来的却是utf8,所以就看到乱码了. 这么说是不是还不太理解,举个例子: $xml = new DOMDocument(); @$xml->loa

  • 解析Tomcat 6、7在EL表达式解析时存在的一个Bug

    今天在做数据分页显示的时候遇到了一个问题,经过测试,证实是Tomcat 6的一个bug,我所用的版本为:apache-tomcat-6.0.36,和7.0.30均能复现.下面详细描述一下这个bug: 该bug是在JSTL<c:forEach>标签中发现的,后来分析是EL表达式实现时产生的问题.jsp页面中有一个list需要遍历,这个list的类型为ArrayList<String>,我在其中放置的数据为(为方便我写成数组的形式):["1","...&q

  • Firefox返回时Iframe的显示Bug的解决方法

    <script type="text/javascript">//<![CDATA[ if(getCookie('firefoxIframe')){ document.write('<p id="addAd"><a href="cookie.html">点击这里删除这个iframe</a></p>'); document.write('<iframe height="

  • asp.net中url地址传送中文参数时的两种解决方案

    在Web.comfig中配置 是一样的: <globalization requestEncoding="gb2312" responseEncoding="gb2312"/> 页面Header部分也都有 <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> 真是奇怪, 只好用了笨办法: 写参数: 复制代码 代码如下

  • WordPres对前端页面调试时的两个PHP函数使用小技巧

    函数esc_js()(过滤 Html 内嵌 JS) 参数 $text (字符串)(必须)要过滤的字符串. 默认值:None 返回值 (字符串)返回过滤后的字符串. 例子 <input type="text" value="<?php echo esc_attr( $instance['input_text'] ); ?>" id="subbox" onfocus="if ( this.value == '<?ph

随机推荐