当前位置: 首页 > news >正文

Spring事件机制

文章目录

  • 一、Spring事件
  • 二、实现Spring事件
    • 1、自定义事件
    • 2、事件监听器
      • 2.1 实现ApplicationListener接口
      • 2.2 @EventListener
      • 2.3 @TransactionalEventListener
    • 3、事件发布
    • 4、异步使用
  • 三、EventBus
    • 1、事件模式
    • 2、EventBus三要素
    • 3、同步事件
      • 3.1 定义事件类
      • 3.2 定义事件监听
      • 3.3 测试
    • 4、异步事件
      • 4.1 定义事件
      • 4.2 定义事件监听
      • 4.3 测试
  • 四、EventBus和Spring Event区别

一、Spring事件

Spring Event(Application Event)其实就是一个观察者设计模式,一个 Bean 处理完成任务后希望通知其它 Bean 或者说一个 Bean 想观察监听另一个Bean 的行为。

Spring的事件(Application Event)为Bean和Bean之间的消息同步提供了支持。当一个Bean处理完成一个任务之后,希望另外一个Bean知道并能做相应的处理,这时我们就需要让另外一个Bean监听当前Bean所发生的事件


Spring的事件需要遵循如下流程:

  1. 自定义事件,继承ApplicationEvent
  2. 定义事件监听器
  3. 使用容器发布事件

二、实现Spring事件

1、自定义事件

自定义一个事件类,继承ApplicationEvent。该事件可以被ApplicationContext通过publishEvent方法进行发送

public class DemoEvent extends ApplicationEvent {private String msg;public DemoEvent(Object source, String msg) {super(source);this.msg = msg;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

2、事件监听器

监听器有三种实现方式

  • 实现ApplicationListener接口
  • 使用@EventListener注解
  • 使用@TransactionalEventListener注解

2.1 实现ApplicationListener接口

新建一个类实现 ApplicationListener 接口,并且重写 onApplicationEvent 方法,然后交给Spring管理

@Component
public class DemoListener implements ApplicationListener<DemoEvent> {//实现ApplicationListener接口,并指定监听的事件类型 @Overridepublic void onApplicationEvent(DemoEvent event) { //使用onApplicationEvent方法对消息进行接受处理 String msg = event.getMsg();System.out.println("DemoListener获取到了监听消息:" + msg);}
}

2.2 @EventListener

将处理事件的方法使用 @EventListener 注解标记,此时 Spring将创建一个ApplicationListener的bean对象,使用给定的方法处理事件,参数可以指定处理的事件类型,以及处理条件。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {@AliasFor("classes") Class<?>[] value() default {};@AliasFor("value") Class<?>[] classes() default {};String condition() default "";
}
@Component
public class DemoListener {@EventListener(DemoEvent.class)public void sendMsg(DemoEvent event) {String msg = event.getMsg();System.out.println("DemoListener获取到了监听消息:" + msg);}
}

2.3 @TransactionalEventListener

@EventListener@TransactionalEventListener 都是 Spring提供的注解,用于处理事件。主要区别在于处理事件的时间和事务的关联性。

  • @EventListener:可以应用于任何方法,使得该方法成为一个事件监听器。当一个事件被发布时,所有标记为 @EventListener 的方法都会被调用,无论当前是否存在一个活动的事务。 使用@EventListener 注解的方法可能在事务提交之前或之后被调用。
  • @TransactionalEventListener:该注解允许更精细地控制事件监听器在事务处理过程中的执行时机。@TransactionalEventListener 默认在当前事务提交后才处理事件(TransactionPhase.AFTER_COMMIT),这可以确保事件处理器只在事务成功提交后才被调用。也可以通过 phase 属性来改变事件处理的时机,例如在事务开始前、事务提交前、事务提交后或者事务回滚
@Component
public class DemoListener {@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, value = DemoEvent.class)public void messageListener(DemoEvent event) {String msg = event.getMsg();System.out.println("DemoListener获取到了监听消息:" + msg);}
}

3、事件发布

通过spring上下文对象ApplicationContextpublishEvent方法即可发布事件

@Component
public class DemoPublisher {@Autowiredprivate ApplicationContext applicationContext; //注入ApplicationContext用来发布事件 public void publish(String msg) {applicationContext.publishEvent(new DemoEvent(this, msg));  // 使用ApplicationContext对象的publishEvent发布事件}
}

4、异步使用

如果要开启异步,需要在启动类增加 @EnableAsync 注解,Listener 类需要开启异步的方法增加 @Async 注解

@Async的原理是通过 Spring AOP 动态代理 的方式来实现的。Spring 容器启动初始化bean时,判断类中是否使用了@Async注解,如果使用了则为其创建切入点和切入点处理器,根据切入点创建代理,在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现了异步执行。



三、EventBus

