业务解耦-Spring事件监听的三种实现方式

实现ApplicationListener

步骤如下:

1.写Event类,需要继承Spring的ApplicationEvent类

2.写监听类,需要实现Spring的ApplicationListener接口,加上@Component注解

3.监听类实现onApplicationEvent方法

4.通过ApplicationContext.publishEvent(Event)发布事件

Event的代码如下:

import lombok.Getter;
import lombok.Setter;
import org.springframework.context.ApplicationEvent;@Getter
@Setter
public class CustomEvent extends ApplicationEvent  {private String message;public CustomEvent(Object source) {super(source);}}

两个Listener的代码如下。这里定义了两个Listener,@Order定义了执行顺序,其中CustomEventListener2先执行,CustomEventListener1后执行

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Order(2)
public class CustomEventListener1 implements ApplicationListener<CustomEvent> {@Overridepublic void onApplicationEvent(CustomEvent event) {log.info ("CustomEventListener1 received: {}", JSON.toJSONString(event));}}
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Order(1)
public class CustomEventListener2  implements ApplicationListener<CustomEvent> {@Overridepublic void onApplicationEvent(CustomEvent event) {log.info ("CustomEventListener2 received: {}", JSON.toJSONString(event));}
}

发布事件的方法如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;@Service
public class SpringListenerService {@Autowiredprivate ApplicationContext applicationContext;public void customerListener() {CustomEvent event = new CustomEvent("CustomEventSource");event.setMessage("CustomEvent");applicationContext.publishEvent(event);}
}

启动项目调用SpringListenerService.customerListener方法后日志打印如下图,可以看到是按照Order定义的顺序执行的

需要注意的是:整个调用链都是同步执行的,如果某个Listener抛出了异常,那么后续的Listener也不会继续执行,而发布事件所在的方法也会受影响抛出异常。

如果某个Listener想要异步执行,可以在相应的onApplicationEvent方法上加上@Async注解(应用启动类上需要加上@EnableAsync注解来启用异步)

@EventListener

步骤如下:

1.写Event类,普通的POJO即可。

2.写监听类,加上@Component注解,即需要被Spring扫描到。

3.在监听类上写监听方法,需要加上@EventListener注解。方法的参数是Event类。

4.通过ApplicationContext.publishEvent(Event)发布事件,通过ApplicationEventPublisher发布事件也行

)。

Listener代码如下:定义了两个加@EventListener注解的方法,这两个方法都是监听的UserEvent,所以用@Order注解定义了顺序,数字越小优先级越高,越优先执行

import com.alibaba.fastjson2.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class UserEventListener {//使用注解@EventListener//定义执行顺序@Order(2)public void receiveEvent2(UserEvent userEvent) {log.info("receiveEvent Order 2: {}", JSON.toJSONString(userEvent));}@EventListener@Order(1)public void receiveEvent(UserEvent userEvent) {log.info("receiveEvent Order 1 end: {}", JSON.toJSONString(userEvent));}@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic static class UserEvent {private String name ;}
}

发布事件的方法如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;/*** @Description* @ClassName SpringListenerService* @Date 2024/8/29 23:01*/
@Service
public class SpringListenerService {@Autowiredprivate ApplicationContext applicationContext;public void eventListener() {UserEventListener.UserEvent eventListener = UserEventListener.UserEvent.builder().name("eventListener").build();applicationContext.publishEvent(eventListener);}
}

启动项目调用SpringListenerService.eventListener方法后日志打印如下图,可以看到是按照Order定义的顺序执行的

需要注意的是:整个调用链同样都是同步执行的,如果某个Listener抛出了异常,那么后续的Listener也不会继续执行,而发布事件所在的方法也会受影响抛出异常。

如果某个Listener想要异步执行,可以在相应的方法上加上@Async注解(应用启动类上需要加上@EnableAsync注解来启用异步)

@EventListener
//异步执行
@Async
@Order(1)
public void receiveEvent(UserEvent userEvent) {log.info("receiveEvent Order 1 end: {}", JSON.toJSONString(userEvent));
}

@TransactionalEventListener

以上介绍的两种实现方式,在处理某些场景的时候会有问题:如果Listener的代码异常不需要影响发布事件所在方法,就需要采用异步的方式。但是,如果发布事件所在的方法中存在事务,那么,Listener执行的时候,该事物可能还未提交,那么Listener中就会查不到相应的数据

这时候@TransactionalEventListener就能完美解决这个问题,它可以控制在事务的哪个阶段去执行监听。需要注意的是在Spring4.2+才有

先看下@TransactionalEventListener注解的内容:

