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

技术派Spring事件监听机制及原理

Spring事件监听机制是Spring框架中的一种重要技术,允许组件之间进行松耦合通信。通过使用事件监听机制,应用程序的各个组件可以在其他组件不直接引用的情况下,相互发送和接受消息。

需求

在技术派中有这样一个需求,当发布文章或者文章下线时,会发布一个事件给SiteMap(站点地图,帮助搜索引擎有效的抓取和索引网站),SiteMap监听到该事件后会进行更新。

事件监听的本质是观察者模式的应用包括事件、事件监听器、事件发布器等主要组件。

事件:一个实现了ApplicationEvent类的对象,代表了应用程序中某个特定的事件。我们可以根据需要创建自定义事件,只要继承ApplicationEvent类并添相关的属性和方法就可以了。

事件监听器:实现了ApplicationListener<E>接口的对象,其中E表示事件监听器需要处理的事件类型。监听器可以通过onApplicationEvent(E event)方法处理接受到的事件,另外也可以使用@EventListener注解来简化事件监听器的实现,技术派正是采用的这种方式。

事件发布器:事件发布器负责将事件发布给所有关注该事件的监听器,在Spring中,ApplicationEventPublisher接口定义了事件发布的基本功能,而ApplicationEventPublisherAware接口允许组件获取到事件发布器的引用。Spring的核心容器ApplicationContext实现了ApplicationEventPublisher接口,因此在Spring应用中,通常直接使用ApplicationContext作为事件发布器,技术派采用该方式。

实例

第一步(事件)

创建自定义事件ArticleMsgEvent,继承ApplicationEvent。

@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = true)
public class ArticleMsgEvent<T> extends ApplicationEvent {private ArticleEventEnum type;private T content;public ArticleMsgEvent(Object source, ArticleEventEnum type, T content) {super(source);this.type = type;this.content = content;}
}

类上的四个注解为lombok提供。两个字段,

type:枚举类型(ArticleEventEnum),代表事件的类型。

表示文章上线或者下线。

content:泛型(T),表示事件的内容,在本例中,我们会传一个文章的ID。

source:在构造方法里卖我们还会传一个Object类型的数据,表示事件的来源,也就是事件的发布者。

ApplicationEvent:是Spring Framework框架中用于定义事件的基类。

第二步(发布事件)

定义SpringUtil工具类,实现了ApplicationContexAware

@Component
public class SpringUtil implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringUtil.context = applicationContext;}/*** 发布事件消息** @param event*/public static void publishEvent(ApplicationEvent event) {context.publishEvent(event);}

通过实现ApplicationContextAware接口,可以让这个类在Spring容器启动时自动获得ApplicationContext引用。(作为事件发布器)@Component可以让该类被Spring容器自动实例化和管理。

自动装配过程是通过Spring得ApplicationContextAwareProcessor类实现得,它是一个后置处理器。在Spring容器初始化时,他会检查所有得Bean,如果Bean实现了ApplicationContextAware接口。他会调用setApplicationContext方法将ApplicationContext的引用传递给Bean。

第三步(用事件)

通过调用SpringUtil.publishEvent()发布事件。在ArticleSettingServiceImpl类中。