  1. EventBus是一个轻量级的发布/订阅模式的应用模式,相比于各种 MQ 中间件更加简洁、轻量,它可以在单体非分布式的小型应用模块内部使用。
  2. 我们也可以把它和 MQ 中间件结合起来使用,使用 EventBus 作为当前应用程序接收 MQ 消息的统一入口,然后应用内部基于 EventBus 进行分发订阅,以达到高内聚低耦合的目的(当应用内部需要消费多种不同 MQ 中间件消息时,不需要在当前应用的好多不同代码位置都编写 MQ 消费代码)

1、事件模式

EventBus 默认为同步调用,同一个 EventBus 中注册的多个订阅处理,再事件下发后是被总线串行逐个调用的,如果其中一个方法占用事件较长,则同一个 EventBus 中的其他事件处于等待状态,且发送消息事件的代码调用处也是同步调用等待的状态。同一个 EventBus 对象,不仅仅在同一个 post 调用中串行执行,在多次并发 post 调用时,多个 post 调用之间也是串行等待执行的关系

在这里插入图片描述

  • 同步事件模式:同步事件模式下,事件的触发和事件的处理在同一个线程中同步处理
  • 异步事件模式:异步事件模式下,事件的触发和事件的处理在不同的线程中,事件的处理在一个线程池中

2、EventBus三要素

  • Event 事件,它可以是任意类型
  • Subscriber 事件订阅者,需要加上注解@Subscribe(),并且指定线程模型,默认是POSTING
  • Publisher 事件的发布者。我们可以在任意线程里发布事件,调用post(Object)方法即可

3、同步事件

3.1 定义事件类

public class MessageEvent {private String message;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "MessageEvent{" + "message='" + message + '\'' + '}';}
}

3.2 定义事件监听

import com.google.common.eventbus.Subscribe;public class MessageListener {@Subscribepublic void listen(MessageEvent event) {System.out.println(Thread.currentThread().getName() + " : " + event);}
}

3.3 测试

import com.google.common.eventbus.EventBus;public class SyncEventTest {public static void main(String[] args) {EventBus eventBus = new EventBus();eventBus.register(new MessageListener());MessageEvent messageEvent = new MessageEvent();messageEvent.setMessage("你好!");for (int i = 0; i < 100; i++) {eventBus.post(messageEvent);}}
}

测试结果
从结果可以看出,事件的触发和事件的处理都是在同一个线程中。

main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'} 
main : MessageEvent{message='你好!'}......


4、异步事件

4.1 定义事件

public class MessageEvent {private String message;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "MessageEvent{" + "message='" + message + '\'' + '}';}
}

4.2 定义事件监听

import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;public class MessageListener {/*** 注解@AllowConcurrentEvents是用来标识当前订阅者是线程安全的 * Guava会对listener对象,遍历其带有@Subscribe注解的所有方法,然后对针对每一个listener对象和method方法,标识唯一一个订阅者* 找到唯一识别的观察者后,会对该观察者进行包装wrap,包装成一个EventSubscriber对象,* 对于没有@AllowConcurrentEvents注解的方法,会被包装成SynchronizedEventSubscriber,即同步订阅者对象。 * 同步订阅者对象在处理事件时是使用了synchronized,强同步锁! * 总结: * 如果当前观察者(method)是线程安全的thread-safe,建议增加注解@AllowConcurrentEvents,以减少同步开销。 * 对于使用的是非异步(AsyncEventBus),也建议增加@AllowConcurrentEvents,因为不需要进行同步。*/@AllowConcurrentEvents@Subscribepublic void listen(MessageEvent event) {System.out.println(Thread.currentThread().getName() + " : " + event);}
}

4.3 测试

