技术派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.mzph.cn/news/864453.shtml

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

相关文章

ollama,springAi实现自然语言处理

ollama安装使用&#xff1a; https://ollama.com/ 下载速度比较慢的可以直接使用以下版本0.1.41 https://pan.baidu.com/s/1hCCkYvFjWqxvPyYA2-YElA?pwdotap 直接管理员身份双击安装&#xff0c;安装成功后会在任务栏里出现这个小图标&#xff1a; 打开cmd&#xff0c;输入…

WhatsApp:连接世界的即时通讯巨头

在数字化浪潮席卷全球的今天&#xff0c;即时通讯工具已成为人们日常生活中不可或缺的一部分。其中&#xff0c;WhatsApp凭借其卓越的功能、出色的用户体验和广泛的用户基础&#xff0c;在全球通讯领域崭露头角&#xff0c;成为连接世界的即时通讯巨头。今天将带您深入了解What…

tkinter显示图片

tkinter显示图片 效果代码解析打开和显示图像 代码 效果 代码解析 打开和显示图像 def open_image():file_path filedialog.askopenfilename(title"选择图片", filetypes(("PNG文件", "*.png"), ("JPEG文件", "*.jpg;*.jpeg&q…

数据资产赋能企业决策:通过精准的数据分析和洞察,构建高效的数据资产解决方案,为企业提供决策支持,助力企业实现精准营销、风险管理、产品创新等目标,提升企业竞争力

一、引言 在信息化和数字化飞速发展的今天&#xff0c;数据已成为企业最宝贵的资产之一。数据资产不仅包含了企业的基本信息&#xff0c;还蕴含了丰富的市场趋势、消费者行为和潜在商机。如何通过精准的数据分析和洞察&#xff0c;构建高效的数据资产解决方案&#xff0c;为企…

【论文通读】GUI Action Narrator: Where and When Did That Action Take

GUI Action Narrator: Where and When Did That Action Take 前言AbstractMotivationSolutionAct2CapData CollectionMetrics MethodExperimentAblation StudyVisual Prompt SizeSpatial PromptTemporal Prompt Conclusion 前言 一篇GUI操作benchmark的工作&#xff0c;作者提…

tkinter实现进度条

tkinter实现进度条 效果代码解析导入需要的模块定义进度条 代码 效果 代码解析 导入需要的模块 import tkinter as tk from tkinter import ttk定义进度条 def start_progress():progress[value] 0max_value 100step 10for i in range(0, max_value, step):progress[valu…

Win11找不到组策略编辑器(gpedit.msc)解决

由于需要同时连接有线网络和无线网络&#xff0c;且重启后双网络都自动连接&#xff0c;因此需要配置组策略。 但是win11找不到组策略编辑器。 灵感来源&#xff1a;Win11找不到组策略编辑器&#xff08;gpedit.msc&#xff09;解决教程 - 知乎 (zhihu.com) 在Win11中&#…

国网协议电表采集方案

项目背景及需求项目地点&#xff1a;重庆港西光伏电站&#xff08;中广核重庆&#xff09;项目背景&#xff1a;光伏发电并网项目电能监控项目目的及难点&#xff1a;实现对EDMI协议电表&#xff08;Mk6E&#xff09;的数据采集&#xff0c;监控光伏发电有效性&#xff0c;做到…

项目管理九大口诀

有工作一定有目标 有目标一定有任务 有任务一定有计划 有计划一定有执行 有执行一定有监控 有监控一定有调整 有调整一定有结果 有结果一定有责任 有责任一定有奖惩 &#x1fa77;有工作一定有目标 目标制定&#xff1a;SMART Specific&#xff08;具体性&#xff09;&#x…

# 职场生活之道:善于团结

在职场这个大舞台上&#xff0c;每个人都是演员&#xff0c;也是观众。要想在这个舞台上站稳脚跟&#xff0c;除了专业技能&#xff0c;更要学会如何与人相处&#xff0c;如何团结他人。团结&#xff0c;是职场生存的重要法则之一。 1. 主动团结&#xff1a;多一个朋友&#x…

《昇思25天学习打卡营第1天|基本介绍》

文章目录 前言&#xff1a;今日所学&#xff1a; 前言&#xff1a; 今天非常荣幸的收到了昇思25天学习打卡营的邀请。昇思MindSpore作为华为昇腾AI全栈的重要一员&#xff0c;他支持端、边、云独立的和协同的统一训练和推理框架&#xff0c;有着易于开发、执行效率高、全场景框…

Kotlin扩展函数(also apply run let)和with函数

also apply run let with的使用例子 private fun testOperator() {/*** also*/val person Person("ZhangSan", 18)person.also {// 通常仅仅打印使用, 也可以通过it修改it.name "ZhangSan1"println("also inner name: " it.name)}println(&qu…

Redis-分布式锁(基本原理和不同实现方式对比)

文章目录 1、基本原理2、不同实现方式 1、基本原理 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&am…

Firewalld 概述

1.firewalld简介 firewalld的作用是为包过滤机制提供匹配规则(或称为策略),通过各种不同的规则&#xff0c;告诉 netfiter对来自指定源、前往指定目的或具有某些协议特征的数据包采取何种处理方式。 为了更加方便地组织和管理防火墙&#xff0c;firewalld 提供了支持网络区域…

以太网基础知识

文章目录 一、以太网&#xff08;Ethernet&#xff09;介绍二、协议介绍三、什么是PHY&#xff1f;1.标准接口协议&#xff1a;2.寄存器配置&#xff1a;3.自动协商&#xff1a; 四、时序4.1RGMII接口时序4.1.1 对其模式4.1.2 延时模式&#xff08;常用&#xff09; 4.2MDIO接口…

WEB01MySQL安装和数据库

第一天、WEB课程 web课程主要讲三部分内容 数据库 数据库介绍 什么是数据库 数据存储的仓库&#xff0c;其本质也是一个文件系统 数据库会按照特定的格式对数据进行存储&#xff0c;用户可以对数据库中的数据进行增加&#xff0c;修改&#xff0c;删除及查询操作。 数据库…

2024/6/30 英语每日一段

Years of economic and political turbulence have brought stagnation.“In a world where there is more risk and uncertainty, people become reluctant to voluntarily move jobs and find better jobs,” says Manning. At the same time, businesses have cut back on i…

企业互联网建站源码系统 附带完整的安装代码包以及搭建部署教程

系统概述 企业互联网建站源码吸系统是一款集众多先进功能于一身的建站工具。它提供了丰富的模板和组件&#xff0c;允许企业根据自身需求和品牌形象进行个性化定制&#xff0c;快速搭建出具有独特风格的网站。 代码示例 系统特色功能一览 1.用户友好界面&#xff1a;系统采用…

你还搞不懂串口的格式转换问题吗?

相信大多数人在使用串口传输不同单片机之间的数据时都会运到数据格式怎么对应起来的问题&#xff0c;今天我们就来聊聊&#xff01; 在开始之前我插一个内容&#xff0c;就是不同的单片机之间的电平可能不相同&#xff0c;是不能直接使用杜邦线连接通信的&#xff0c;需要进行电…

带安全启动—Ubuntu系统—手动安装Nvidia驱动

教程1&#xff1a;在启用安全启动的 Fedora 中安装英伟达驱动 教程2&#xff1a;UEFI安全启动模式下安装Ubuntu的NVIDIA显卡驱动 1. 搜索合适的驱动 Nvidia驱动官网 选择这个 驱动(.run)链接 2. 安装必要的软件依赖 CUDA底层用C写的&#xff0c;因此导入编译器 sudo apt i…