@Override
public void updateArticle(ArticlePostReq req) {
ArticleDO article = articleDao.getById(req.getArticleId());
if (article == null) {return;
}if (StringUtils.isNotBlank(req.getTitle())) {article.setTitle(req.getTitle());
}
article.setShortTitle(req.getShortTitle());ArticleEventEnum operateEvent = null;
if (req.getStatus() != null) {article.setStatus(req.getStatus());if (req.getStatus() == PushStatusEnum.OFFLINE.getCode()) {operateEvent = ArticleEventEnum.OFFLINE;} else if (req.getStatus() == PushStatusEnum.REVIEW.getCode()) {operateEvent = ArticleEventEnum.REVIEW;} else if (req.getStatus() == PushStatusEnum.ONLINE.getCode()) {operateEvent = ArticleEventEnum.ONLINE;}//            switch (req.getStatus()){//                case 0 ://                    operateEvent = ArticleEventEnum.OFFLINE;//                    break;//                case 3 ://                    operateEvent = ArticleEventEnum.REVIEW;//                    break;//                case 2 ://                    operateEvent = ArticleEventEnum.ONLINE;//                    break;//                default://                    break;//            }}
articleDao.updateById(article);if (operateEvent != null) {// 发布文章待审核、上线、下线事件SpringUtil.publishEvent(new ArticleMsgEvent<>(this, operateEvent, article.getId()));
}
}

第四步(监听并处理事件)

通过 @EventListener注解来处理事件,在SitemapServiceImpl类中可以看到。

/*** 基于文章的上下线,自动更新站点地图** @param event*/
@EventListener(ArticleMsgEvent.class)
public void autoUpdateSiteMap(ArticleMsgEvent<Long> event) {ArticleEventEnum type = event.getType();if (type == ArticleEventEnum.ONLINE) {addArticle(event.getContent());} else if (type == ArticleEventEnum.OFFLINE || type == ArticleEventEnum.DELETE) {rmArticle(event.getContent());}
}public void addArticle(Long articleId) {
RedisClient.hSet(SITE_MAP_CACHE_KEY, String.valueOf(articleId), System.currentTimeMillis());
}public void rmArticle(Long articleId) {RedisClient.hDel(SITE_MAP_CACHE_KEY, String.valueOf(articleId));
}

当ArticleMsgEvent类型的事件被发布时,此方法自动被触发,在该方法中,首先获得事件的类型(ArticleEventEnum枚举值),然后根据事件类型执行相应的操作,上线时将文章添加到SiteMap,下线时从SiteMap中删除。

测试

这个就时技术派中的事件监听机制了。

启动Redis,启动服务端,启动admin端,在后端找一篇文章下线文章。

就可以在debug模式下看到事件触发了。

原理分析

Spring事件监听机制涉及到s四个主要的类:

事件对象:ApplicationEvent

事件监听器:ApplicationLisener,事件监听器,可以通过@EventListener注解定义事件处理方法,而无需实现ApplicationListener接口

事件发布者:ApplicationEventPublisher,在Spring中可以通过ApplicationEventPublisherAware接口或使用@Autowired注解来注入ApplicationEventPublisher实例,当事件被发布时,Spring会自动调用已注册的ApplicationListener实现类得onApplicationEvent()方法。

事件管理者:ApplicationEventMulticaster,管理监听器和发布事件,通常由SimpleApplicationEventMulticaster类实现。他会遍历所有已经注册的监听器,并调用他们的onApplicationEvent()方法。

ApplicationEvent

ApplicationEvent继承了EventObject对象。

来看看ApplicationEvent的子类关系图

ApplicationEvent 有一个重要的子类 ApplicationContextEvent,而ApplicationContextEvent 又有 4 个重要的子类:

ContextStartedEvent:当 Spring 容器启动时触发该事件。这意味着所有 Bean 都已加载,并且 ApplicationContext 已初始化。

ContextStoppedEvent:当 Spring 容器停止时触发该事件。当容器关闭并停止处理请求时,通常会触发此事件。

ContextRefreshedEvent:当 ApplicationContext 刷新时触发该事件。这表示所有Bean 都已创建,并且已初始化所有单例 Bean(前提是它们在容器初始化时需要初始化)

ContextClosedEvent:当 Spring 容器关闭时触发该事件。这表示所有 Bean 都已销塾Spring 容器已清理资源并停止,

ApplicationListener

ApplicationListener继承EventListener接口,并要求实现onApplicationEvent(E event)方法。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}

onApplicationEvent(E event)方法:当发布某个事件时,所有注册的ApplicationListener 实例的 onApplicationEvent 方法都会被调用。在这个方法中,可以编写处理特定事件的逻辑。此方法接收一个类型为E的参数,这是 ApplicationEvent 的子类表示触发的事件。

当 Spring 应用启动时,Spring 会扫描所有的 Bean,寻找使用了 @EventListener 注解的方法。一旦找到这样的方法,Spring 会为这些方法创建 ApplicationListener 实例并将其注册到 ApplicationEventMulticaster。