import com.google.common.eventbus.AsyncEventBus;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class AsyncEventTest {private static final int CORE_SIZE = Runtime.getRuntime().availableProcessors();private static final int ALIVE_TIME = 60;private static final int CACHE_SIZE = 1000;public static void main(String[] args) {AsyncEventBus eventBus = new AsyncEventBus(new ThreadPoolExecutor(CORE_SIZE, CACHE_SIZE << 1, ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue(CACHE_SIZE)));eventBus.register(new MessageListener());MessageEvent messageEvent = new MessageEvent();messageEvent.setMessage("你好!");for (int i = 0; i < 100; i++) {eventBus.post(messageEvent);}}
}

测试结果
从结果可以看出,异步事件模式下,事件的触发和处理是在不同的线程中的,事件的处理是在单独的线程池中进行处理

pool-1-thread-2 : MessageEvent{message='你好!'} 
pool-1-thread-3 : MessageEvent{message='你好!'} 
pool-1-thread-4 : MessageEvent{message='你好!'} 
pool-1-thread-5 : MessageEvent{message='你好!'} 
pool-1-thread-6 : MessageEvent{message='你好!'} 
pool-1-thread-7 : MessageEvent{message='你好!'}pool-1-thread-8 : MessageEvent{message='你好!'} 
pool-1-thread-9 : MessageEvent{message='你好!'} 
pool-1-thread-10 : MessageEvent{message='你好!'} 
.......


四、EventBus和Spring Event区别

项目事件发布者发布方法是否异步监听者注册方式
EventBus任意对象EventBusEventBus#post注解Subscribe的方法手动注册EventBus#register
Spring Event任意对象ApplicationEventPublisherApplicationEventPublisher#publishEvent支持同步异步注解EventListener的方法系统注册

相关文章:

Spring事件机制

文章目录 一、Spring事件二、实现Spring事件1、自定义事件2、事件监听器2.1 实现ApplicationListener接口2.2 EventListener2.3 TransactionalEventListener 3、事件发布4、异步使用 三、EventBus1、事件模式2、EventBus三要素3、同步事件3.1 定义事件类3.2 定义事件监听3.3 测…...

vue+canvas音频可视化

1.代码 <template><div class"subGuide"><canvas id"canvas"></canvas><br><audio id"audio" src"./audio.mp3" controls></audio></div> </template><script> export…...

俊昭stm32笔记

stm32——中断优先级 stm32——创建基础工程模板stm32——创建基础工程模板-CSDN博客 stm32——MCU启动方式stm32——MCU启动方式_stm32调试时程序启动方式-CSDN博客 stm32——串口stm32——串口_stm32 串口-CSDN博客 stm32——lcd液晶显示stm32——lcd液晶显示-CSDN博客...

W30-python03-pytest+selenium+allure访问百度网站实例

此篇文章为总结性&#xff0c;将pystest、selenium、allure结合起来 功能如下&#xff0c;web自动化&#xff0c;输入baidu网站&#xff0c;搜索“雷军”、打开网页中第一条内容 pytestsel.py如下&#xff1a; import time import re import allure import pytest from tools…...

如何在 Debian 8 上安装和使用 PostgreSQL 9.4

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 关系型数据库是满足多种需求的数据组织的基石。它们支持从在线购物到火箭发射等各种应用。PostgreSQL 是一种历史悠久但仍然活跃的…...

【微信小程序】微信小程序设置本地背景图片在真机无法显示的解决方案

微信小程序设置本地背景图片在真机无法显示的解决方案 在开发微信小程序时&#xff0c;很多开发者会遇到一个常见的问题&#xff1a;在调试环境中设置本地背景图片可以正常显示&#xff0c;但在真机上却无法显示。本文将详细探讨这一问题的原因&#xff0c;并提供三种解决方案…...

Arthas在线诊断案例实战整理

Arthas - Java 应用诊断利器 Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法…...

使用 XRDP 远程linux主机

一、简介 XRDP是一个开源的远程桌面协议&#xff08;Remote Desktop Protocol,RDP&#xff09;服务器&#xff0c;采用的是标准的RDP。 官网地址&#xff1a;https://www.xrdp.org/ github地址&#xff1a; https://github.com/neutrinolabs/xrdp/releases XRDP也是C/S架构&…...

学习小型gpt源码(自用)

数据集构建_哔哩哔哩_bilibili &#xff08;b站上有一系列课&#xff0c;从数据处理到模型构建和训练使用&#xff09; 什么是batch&#xff1f; 为什么一个batch内的句子要一样长&#xff1f; 不同batch的长度可以不一样&#xff0c;但是同一个batch内长度一样&#xff01;…...

@Transactional使用的注意事项

