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

news2024/11/16 16:00:57

实现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> {

    @Override
    public 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> {

    @Override
    public 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 {

    @Autowired
    private 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
    @AllArgsConstructor
    public 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 {

    @Autowired
    private 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.coloradmin.cn/o/2090747.html

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

相关文章

开放大世界的全局寻路

开放大世界的寻路一直是很困扰我的一个点&#xff0c;地图大、还是动态可变的&#xff0c;所以寻路会有很多要求。就我们项目来讲&#xff0c;有这几个要求&#xff1a; 能满足极大范围的地图&#xff1b; 地图寻路数据能实时构建&#xff0c;且重建代价很小&#xff1b; 寻路的…

地质灾害监测预警系统的作用

在地球的广阔舞台上&#xff0c;自然灾害如同不可预测的演员&#xff0c;时常上演着惊心动魄的剧目。地震的震撼、滑坡的肆虐、泥石流的咆哮&#xff0c;这些地质灾害不仅给人类生命财产带来巨大威胁&#xff0c;也考验着社会的防灾减灾能力。为了应对这一挑战&#xff0c;地质…

【Linux】在 bash shell 环境下,当一命令正在执行时,按下 control-Z 会?

目录 题目分析答案 题目 分析 ctrl-c&#xff1a; 发送 SIGINT 信号给前台进程组中的所有进程。常用于终止正在运行的程序&#xff1b;ctrl-z&#xff1a; 发送 SIGTSTP信号给前台进程组中的所有进程&#xff0c;常用于挂起一个进程&#xff1b;ctrl-d&#xff1a; 不是发送信…

乐城堡 JoyCastle Unity岗位笔试题

1)实现 move(GameObjct gameObject, Vector3 begin, Vector3 end, float time, bool pingpong){ } 使 gameObject 在 time 秒内&#xff0c;从 begin 移动到 end&#xff0c;若 pingpong 为 true&#xff0c;则在结束时 使 gameObject 在 time 秒内从 end 移动到 begin&#xf…

机器学习中的增量学习(Incremental Learning,IL)策略是什么?

机器学习中的增量学习&#xff08;Incremental Learning&#xff0c;IL&#xff09;策略是什么&#xff1f; 在当今快速发展的数据驱动世界中&#xff0c;传统的静态机器学习模型逐渐显露出局限性。随着数据量的增长和分布的变化&#xff0c;模型需要不断更新&#xff0c;以保…

opc da 服务器数据 转IEC61850项目案例

目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 应用条件 2 4 查看OPC DA服务器的相关参数 2 5 配置网关采集opc da数据 4 6 用IEC61850协议转发数据 6 7 网关使用多个逻辑设备和逻辑节点的方法 9 8 在服务器上运行仰科OPC DA采集软件 10 9 案例总结 12 1 案例说明 在OPC DA服务…

使用 Vue3 Element Plus 实现el-table中的特定单元格编辑,下拉选择等

效果预览 完整代码(后面有解析) <template><div style="display: flex;align-items: center;justify-co

磁性齿轮箱市场报告:前三大厂商占有大约79.0%的市场份额

磁性齿轮箱是一种用于扭矩和速度转换的非接触式机构。它们无磨损、无摩擦、无疲劳。它们不需要润滑剂&#xff0c;并且可以针对其他机械特性&#xff08;如刚度或阻尼&#xff09;进行定制。 一、全球磁性齿轮箱行业现状与洞察 据 QYResearch 调研团队最新发布的“全球磁性齿轮…

成都高温限电:当电动汽车「无电可充」

8月末的成都&#xff0c;因为高温限电了。 近几日&#xff0c;成都市气象台连续发布了高温红色预警信号。据新华社报道&#xff0c;8月21日&#xff0c;四川电网用电负荷两次创下历史新高&#xff0c;最高达6797万千瓦&#xff0c;较去年最大用电负荷增长近13%&#xff0c;电力…

Golang | Leetcode Golang题解之第385题迷你语法分析器

题目&#xff1a; 题解&#xff1a; func deserialize(s string) *NestedInteger {index : 0var dfs func() *NestedIntegerdfs func() *NestedInteger {ni : &NestedInteger{}if s[index] [ {indexfor s[index] ! ] {ni.Add(*dfs())if s[index] , {index}}indexreturn…

HarmonyOS鸿蒙开发:在线短视频流畅切换最佳实践

简介 为了帮助开发者解决在应用中在线短视频快速切换时容易出现快速切换播放时延过长的问题&#xff0c;将提供对应场景的解决方案。 该解决方案使用&#xff1a; 视频播放框架AVPlayer和滑块视图容器Swiper进行短视频滑动轮播切换。绘制组件XComponent的Surface类型动态渲染…

挂载5T大容量外接硬盘到ubuntu

挂载5T大容量外接硬盘到ubuntu S1&#xff1a;查看硬盘 使用 $ sudo fdisk -l找到对应盘&#xff0c;例如下图所示 /dev/sdc S2: 创建分区 使用 $ sudo fdisk /dev/sdc对上硬盘进行创建分区&#xff1b;可以依次使用以下指令 m &#xff1a;查看命令&#xff1b; g &…

前端篇-html

day1: 超文本标记语言&#xff08;英语&#xff1a;HyperText Markup Language&#xff0c;简称&#xff1a;HTML&#xff09;是一种用于创建网页的标准标记语言。 作用&#xff1a;可以使用 HTML 来建立自己的 WEB 站点&#xff0c;HTML 运行在浏览器上&#xff0c;由浏览器…

基于贝叶斯优化CNN-LSTM网络的数据分类识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 卷积神经网络&#xff08;CNN&#xff09; 4.2 长短期记忆网络&#xff08;LSTM&#xff09; 4.3 BO-CNN-LSTM 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) B…

基于物联网的低成本便携式传感器节点用于火灾和空气污染的检测与报警

目录 摘要 引言 材料和方法 传感器节点 IoT 微控制器 颗粒物传感器 环境和气体传感器 MQTT代理 Node-Red监控平台 系统结构 数据存储 工作描述 实验结果 讨论 结论 致谢 参考文献 这篇论文的标题是《Low-cost IoT-based Portable Sensor Node for Fire and Air…

区块链媒体套餐发稿:世媒讯引领项目推广新风潮

在区块链技术迅猛发展的今天&#xff0c;越来越多的企业和项目涌现出来&#xff0c;希望通过区块链技术改变传统行业&#xff0c;并在全球范围内获得更多关注和支持。然而&#xff0c;在这个竞争激烈的市场中&#xff0c;如何快速有效地推广和传播项目变得尤为重要。选择合适的…

disk manager操作教程 如何使用Disk Manager组件 Mac如何打开ntfs格式文件

macOS系统有一个特别明显的弱点&#xff0c;即不能对NTFS格式磁盘写入数据。想要适合Mac系统使用来回转换磁盘格式又十分麻烦&#xff0c;这该怎么办呢&#xff1f;Tuxera ntfs for mac作为一款Mac完全读写软件&#xff0c;大家在安装该软件后&#xff0c;能充分使用它的磁盘管…

macos Homebrew brew 安装 下载 国内加速镜像配置 - 可彻底解决使用brew命令时github.com无法访问相关问题

macos中的brew的默认仓库是github.com &#xff0c; 由于种种原因gh的访问速度很慢或者干脆被和谐&#xff0c;所以设置一个国内的brew加速非常有必要。 masos brew国内加速镜像配置 设置方法&#xff1a; 将下面的代码放到 ~/.bash_profile 文件中&#xff08;没有就手动创建…

003.Python爬虫系列_HTTPHTTPS协议

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

uniapp小程序实现横屏手写签名

<template><view class"signBox column-me"><!-- 这个是自定义的title-可根据自己封装的title的作为调整 --><status-bar title"电子签名" :bgColor"null"></status-bar><view class"topHint">请…