ApplicationEventMulticaster

ApplicationEventMulticaster 是一个接口,负责管理监听器和发布事件,包含了注册监听器、移除监听器以及发布事件的方法。

Spring 容器中通常会有一个默认的实现,如 SimpleApplicationEventMulticaster,继承了AbstractApplicationEventMulticaster.

AbstractApplicationEventMulticaster 主要实现了管理监听器的方法(上面接口的前 5 个方法),比如说 addApplicationListener。

public void addApplicationListener(ApplicationListener<?> listener) {Assert.notNull(listener, "ApplicationListener must not be null");if (this.applicationEventMulticaster != null) {this.applicationEventMulticaster.addApplicationListener(listener);}this.applicationListeners.add(listener);
}

最核心的一句代码: this.defaultRetriever.applicationListeners.add(listener);,其内部类 DefaultListenerRetriever 里面有两个集合,用来记录维护事件监听器

这就和设计模式中的发布订阅模式一样了,维护一个 List,用来管理所有的订阅者,当发布者发布消息时,遍历对应的订阅者列表,执行各自的回调 handler。

再来看 SimpleApplicationEventMulticaster 类实现的广播事件逻辑

multicastEvent 的主要作用是将给定的 ApplicationEvent 广播给所有匹配的监听器

首先,通过检査 eventType 参数是否为 nul 来确定事件类型。如果 eventType 为null,则使用 resolveDefaultEventType(event)方法从事件对象本身解析事件类型

获取 Executor,它是一个可选的任务执行器,用于在异步执行监听器时调用。如果没有配置 Executor,则默认为 nul,表示使用同步执行。

使用 getApplicationListeners(event,type)方法获取所有匹配给定事件类型的监听器。

对于每个匹配的监听器,检查是否有 Executor 配置。如果存在 Executor,则使用executor.execute()方法将监听器的调用封装到一个异步任务中。如果没有配置Executor,则直接同步调用监听器。

使用 invokeListener(listener,event)方法调用监听器的 onApplicationEvent方法,将事件传递给监听器。

通过这个实现,SimpleApplicationEventMulticaster 可以将事件广播给所有关心该事件的监听器,同时支持同步和异步执行模式。

最后调用 istener.onApplicationEvent(event);也就是我们通过实现接口ApplicationListener 的方式来实现监听器的 onApplicationEvent 实现逻辑。

ApplicationEventPublisher

ApplicationEventPublisher 是一个接口,用于将事件发布给所有感兴趣的监听器。

这个接口的实现类通常会将事件委托给

ApplicationEventMulticaster。在 Spring 中ApplicationContext 通常充当事件发布者,它就实现了 ApplicationEventPublisher 接口。

ApplicationContext 的 publishEvent 方法的逻辑实现主要在类AbstractApplicationContext 中:

这段代码的主要逻辑在这:

这段代码的主要作用是在 ApplicationContext 初始化时处理应用程序事件的发布。当ApplicationContext 还没有完全初始化时,例如在refresh()方法中earlyApplicationEvents 列表会被用来保存早期的事件。在这个阶段ApplicationEventMulticaster 还没有完全配置好,因此无法直接发布事件。这些早期的事件将在 ApplicationContext 初始化完成后,ApplicationEventMulticaster 配置好后,通过 finishRefresh()方法中的 publishEvent(new ContextRefreshedEvent(this));发布。

当 ApplicationContext 初始化完成后,earlyApplicationEvents 列表将被设置为 null。此时,事件可以直接通过 getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType)方法发布给所有匹配的监听器,

这个机制确保了在 ApplicationContext 初始化过程中产生的事件不会丢失,而是在ApplicationContext 初始化完成后被正确地发布给所有感兴趣的监听器。

总结

这篇内容通过源码的形式讲解了 Spring 事件监听机制及其原理。

相关文章:

技术派Spring事件监听机制及原理