在项目中涉及到CRUD操作时&#xff0c;一般都会在方法上添加该注解&#xff0c;以为加上Transactional&#xff0c;Spring就可以自动帮我们进行事务的开启、提交 有一个很多人都会犯的误区&#xff1a; 将Spring事务与Transactional划上了等号&#xff0c;只要有数据库相关操作…...

快手可灵视频生成大模型全方位测评

快手视频生成大模型“可灵”&#xff08;Kling&#xff09;&#xff0c;是全球首个真正用户可用的视频生成大模型&#xff0c;自面世以来&#xff0c;凭借其无与伦比的视频生成效果&#xff0c;在全球范围内赢得了用户的热烈追捧与高度评价。截至目前&#xff0c;申请体验其内测…...

【JavaScript】`Map` 数据结构

文章目录 一、Map 的基本概念二、常见操作三、与对象的对比四、实际应用场景 在现代 JavaScript 中&#xff0c;Map 是一种非常重要且强大的数据结构。与传统的对象&#xff08;Object&#xff09;不同&#xff0c;Map 允许您使用各种类型的值作为键&#xff0c;不限于字符串或…...

Ubuntu22.04使用NVM安装多版本Node.js和版本切换

Fabric官方目前支持Node.js开发区块链应用&#xff0c;建议使用Node长期支持版本&#xff08;LTS&#xff09;。 建议使用NVM安装Node.js&#xff0c;NVM可以帮助我们方便的在Node的不同版本之间进行切换&#xff0c;这样我们就可以同时工作在不同的项目上。 下面是安装的脚本…...

基于C51和OLED12864实现Goole小恐龙

在数字娱乐领域&#xff0c;Google小恐龙&#xff08;T-Rex Runner&#xff09;以其简单而上瘾的游戏机制赢得了广泛的关注和喜爱。这款内置于Chrome浏览器的离线小游戏&#xff0c;不仅为用户带来了乐趣&#xff0c;也激发了开发者们对其进行各种创新和扩展的灵感。本文将介绍…...

【Docker】CentOS7环境下的安装

环境展示 安装 配置仓库 sudo yum install -y yum-utils # docker官方key文件下载 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 建议使用阿里云key文件下载 sudo yum-config-manager --add-repo https://mirrors.aliyun.…...

浏览器的最大并发数(http1.1)

HTTP/1.1&#xff1a;每个资源请求通常需要单独的TCP连接&#xff0c;尽管支持Keep-Alive机制&#xff0c;允许在同一个TCP连接上连续发送多个请求。但通常浏览器限制并发TCP连接数&#xff08;例如&#xff0c;每个域名最多6个并发连接&#xff09;。 HTTP/2&#xff1a;引入…...

Android 开发中px、dpi 和 dp三个单位的介绍

Android 开发中px、dpi 和 dp三个单位的介绍 在 Android 开发中&#xff0c;px、dpi 和 dp 是用来描述屏幕尺寸和密度的单位&#xff0c;它们在设计和开发中有着不同的作用和用途。 1. px&#xff08;像素&#xff09; 定义&#xff1a; px 表示屏幕上的一个像素点&#xff0c…...

zookeeper开启SASL权限认证

目录 一、SASL介绍 二、使用 SASL 进行身份验证 2.1 服务器到服务器的身份验证 2.2 客户端到服务器身份验证 三、验证功能 一、SASL介绍 默认情况下&#xff0c;ZooKeeper 不使用任何形式的身份验证并允许匿名连接。但是&#xff0c;它支持 Java 身份验证与授权服务(JAAS)…...

mysql一个小问题引发的思考-mysql类型转换-查询缓存 及 MYSQL查询缓存以及自动选择不使用查询缓存的情况

一、mysql一个小问题引发的思考-mysql类型转换-查询缓存 最近在做的一个项目中有一个SQL语句发现点问题&#xff0c;大概如下&#xff1a; select * from table where cid0 or find_in_set(1, cid); 数据表中的字段cid是字符串类型&#xff0c;原来的后端同学未提过此字段还能是…...

css更改图片颜色

css更改图片颜色&#xff0c;比较时候颜色单一的图片&#xff0c;比如logo之类的 css中的 filter 属性定义元素&#xff08;通常是 <img>&#xff09;的视觉效果&#xff08;如模糊和饱和度&#xff09; img{ -webkit-filter: invert(51%) sepia(94%) saturate(6433%) h…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...