/** Copyright 2002-2019 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.transaction.event;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AliasFor;/*** An {@link EventListener} that is invoked according to a {@link TransactionPhase}.** <p>If the event is not published within an active transaction, the event is discarded* unless the {@link #fallbackExecution} flag is explicitly set. If a transaction is* running, the event is processed according to its {@code TransactionPhase}.** <p>Adding {@link org.springframework.core.annotation.Order @Order} to your annotated* method allows you to prioritize that listener amongst other listeners running before* or after transaction completion.** @author Stephane Nicoll* @author Sam Brannen* @since 4.2*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {/*** Phase to bind the handling of an event to.* <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.* <p>If no transaction is in progress, the event is not processed at* all unless {@link #fallbackExecution} has been enabled explicitly.*/TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;/*** Whether the event should be processed if no transaction is running.*/boolean fallbackExecution() default false;/*** Alias for {@link #classes}.*/@AliasFor(annotation = EventListener.class, attribute = "classes")Class<?>[] value() default {};/*** The event classes that this listener handles.* <p>If this attribute is specified with a single value, the annotated* method may optionally accept a single parameter. However, if this* attribute is specified with multiple values, the annotated method* must <em>not</em> declare any parameters.*/@AliasFor(annotation = EventListener.class, attribute = "classes")Class<?>[] classes() default {};/*** Spring Expression Language (SpEL) attribute used for making the event* handling conditional.* <p>The default is {@code ""}, meaning the event is always handled.* @see EventListener#condition*/String condition() default "";}

如果英语好的可以看源码的代码注释。

首先该注解添加了@EventListener注解,可见它是@EventListener的加强版

下面对一些重要的属性做解释。

phase:

这个注解取值有:BEFORE_COMMIT(事务提交前)、AFTER_COMMIT(事务提交后)、AFTER_ROLLBACK(事务回滚后)、AFTER_COMPLETION(事务完成时,无论是事务成功提交还是事务回滚)。默认值是BEFORE_COMMIT。

所以刚才提到的那种场景,在事务提交后才触发事件监听,我们可以用phase的默认属性AFTER_COMMIT即可。用该属性值,方法里有异常也不会影响发布事件所在的代码。

需要注意的是:BEFORE_COMMIT是在事务提交前执行的,所以如果出现了异常,也会影响发布事件所在的代码。如果BEFORE_COMMIT所指定的方法是异步执行的,那么可能出现在监听处查不到数据的情况,因为事务还可能未提交。

fallbackExecution:

用于指定:如果没有事务,是否执行相应的事务事件监听器。这个属性用处还是比较大的,如果在发布事件的位置没有事务,就可以指定该属性值为true该属性默认值为false,如果你发布的事件,监听不到,请仔细检查发布事件位置是否存在事务!!!!但是需要注意:如果指定为true,且发布事件的位置没有事务,监听的异常是会影响发布事件所在方法的代码的(此时等同于@EventListener)!!!!如果同时指定了异步,就不会影响!!!

代码使用方式跟@EventListener一致,只不过将注解换成了@TransactionalEventListener,这里不在做@TransactionalEventListener的代码展示

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1522346.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

25届计算机毕业设计:如何用Java SpringBoot+Vue打造高效医疗器械管理系统?

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

手游推广痛点解析,Xinstall如何助力厂商破解难题?

随着手游市场的日益繁荣&#xff0c;手游推广方式也在不断革新。从传统的地推、广告投放到如今新兴的CPA&#xff08;按动作付费&#xff09;和CPS&#xff08;按销售订单付费&#xff09;模式&#xff0c;手游推广正逐步走向效果导向的时代。而在这个过程中&#xff0c;Xinsta…

Ubuntu上安装剪切板管理软件

1. 更新系统和软件 确保你的系统和软件是最新的&#xff0c;有时更新可以修复这类错误。 sudo apt update sudo apt upgrade 2. 重新安装 Diodon 尝试卸载并重新安装 Diodon。 sudo apt remove diodon sudo apt install diodon 3. 检查依赖项 确保系统中安装了所有必要…

TPM管理咨询公司一走,企业又恢复原样,为什么?

在探讨“TPM管理咨询公司离开后&#xff0c;企业为何常常恢复原样”这一深刻问题时&#xff0c;我们不得不深入剖析TPM理念的本质、实施过程中的挑战以及企业在持续变革中面临的普遍困境。TPM作为一种以最大化设备综合效率为目标的生产维护体系&#xff0c;其核心理念在于通过全…

【数据结构】反射,枚举你必须知道的相关知识

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解关于反射以及枚举&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/7D225 &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 目录 &#x1f…

linux固定ip

背景 VMware&#xff0c;centos7 查询 网关 linux指执行 ip addr 命令 拿到自动分配的ip : 192.168.150.102 [rootlocalhost ~]# cd /etc/sysconfig/network-scripts/ 执行: cd /etc/sysconfig/network-scripts/ 进入到network-scripts文件中 执行: vi ifcfg-ens33 编辑ifc…

猪八戒落地-第15届蓝桥省赛Scratch初级组真题第1题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第180讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

备战2024年全国大学生数学建模竞赛:湖羊养殖场空间利用率优化