Spring事件监听机制是Spring框架中的一种重要技术&#xff0c;允许组件之间进行松耦合通信。通过使用事件监听机制&#xff0c;应用程序的各个组件可以在其他组件不直接引用的情况下&#xff0c;相互发送和接受消息。 需求 在技术派中有这样一个需求&#xff0c;当发布文章或…...

秋招突击——设计模式补充——简单工厂模式和策略模式

文章目录 引言正文简单工厂模式策略模式策略模式和工厂模式的结合策略模式解析 总结 引言 一个一个来吧&#xff0c;面试腾讯的时候&#xff0c;问了我单例模式相关的东西&#xff0c;自己这方面的东西&#xff0c;还没有看过。这里需要需要补充一下。但是设计模式有很多&…...

SwiftUI中List的liststyle样式及使用详解添加、移动、删除、自定义滑动

SwiftUI中的List可是个好东西&#xff0c;它用于显示可滚动列表的视图容器&#xff0c;类似于UITableView。在List中可以显示静态或动态的数据&#xff0c;并支持垂直滚动。List是一个数据驱动的视图&#xff0c;当数据发生变化时&#xff0c;列表会自动更新。针对List&#xf…...

PostgreSQL的系统视图pg_stats

PostgreSQL的系统视图pg_stats pg_stats 是 PostgreSQL 提供的一种系统视图&#xff0c;用于展示当前数据库中的统计信息。这些统计信息由数据库内部的自动统计过程通过 ANALYZE 命令收集&#xff0c;它们帮助查询规划器做出更好的执行决策&#xff0c;从而优化查询性能。 pg…...

UML2.0-系统架构师(二十四)

1、&#xff08;重点&#xff09;系统&#xff08;&#xff09;在规定时间内和规定条件下能有效实现规定功能的能力。它不仅取决于规定的使用条件等因素&#xff0c;还与设计技术有关。 A可靠性 B可用性 C可测试性 D可理解性 解析&#xff1a; 可靠性&#xff1a;规定时间…...

leetcode 152. 乘积最大子数组「贪心」「动态规划」

152. 乘积最大子数组 题目描述&#xff1a; 给你一个整数数组nums&#xff0c;请你找出数组中乘积最大的非空连续子数组&#xff0c;并返回该子数组所对应的乘积 思路1&#xff1a;贪心 由于 n u m s [ i ] nums[i] nums[i]都是整数&#xff0c;所以多乘一些数肯定不会让绝…...

Android项目目录结构

Android项目目录结构 1. 顶层目录2. 重要的顶层文件和目录3. app模块目录结构4. 重要的**app**模块文件和目录5. 典型的 **build.gradle** 文件内容 典型的Android项目结构的详细介绍。 1. 顶层目录 MyAndroidApp/ ├── .gradle/ ├── .idea/ ├── app/ ├── build/ ├…...

网络安全--计算机网络安全概述

文章目录 网络信息系统安全的目标网络安全的分支举例P2DR模型信息安全模型访问控制的分类多级安全模型 网络信息系统安全的目标 保密性 保证用户信息的保密性&#xff0c;对于非公开的信息&#xff0c;用户无法访问并且无法进行非授权访问&#xff0c;举例子就是&#xff1a;防…...

用requirements.txt配置环境

1. 在anaconda创建环境 创建Python版本为3.8的环境&#xff0c;与yolov5所需的包适配。 2. 在Anaconda Prompt中激活环境 (base) C:\Users\吴伊晴>conda activate yolov5 3. 配置环境 用指定路径中的requirements.txt配置环境。 (yolov5) C:\Users\吴伊晴>pip insta…...

APP渗透-android12夜神模拟器+Burpsuite实现

一、夜神模拟器下载地址&#xff1a;https://www.yeshen.com/ 二、使用openssl转换证书格式 1、首先导出bp证书 2、将cacert.der证书在kali中转换 使用openssl生成pem格式证书,并授予最高权限 openssl x509 -inform der -in cacert.der -out cacert.pem chmod 777 cacert…...

源码扭蛋机开发初探

