SpringEvent事件发布订阅Demo

文章目录

  • 实现事件
  • 定义事件监听器
    • 方式一
    • 方式二
  • 定义事件发布者
    • 方式一
    • 方式二
  • 项目测试
  • 原理

本文参考:

基础用法:Spring Event事件发布&消费Demo - HumorChen99 - 博客园 (cnblogs.com)

比较全面的用法:Spring Event 事件发布/监听机制 详解并使用-CSDN博客

@EventListener 源码简单探究:SpringBoot中@EventListener注解的使用-CSDN博客

广播器与监听器初始化详细源码:深入理解Spring事件机制(一):广播器与监听器的初始化 - Createsequence - 博客园 (cnblogs.com)

事件如何广播推送的:[深入理解Spring事件机制(二):事件的推送通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)

在看源码的过程中,看到大佬们通过 Spring Event 事件发布与订阅机制,都实现了比较优雅的代码,但是自己对于这方面的实践还是较少,因此这篇文章准备从 Demo 引入,了解相关的用法,后面再出文章进一步分析其中的原理。

实现Spring事件机制主要有4个类:

  1. ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。
  2. ApplicationListener:事件监听器,用于接收事件处理时间。
  3. ApplicationEventMulticaster:事件管理者,用于事件监听器的注册和事件的广播。
  4. ApplicationEventPublisher:事件发布者,委托ApplicationEventMulticaster完成事件发布。

实现事件

自定义事件,继承 ApplicationEvent

package com.jxz;import lombok.Getter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;/*** @Author jiangxuzhao* @Description 自定义事件,继承 ApplicationEvent* @Date 2024/5/31*/
@Getter
@ToString
public class JxzEvent extends ApplicationEvent {private String jxzTopic;private String jxzContent;public JxzEvent(String topic, String content) {super(topic);this.jxzTopic = topic;this.jxzContent = content;}
}

定义事件监听器

方式一

自定义事件监听器,实现 ApplicationListener 接口,监听 JxzEvent 事件,同时别忘了将 JxzEventListener 通过 @Component 注入给 Spring IOC 容器

package com.jxz;import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;/*** @Author jiangxuzhao* @Description 自定义事件监听器,实现 ApplicationListener 接口,监听 com.jxz.JxzEvent 事件* @Date 2024/5/31*/
@Slf4j
@Component
public class JxzEventListener implements ApplicationListener<JxzEvent> {@Overridepublic void onApplicationEvent(JxzEvent event) {log.info("topic is = {}", event.getJxzTopic());log.info("content is = {}", event.getJxzContent());}
}

方式二

通过 @EventListener 注解,实现监听事件的效果

package com.jxz;import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;/*** @Author jiangxuzhao* @Description* @Date 2024/6/1*/
@Component
@Slf4j
public class JxzEventListener2 {@EventListener(value = {JxzEvent.class}) // @EventListener 注解public void listener(JxzEvent event) {log.info("topic is = {}", event.getJxzTopic());log.info("content is = {}", event.getJxzContent());}
}

定义事件发布者

方式一

需要引入 ApplicationEventPublisher 事件发布者,然后构造 JxzEvent 事件发布事件

