SpringEvent事件发布订阅Demo

2024-06-01 18:36

本文主要是介绍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 方法。

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

这篇关于SpringEvent事件发布订阅Demo的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1021792

相关文章

售价599元起! 华为路由器X1/Pro发布 配置与区别一览

《售价599元起!华为路由器X1/Pro发布配置与区别一览》华为路由器X1/Pro发布,有朋友留言问华为路由X1和X1Pro怎么选择,关于这个问题,本期图文将对这二款路由器做了期参数对比,大家看... 华为路由 X1 系列已经正式发布并开启预售,将在 4 月 25 日 10:08 正式开售,两款产品分别为华

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Nginx实现前端灰度发布

《Nginx实现前端灰度发布》灰度发布是一种重要的策略,它允许我们在不影响所有用户的情况下,逐步推出新功能或更新,通过灰度发布,我们可以测试新版本的稳定性和性能,下面就来介绍一下前端灰度发布的使用,感... 目录前言一、基于权重的流量分配二、基于 Cookie 的分流三、基于请求头的分流四、基于请求参数的分

spring @EventListener 事件与监听的示例详解

《spring@EventListener事件与监听的示例详解》本文介绍了自定义Spring事件和监听器的方法,包括如何发布事件、监听事件以及如何处理异步事件,通过示例代码和日志,展示了事件的顺序... 目录1、自定义Application Event2、自定义监听3、测试4、源代码5、其他5.1 顺序执行

定价129元!支持双频 Wi-Fi 5的华为AX1路由器发布

《定价129元!支持双频Wi-Fi5的华为AX1路由器发布》华为上周推出了其最新的入门级Wi-Fi5路由器——华为路由AX1,建议零售价129元,这款路由器配置如何?详细请看下文介... 华为 Wi-Fi 5 路由 AX1 已正式开售,新品支持双频 1200 兆、配有四个千兆网口、提供可视化智能诊断功能,建