目录 一、引言 二、问题分析 问题1&#xff1a;年化出栏量与羊栏缺口估算 问题2&#xff1a;最大化年化出栏量的生产计划 问题3&#xff1a;考虑不确定因素的生产计划 三、解题思路 1. 模型假设与变量设定 2. 问题1的建模与求解 3. 问题2的建模与优化 4. 问题3的建模与…

Linux驱动开发基础(sr04超声波模块)

所学来自百问网 目录 1. SR04 超声波简介 2. 硬件设计 3. 软件设计 4. 示例代码 4.1 驱动代码 4.1.1 轮询模式 4.1.2 中断模式 4.3 应用程序 4.4 Makefile 4.5 实验效果 1. SR04 超声波简介 超声波测距模块是利用超声波来测距。模块先发送超声波&#xff0c;然后接…

mate-indicators占用内存过高导致熔断

目录&#xff1a; 1、问题现象2、解决方法 1、问题现象 mate-indicators占用内存达30.9%&#xff08;内存溢出&#xff09;导致内存不足服务熔断。 2、解决方法 发现mate-indicators进程占用内存资源达到节点总内存40%&#xff0c;导致服务出现内存熔断 临时解决 systemct…

51单片机-矩阵键盘(基于LC602)

时间&#xff1a;2024.8.30 作者&#xff1a;Whappy 目的&#xff1a;手撕51&#xff08;第二遍&#xff09; 代码&#xff1a; main.c #include <REGX52.H> #include "LCD1602.h" #include "Delay.h" #include "MatrixKey.h"unsigned…

Spring及Springboot事件机制详解

程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用&#xff0c;简单才好维护。因此&#xff0c;不应该以评论艺术品的眼光来评价程序设计是否优秀&#xff0c;程序设计的艺术不在于有多复杂多深沉&#xff0c;…

bigcache源码解析

1. 设计目标 Bigcache 是用 Golang 实现的本地内存缓存的开源库&#xff0c;主打的就是可缓存数据量大&#xff0c;查询速度快。 在其官方的介绍文章《 Writing a very fast cache service with millions of entries in Go 》一文中&#xff0c;明确提出的 bigcache 的设计目标…

“20人+14天”,个人开发者如何通过 Google Play 谷歌封闭测试

个人开发者的应用测试要求 为了帮助开发者提供高品质的应用从而带给用户更优质的使用体验&#xff0c;Google为所有在2023年11月13日之后创建的个人开发者账号增加了一项要求&#xff1a; 至少有20名测试人员在过去至少14天内选择持续参与测试。 满足这项要求后即可申请正式版…

SqlServer: 安装或升级到SqlServer2022

一、下载安装包。 https://info.microsoft.com/ww-landing-sql-server-2022.html?lcidzh-CN 简单注册一下之后&#xff0c;就可以下载安装包了。 或者在我的资源中下载&#xff1a; https://download.csdn.net/download/yenange/89709660 系统要求&#xff1a; https://…

<数据集>遥感航拍飞机和船舶和识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;19973张 标注数量(xml文件个数)&#xff1a;19973 标注数量(txt文件个数)&#xff1a;19973 标注类别数&#xff1a;2 标注类别名称&#xff1a;[ship,plane] 序号类别名称图片数框数1ship17575416292plane239815…

微信小程序webgl 显示图片

// wxml <view class"container"><!-- 加载地图容器 --><canvas type"webgl" id"testMap" style"width: 100%; height: 100%;" disable-scroll bindtouchstart"touchStart" bindtouchmove"touchMove&qu…

二开PHP泛目录生成源码 可生成新闻页面和关键词页面——码山侠

PS 本资源提供给大家学习及参考研究借鉴美工之用&#xff0c;请勿用于商业和非法用途&#xff0c;无任何技术支持&#xff01; 下载i5i.net 泛目录可以用来提升网站收录和排名 合理运用目录可以达到快速出词和出权重的效果 程序小 基本的服务器都带的得动 打开i5i.net——…

HarmonyOS开发实战( Beta5版)不要使用函数/方法作为复用组件的入参规范实践

概述 在滑动场景下&#xff0c;常常会对同一类自定义组件的实例进行频繁的创建与销毁。此时可以考虑通过组件复用减少频繁创建与销毁的能耗。组件复用时&#xff0c;可能存在许多影响组件复用效率的操作&#xff0c;本篇文章将重点介绍如何通过组件复用四板斧提升复用性能。 组…

惠中科技光伏清洗剂:科技创新引领绿色清洁新风尚

惠中科技光伏清洗剂&#xff1a;科技创新引领绿色清洁新风尚 在光伏产业蓬勃发展的今天&#xff0c;光伏板的清洁问题日益凸显&#xff0c;成为影响发电效率的关键因素之一。面对传统清洗方法效率低、成本高、环境影响大等痛点&#xff0c;惠中科技以科技创新为驱动&#xff0…