技术派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 事件监听机制及其原理。

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

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

相关文章

简单分享下python多态

目录&#xff1a; 一、多态是啥嘞&#xff08;龙生九子各有不同&#xff0c;这就是多态&#xff09; 二、基础的实例 三、多态的优势与应用场景 四、深入理解 一、多态是啥嘞&#xff08;龙生九子各有不同&#xff0c;这就是多态&#xff09; 多态&#xff08;Polymorphism&…

如何利用算法优化广告效果

效果广告以超过67%的占比&#xff0c;成为了中国互联网广告预算的大头。在BAT、字节等大的媒体平台上&#xff0c;效果广告以CPC实时竞价广告为主。在这种广告产品的投放中&#xff0c;广告主或其代理公司通过针对每个广告点击出价&#xff0c;系统自动把这些点击出价换算成eCP…

【人工智能】-- 智能机器人

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;引言 &#x1f349;机器人介绍 &#x1f348;机器人硬件 &#x1f34d;机械结构 &#x1f34d;传感器 &#x1f34d;控…

nginx配置尝试

from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.responses import JSONResponse, FileResponse, HTMLResponse import logging import os from datetime import datetime import uvicorn# 初始化日志 logging.basicConfig(filenamefile_server.lo…

学java的第3天 后端商城小程序工作

1.数据库的大坑 特殊字段名 ’我的图片表中有一个字段是描述我写成desc了&#xff0c;正好是mysql中的关键字 就不能使用了 2.后端编写 2.1可以把请求分开 在商品浏览页中 只显示商品的大致信息 当用户再点击其他按钮时在发出请求 2.2把请求合并 把数据整合到一起 利用ass…

SpringBoot环境集成 sms4j短信聚合

SpringBoot环境集成 sms4j短信聚合 官方文档 前言 在正式使用sms4j短信功能之前&#xff0c;请详细阅读本文档&#xff0c;依照本篇流程进行操作和配给&#xff0c;即可解决大部分问题&#xff0c;如对我们的文档有建议&#xff0c;请联系开发者团队&#xff0c; 我们将根据可…

电脑为什么会提示丢失msvcp140.dll?怎么修复msvcp140.dll文件会靠谱点

电脑为什么会提示丢失msvcp140.dll&#xff1f;其实只要你的msvcp140.dll文件一损坏&#xff0c;然而你的电脑程序需要运用到这个msvcp140.dll文件的时候&#xff0c;就回提示你丢失了msvcp140.dll文件&#xff01;因为没有这个文件&#xff0c;你的很多程序都用不了的。今天我…

电脑录歌用什么软件好?分享电脑录音软件:6款

短视频普遍的今天&#xff0c;越来越多的人喜欢通过电脑进行音乐创作和录制。然而&#xff0c;面对市面上琳琅满目的电脑录音软件&#xff0c;很多人可能会感到困惑&#xff1a;电脑录歌用什么软件好呢&#xff1f;本文将为大家分享六款精选的录音软件&#xff0c;帮助大家找到…

【matlab】分类回归——智能优化算法优化径向基神经网络

目录 径向基&#xff08;Radial Basis Function, RBF&#xff09;神经网络 一、基本概念 二、网络结构 三、工作原理 四、学习算法 五、优点与应用 六、与BP神经网络的比较 智能优化算法 常见的智能优化算法 灰狼优化算法&#xff08;Grey Wolf Optimizer, GWO&#…

品牌推广的核心价值:作用解析与意义探讨!

在激烈的市场竞争环境之下&#xff0c;品牌推广已经成为企业不可缺少的一部分。不仅关乎企业的知名度&#xff0c;对市场份额更是起到了决定性的作用。 作为一名手工酸奶品牌的创始人&#xff0c;目前全国也复制了100多家门店&#xff0c;这篇文章&#xff0c;我将和大家分享品…

浪潮信息携手算力企业为华东产业集群布局提供高质量算力支撑

随着信息技术的飞速发展&#xff0c;算力已成为推动数字经济发展的核心力量。近日&#xff0c;浪潮信息与五家领先的算力运营公司在南京正式签署战略合作协议&#xff0c;共同加速华东地区智算基础设施布局&#xff0c;为区域经济发展注入新动力。 进击的算力 江苏持续加码智算…

【C语言】指针(1):入门理解篇

目录 一、内存和地址 1.1内存 1.2 深入理解计算机编址 二、指针变量和地址 2.1 取地址操作符&#xff08;&&#xff09; 2.2 指针变量和解应用操作符 2.2.1 指针变量 2.2.2 解引用操作符 2.3指针变量的大小 三、指针变量类型的意义 3.1 指针的解引用 3.1指针-整数…

2024 年 6 月区块链游戏研报:Pixels 引发 DAU 波动,行业用户留存率差异显著

作者&#xff1a;Stella L (stellafootprint.network) 数据来源&#xff1a;区块链游戏研究页面 2024 年 6 月&#xff0c;加密货币市场遭遇显著回调&#xff0c;比特币跌幅达 7.3%&#xff0c;以太坊更是下跌了 9.8%。此番波动不可避免地波及区块链游戏领域&#xff0c;导致…

C语言 do while 循环语句练习 中

练习&#xff1a; 4.编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 // 编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 //welcome to china!!! //w ! //we !! //wel !!! //.... //welco…

BufferReader/BufferWriter使用时出现的问题

项目场景&#xff1a; 在一个文件中有一些数据&#xff0c;需要读取出来并替换成其他字符再写回文件中&#xff0c;需要用Buffer流。 问题描述 文件中的数据丢失&#xff0c;并且在读取前就为空&#xff0c;读取不到数据。 问题代码&#xff1a; File f new File("D:\\…

Selenium的这些自动化测试技巧你知道几个?

Selenium自动化测试技巧 与以前瀑布式开发模式不同&#xff0c;现在软件测试人员具有使用自动化工具执行测试用例套件的优势&#xff0c;而以前&#xff0c;测试人员习惯于通过测试脚本执行来完成测试。 但自动化测试的目的不是完全摆脱手动测试&#xff0c;而是最大程度地减少…

Ubuntu24.04(22.04+版本通用)Miniconda与Isaacgym

1. ubuntu24.04安装minicondda mkdir -p ~/miniconda3 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh解释下这段代码 bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3~/miniconda3/miniconda.sh: 指向Mi…

通信软件开发之业务知识:PON口割接什么意思?

一 PON口割接&#xff08;原创总结&#xff09; 在通信领域&#xff0c;PON口割接指的是对无源光网络&#xff08;Passive Optical Network&#xff0c;PON&#xff09;端口进行的切换或调整操作。简单来说&#xff0c;就是对光纤网络中的某个端口进行重新连接或重新分配&…

最近你悟出来什么道理?

点击上方△腾阳 关注 转载请联系授权 大家伙&#xff0c;我是腾阳。 活了近30年的我&#xff0c;终于领悟到&#xff0c;人生的旅途是一场深刻而复杂的自我发现与灵魂成长的壮丽征途。 这不仅仅是对外在世界的探索&#xff0c;更是内心深处的一场革命&#xff0c;是灵魂从懵…

11.x86游戏实战-汇编指令add sub inc dec

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;10.x86游戏实战-汇编指令lea 首先双击下图红框位置 然后在下图红框位置输入0 然…