在软件开发的世界里&#xff0c;创新总是层出不穷。今天&#xff0c;我们将一起探讨一个有趣而富有创意的项目——源码扭蛋机。源码扭蛋机&#xff0c;顾名思义&#xff0c;就是将传统的扭蛋机概念与代码编程相结合&#xff0c;让开发者们在扭动的过程中随机获得各种有趣的、实…...

Patch SCN使用说明---惜分飞

软件说明 该软件是惜分飞&#xff08;https://www.xifenfei.com&#xff09;开发&#xff0c;仅用来查看和修改Oracle数据库SCN(System Change Number),主要使用在数据库因为某种原因导致无法正常启动的情况下使用该工具进行解决.特别是Oracle新版本中使用隐含参数,event,orad…...

【微服务架构的守护神】Eureka与服务熔断深度解析

标题&#xff1a;【微服务架构的守护神】Eureka与服务熔断深度解析 在微服务架构中&#xff0c;服务的数量众多&#xff0c;网络请求的复杂性也随之增加&#xff0c;这使得系统的稳定性面临挑战。服务熔断作为一种保护机制&#xff0c;能够在服务出现问题时及时切断请求&#…...

使用label-studio对OCR数据进行预标注

导读 label-studio作为一款数据标注工具相信大家都不陌生&#xff0c;对于需要进行web数据标注协同来说应该是必备工具了&#xff0c;标注的数据类型很全涉及AI的各个任务(图像、语音、NLP、视频等)&#xff0c;还支持自定义涉及模版。 然而&#xff0c;我们在标注数据的过程…...

嵌入式linux sqlite3读写demo

以下是一个简单的C语言程序&#xff0c;使用SQLite数据库进行读写操作的示例。请确保您已经安装了SQLite3库。 #include <stdio.h> #include <stdlib.h> #include <sqlite3.h> static int callback(void *NotUsed, int argc, char **argv, char **azColNam…...

vue实现搜索文章关键字,滑到指定位置并且高亮

1、输入搜索条件&#xff0c;点击搜索按钮 2、滑到定位到指定的搜索条件。 <template><div><div class"search_form"><el-inputv-model"searchVal"placeholder"请输入关键字查询"clearablesize"small"style&quo…...

Stable Diffusion与AI艺术:探索人工智能的创造力

引言 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;AI艺术逐渐走进了公众视野。尤其是近年来&#xff0c;Stable Diffusion等技术的出现&#xff0c;显著提升了AI在艺术创作领域的表现力和创造力。这篇文章将深入探讨Stable Diffusion技术的工作原理、应…...

华为HCIP Datacom H12-821 卷26

1.单选题 在VRRP中&#xff0c;同一备份组的设备在进行VRRP报文认证时&#xff0c;以下哪一参数不会影响Master设备和Backup设备认证协商结果 A、认证字 B、优先级 C、认证方式 D、VRRP版本 正确答案&#xff1a; B 解析&#xff1a; 优先级只会影响谁是主谁是备&…...

golang 获取系统的主机 CPU 内存 磁盘等信息

golang 获取系统的主机 CPU 内存 磁盘等信息 要求 需要go1.18或更高版本 官方地址&#xff1a;https://github.com/shirou/gopsutil 使用 #下载包 go get github.com/shirou/gopsutil/v3/cpu go get github.com/shirou/gopsutil/v3/disk go get github.com/shirou/gopsuti…...

Infinitar链游新发展新机遇

区块链游戏市场在近年来经历了显著增长&#xff0c;吸引了大量的投资和关注。随着加密货币和NFT&#xff08;非同质化代币&#xff09;概念的普及&#xff0c;越来越多的投资者、游戏开发者和看到了区块链技术在游戏领域的应用潜力&#xff0c;纷纷涌入市场。区块链游戏的用户量…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

如何理解 IP 数据报中的 TTL?

目录 前言理解 前言 面试灵魂一问&#xff1a;说说对 IP 数据报中 TTL 的理解&#xff1f;我们都知道&#xff0c;IP 数据报由首部和数据两部分组成&#xff0c;首部又分为两部分&#xff1a;固定部分和可变部分&#xff0c;共占 20 字节&#xff0c;而即将讨论的 TTL 就位于首…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...