package com.jxz;import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** @Author jiangxuzhao* @Description Controller 类方便测试* @Date 2024/5/31*/
@RestController
public class JxzController {/*** 需要引入 ApplicationEventPublisher 事件发布者*/@Resourceprivate ApplicationEventPublisher applicationEventPublisher;@GetMapping(path = "test/")public String test(String topic, String content) {// 通过传入参数构造 JxzEvent 事件JxzEvent jxzEvent = new JxzEvent(topic, content);// 发布事件applicationEventPublisher.publishEvent(jxzEvent);return jxzEvent.toString();}
}

方式二

Spring 中顶级的容器管理类 ApplicationContext

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver

extends 继承于 ApplicationEventPublisher,因此也可以通过注入父类的接口引入这个发布者

@Resource
private ApplicationContext applicationContext;@GetMapping(path = "/test2")
public String test2(String topic, String content) {// 通过传入参数构造 JxzEvent 事件JxzEvent jxzEvent = new JxzEvent(topic, content);// 发布事件applicationContext.publishEvent(jxzEvent);return jxzEvent.toString();
}

其实这两种方式都是通过接口的方式,自动装配了 AnnotationConfigServletWebServerApplicationContext 这个实现类。

项目测试

最后再给程序加一个启动类就可以测试了

package com.jxz;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @Author jiangxuzhao* @Description* @Date 2024/5/31*/
@SpringBootApplication
public class SpringEventApplication {public static void main(String[] args) {SpringApplication.run(SpringEventApplication.class, args);}
}

启动这个类并加上 VM Option = -Dserver.port=8071,本地端口 8071 接收请求

http://localhost:8071/test?topic="jxztopic"&content="明天放假"

控制台输出如下,表明事件成功发出,监听者成功接收并打印内容

2024-05-31 23:59:23.930  INFO 45974 --- [nio-8071-exec-4] com.jxz.JxzEventListener                 : topic is = "jxztopic"
2024-05-31 23:59:23.932  INFO 45974 --- [nio-8071-exec-4] com.jxz.JxzEventListener                 : content is = "明天放假"

原理

原理这块还是值得细细看源码的,这里姑且做个引子,起个提纲挈领的作用,需要有些 Spring 源码的底子,小弟我也只能管中窥豹。

本质上就是使用到了观察者模式,之前也写过一篇文章讲过 设计模式-从回调函数入手理解观察者模式_回调函数与观察者模式-CSDN博客

监听器的逻辑:

SpringApplication#run -> AbstractApplicationContext#createApplicationContext() -> AnnotationConfigServletWebServerApplicationContext -> new AnnotatedBeanDefinitionReader -> AnnotationConfigUtils.registerAnnotationConfigProcessors -> new RootBeanDefinition(EventListenerMethodProcessor.class) -> AbstractApplicationContext#finishBeanFactoryInitialization -> beanFactory.preInstantiateSingletons() -> smartSingleton.afterSingletonsInstantiated() -> EventListenerMethodProcessor#afterSingletonsInstantiated -> EventListenerMethodProcessor#processBean -> context.addApplicationListener -> applicationEventMulticaster.addApplicationListener(listener) -> AbstractApplicationEventMulticaster#addApplicationListener -> this.defaultRetriever.applicationListeners.add(listener)

由于 EventListenerMethodProcessor 实现了 SmartInitializingSingleton 接口,因此在 beanFactory 结束初始化结束之后会被调用,最终调用到其afterSingletonsInstantiated 方法,在 processBean 方法中,会找出所有被@EventListener 注解的方法,将它们初始化成 ApplicationListener 对象,并最终放入 AbstractApplicationEventMulticaster#ListenerRetriever#applicationListeners 集合中,同时放入 AbstractApplicationContext#applicationListeners 集合中,这里就是完成了监听者的注册。

发布者逻辑:

ApplicationEventPublisher#publishEvent -> AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType) -> getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType) -> SimpleApplicationEventMulticaster#multicastEvent

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {invokeListener(listener, event);}}
}

其中 getApplicationListeners 方法就会从通过 retrieveApplicationListeners 从前面注册的 defaultRetriever.applicationListeners 里面拿到所有的监听者,同时配置了一个本地缓存 retrieverCache 来提速。最终调用 invokeListener -> doInvokeListener -> listener.onApplicationEvent(event) 就是成功调用了这些监听者的 onApplicationEvent 方法。

由此可见,观察者模式是通过同步的方式实现的事件通知,而非消息队列那样依赖消息中心实现的异步通知方式。

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

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

相关文章

yolov10模块

yolov10模块 1 C2f2 C2fCIB2.1 CIB2.2 RepVGGDW 3 PSA4 SCDown5 v10Detect 论文代码&#xff1a;https://github.com/THU-MIG/yolov10 论文链接&#xff1a;https://arxiv.org/abs/2405.14458 Conv是Conv2dBNSiLU PW是Pointwise Convolution(逐点卷积) DW是Depthwise Convolut…

SpringMvc的工作流程是怎样的

Spring MVC 的工作流程涉及多个组件&#xff0c;它们协同工作来处理HTTP请求并生成响应。下面是一步步说明 Spring MVC 如何处理一个 HTTP 请求&#xff1a; DispatcherServlet 接收请求&#xff1a; 用户发送 HTTP 请求到服务器&#xff0c;请求被前端控制器 DispatcherServle…

python API自动化(Requests库应用)

1.接口自动化的逻辑 接口测试自动化&#xff0c;简单来讲就是功能测试用例脚本化然后执行脚本&#xff0c;产生一份可视化测试报告。不管什么样的测试方式&#xff0c;都是为了验证功能与发现 BUG。那为什么要做接口测试自动化呢&#xff1f;一句话概括就是为了节省人力成本 …

【SQL学习进阶】从入门到高级应用【企业真题】

文章目录 第一题第二题第三题第四题第五题第六题第七题第八题第九题MySQL行转列使用case whengroup by完成 第十题 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f495;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01; &#x1f495;希望您在这…

疫情物资捐赠和分配系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;机构管理&#xff0c;用户管理&#xff0c;发放管理&#xff0c;物资管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;物资论坛&#xff0c;公告信息…

STM32作业设计

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

彻底卸载Windows Defender

概述 卸载Windows Defender的方法有很多&#xff0c;如修改注册表、组策略&#xff0c;执行脚本等等&#xff0c;这些方法操作过于繁琐和复杂&#xff0c;不适合小白&#xff0c;今天带来一款强大的卸载工具&#xff0c;只需要以管理员身份运行该软件即可&#xff0c;不用其他操…

禹晶、肖创柏、廖庆敏《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》Chapter 6插图

禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 Chapter 6插图

Stable Diffusion详细教程

目录 &#x1f40b;引言 &#x1f40b;Stable Diffusion基本概念 &#x1f988;潜在扩散模型 &#x1f988;图像生成原理 &#x1f40b;Stable Diffusion安装部署 &#x1f988;环境要求 &#x1f988;安装步骤 &#x1f40b;Stable Diffusion阶段 &#x1f988;准备阶…

git 下载失败

-- 错误0 加 sudo -- 错误 $ git clone https://github.com/espressif/arduino-esp32.git -b release/v2.x arduino Cloning into arduino... remote: Enumerating objects: 53886, done. remote: Counting objects: 100% (1194/1194), done. remote: Compressing objects: 1…

PHP 页面报错Warning</b>: Cannot modify header information - headers already sent by

先给出解决方案再解释&#xff0c;如果急着用就不用看解释了。 解决方案一&#xff1a;保存php文件编码为utf-8无BOM码&#xff0c;具体操作可以用notepad等编辑器完成&#xff0c;把 sesstion_start() 放在文档所有输出&#xff08;包括html标签和php的输出语句&#xff0c;具…

ch4网络层---计算机网络期末复习(持续更新中)

网络层概述 将分组从发送方主机传送到接收方主机 发送方将运输层数据段封装成分组 接收方将分组解封装后将数据段递交给运输层网络层协议存在于每台主机和路由器上 路由器检查所有经过它的IP分组的分组头 注意路由器只有3层(网络层、链路层、物理层) 网络层提供的服务 一…

Java筑基-集合[Set、Map、List、Stack、Queue]

这里写目录标题 一、Collection接口结构图二、Set集合1、常用方法 三、List集合1、List集合常用方法2、代码案例 四、Stack集合1、方法2、代码展示 五、Queue集合1、常用的方法2、代码展示 六、Map集合1、基本概念2、常用方法3、代码展示 一、Collection接口结构图 二、Set集合…

小熊家务帮day8-day9 客户管理模块2 (用户定位,地址簿,实名认证,银行卡信息上传等功能)

客户管理模块 0.用户定位功能0.1 需求0.2 接口分析0.3 接口开发Controller层开发Service层开发 1.我的地址簿功能1.1 需求1.2 数据库设计1.3 新增地址簿1.3.1 接口设计1.3.2 接口开发Controller层开发Service层开发测试功能 1.4 地址簿查询1.4.1 接口设计1.4.2 接口开发Control…

Caliburn.Micro框架学习笔记——多页面处理案例

在聊这个之前&#xff0c;我们先来看一个静态类 在 Caliburn.Micro 中&#xff0c;ViewLocator 是一个用于查找和关联视图与视图模型的静态类。默认情况下&#xff0c;它根据约定&#xff08;命名约定或其他规则&#xff09;自动找到与视图模型相对应的视图。然而&#xff0c;…

C语言 | Leetcode C语言题解之第126题单词接龙II

题目&#xff1a; 题解&#xff1a; char** list; int** back; int* backSize;// DFS uses backtrack information to construct results void dfs(char*** res, int* rSize, int** rCSizes, int* ans, int last, int retlevel) {int i ans[last];if (i 0) {res[*rSize] (c…

langchain构建一个 Agent 的过程

构建一个 Agent 的过程 概述 在使用大型语言模型&#xff08;LLM&#xff09;时&#xff0c;语言模型本身无法直接采取行动&#xff0c;只能输出文本。LangChain 的一个重要用例是创建代理&#xff08;Agents&#xff09;。代理是使用 LLM 作为推理引擎的系统&#xff0c;用于…

运用selenium爬取京东商品数据储存到MySQL数据库中

使用Selenium爬取京东商品数据并存储到MySQL数据库中的过程可以分为几个步骤&#xff1a; 1. 准备工作 安装所需库 确保你已经安装了Python环境以及以下库&#xff1a; selenium&#xff1a;用于自动化浏览器操作。pymysql 或 mysql-connector-python&#xff1a;用于连接M…

在MongoDB中,您可以通过以下步骤来创建账号密码,并限制其在特定数据库上的访问权限

在MongoDB中&#xff0c;您可以通过以下步骤来创建账号密码&#xff0c;并限制其在特定数据库上的访问权限&#xff1a; 连接到MongoDB数据库&#xff1a; 使用MongoDB的客户端&#xff08;如mongo shell或者MongoDB Compass&#xff09;连接到MongoDB服务器。 切换到admin数…

【TensorFlow深度学习】池化层的功能与类型详解

池化层的功能与类型详解 池化层的功能与类型&#xff1a;深度学习中的维度缩减与特征抽取艺术概述主要的池化类型结语 池化层的功能与类型&#xff1a;深度学习中的维度缩减与特征抽取艺术 在深度神经网络架构中&#xff0c;池化层&#xff08;Pooling Layer&#xff09;是一个…