Spring的事件监听机制
这里写自定义目录标题
- 1. 概述(重点)
- 2. ApplicationEventMulticaster
- 2.1 SimpleApplicationEventMulticaster
- 2.2 AbstractApplicationEventMulticaster
- 3. ApplicationListener
- 3.1 注册监听器
- 3.2 自定义
- 4. SpringApplicationRunListeners
1. 概述(重点)
事件监听机制是观察者模式的一种,Spring的事件监听机制有几个重要的顶层接口,分别是:
- ApplicationEventMulticaster 主要看 AbstractApplicationEventMulticaster
- ApplicationListener
- ApplicationEvent
它们三哥们的关系可以用下面的图来概括
AbstractApplicationEventMulticaster维护了 源类型和事件类型 作为KEY 跟 监听器的关系,在下面 2.1 SimpleApplicationEventMulticaster有代码的实现解读。
2. ApplicationEventMulticaster
目前就只有一个实现类SimpleApplicationEventMulticaster
和一个抽象类AbstractApplicationEventMulticaster
。
2.1 SimpleApplicationEventMulticaster
发布和广播事件,主要注意一下getApplicationListeners方法就行了,会根据事件类型
和源类型
找监听器。
@Override
public void multicastEvent(ApplicationEvent event) {multicastEvent(event, resolveDefaultEventType(event));
}@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {invokeListener(listener, event);}}
}
2.2 AbstractApplicationEventMulticaster
getApplicationListeners
方法就是根据Class<?>、ResolvableType两个对象作为一个缓存key,这个key与一堆监听器`ApplicationListener`做映射,也就是说通过Class<?>、ResolvableType两个对象可以找到监听器,在用法上可以从SimpleApplicationEventMuticaster
的 multicastEvent
方法去看。
看看getApplicationListeners方法的代码实现
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {Object source = event.getSource();Class<?> sourceType = (source != null ? source.getClass() : null);ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);CachedListenerRetriever newRetriever = null;// 这个cacheKey就是ApplicationEvent,ResolvableType两个对象。// 根据cacheKey找监听器,找不到就把cacheKey作为key,创建一个什么都么有的CachedListenerRetriever对象作为值缓存到retrieverCache中。// 这里有一些细节我没搞明白,putIfAbsent总是返回null。CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);if (existingRetriever == null) {if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {newRetriever = new CachedListenerRetriever();existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);if (existingRetriever != null) {newRetriever = null; // no need to populate it in retrieveApplicationListeners}}}if (existingRetriever != null) {Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();if (result != null) {return result;}}// 假设上面cacheKey对应的值没有监听器,那这个方面就找 给定事件类型和源类型 的监听器。return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}// 从默认的 defaultRetriever 和 我们注入的bean 中找监听器。
private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {List<ApplicationListener<?>> allListeners = new ArrayList<>();Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);Set<ApplicationListener<?>> listeners;Set<String> listenerBeans;synchronized (this.defaultRetriever) {listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}// 从defaultRetriever中找监听器// 这里的supportsEvent有点看不懂,不过只要理解通过事件类型和源类型判断监听器是否符合 事件要求就得了。for (ApplicationListener<?> listener : listeners) {if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {filteredListeners.add(listener);}allListeners.add(listener);}}// 根据bean名字去找监听器if (!listenerBeans.isEmpty()) {ConfigurableBeanFactory beanFactory = getBeanFactory();for (String listenerBeanName : listenerBeans) {try {if (supportsEvent(beanFactory, listenerBeanName, eventType)) {ApplicationListener<?> listener =beanFactory.getBean(listenerBeanName, ApplicationListener.class);if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {if (beanFactory.isSingleton(listenerBeanName)) {filteredListeners.add(listener);}else {filteredListenerBeans.add(listenerBeanName);}}allListeners.add(listener);}}else {Object listener = beanFactory.getSingleton(listenerBeanName);if (retriever != null) {filteredListeners.remove(listener);}allListeners.remove(listener);}}catch (NoSuchBeanDefinitionException ex) {}}}// 把找到的监听器都放到CachedListenerRetriever中。AnnotationAwareOrderComparator.sort(allListeners);if (retriever != null) {if (filteredListenerBeans.isEmpty()) {retriever.applicationListeners = new LinkedHashSet<>(allListeners);retriever.applicationListenerBeans = filteredListenerBeans;}else {retriever.applicationListeners = filteredListeners;retriever.applicationListenerBeans = filteredListenerBeans;}}return allListeners;
}
3. ApplicationListener
主要是了解如何注册和自定义监听器就行了
3.1 注册监听器
通过META-INF/spring.factories
注册监听器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 这里就是注册ApplicationListener的代码了,通过META-INF/spring.factories注册。setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}
3.2 自定义
public class MyApplicationPreparedListener implements ApplicationListener<ApplicationPreparedEvent> {@Overridepublic void onApplicationEvent(ApplicationPreparedEvent event) {System.out.println("开始填充上下文。。。。");}
}
MATE-INF/spring.factories
org.springframework.context.ApplicationListener=sample.config.MyApplicationPreparedListener
4. SpringApplicationRunListeners
SpringApplicationRunListeners
管理了多个EventPublishingRunListener
,EventPublishingRunListener里面包含了事件监听器模型#1.概述(重点)中描述的部分。
class SpringApplicationRunListeners {private final Log log;private final List<SpringApplicationRunListener> listeners;private final ApplicationStartup applicationStartup;...
}public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {private final SpringApplication application;private final String[] args;private final SimpleApplicationEventMulticaster initialMulticaster;...
}
EventPublishingRunListener
中定义了Spring整个启动过程中会触发的事件,下面对触发位置进行大概的描述,更具体的内容还有待研究。
- starting
在SpringApplication#run,事件ApplicationStartingEvent
- environmentPrepared
在SpringApplication#prepareEnvironment,事件ApplicationEnvironmentPreparedEvent
- contextPrepared
在SpringApplication#prepareContext,事件ApplicationContextInitializedEvent
- contextLoaded
在SpringApplication#prepareContext,事件ApplicationPreparedEvent
- started
在SpringApplication#run,事件ApplicationStartedEvent
- ready
在SpringApplication#run,事件ApplicationReadyEvent
- failed
在SpringApplication#handleRunFailure,事件ApplicationFailedEvent
我们也可以利用上面的事件,自己创建一个监听器,然后放到spring.factories,比如现在注册一个ApplicationPreparedEvent
的监听器。
public class MyApplicationPreparedListener implements ApplicationListener<ApplicationPreparedEvent> {@Overridepublic void onApplicationEvent(ApplicationPreparedEvent event) {System.out.println("开始填充上下文。。。。");}
}
MATE-INF/spring.factories
org.springframework.context.ApplicationListener=sample.config.MyApplicationPreparedListener
相关文章:

Spring的事件监听机制
这里写自定义目录标题 1. 概述(重点)2. ApplicationEventMulticaster2.1 SimpleApplicationEventMulticaster2.2 AbstractApplicationEventMulticaster 3. ApplicationListener3.1 注册监听器3.2 自定义 4. SpringApplicationRunListeners 1. 概述&#…...

Zookeeper分布式命名服务实战
目录 分布式命名服务 分布式API目录 分布式节点的命名 分布式的ID生成器 分布式的ID生成器方案: 基于Zookeeper实现分布式ID生成器 基于Zookeeper实现SnowFlakeID算法 分布式命名服务 命名服务是为系统中的资源提供标识能力。ZooKeeper的命名服务主要是利用Z…...
DEV-C++ ege.h库 绘图教程(六)
一、前情回顾 DEV-C ege.h库 绘图教程(一) DEV-C ege.h库 绘图教程(二) DEV-C ege.h库 绘图教程(三) DEV-C ege.h库 绘图教程(四) DEV-C ege.h库 绘图教程(五)…...

MySQL原理(一)架构组成之物理文件组成
目录 一、日志文件 1、错误日志 Error Log 1.1、作用: 1.2、开启关闭: 1.3、使用 2、二进制日志 Binary Log & Binary Log Index 2.1、作用: 2.2、开启关闭: 2.3、Binlog还有一些附加选项参数 (1&#x…...
代码随想录算法训练营第三十七天 | 738.单调递增的数字、 968.监控二叉树
题目链接:738.单调递增的数字 文章讲解:代码随想录 738.单调递增的数字讲解 视频讲解:贪心算法,思路不难想,但代码不好写!LeetCode:738.单调自增的数字 思路和解法 题目: 当且仅当每个相邻位…...
【Django-ninja】django-ninja的hello world
django-ninja简介 Django Ninja是一个用于使用Django和Python 3.6类型提示构建API的Web框架。 主要特点: 易用性:旨在易于使用和直观。 高性能执行:由于Pydantic和异步支持,具有非常高的性能。 编码效率高:类型提…...

ArrayList集合初始化长度是多少,初始化的时候分配内存空间吗
ArrayList一旦初始化,在内存中就会分配空间吗 是的,当ArrayList在Java中初始化时,即使它没有添加任何元素,也会立即分配内存空间。具体来说,对于默认构造函数创建的ArrayList(即不指定初始容量)…...
C语言数组:从入门到进阶
前言: 在这篇博客中,我们将学习如何使用C语言数组的基本知识。数组是C语言中的一种重要数据结构,它允许我们存储一系列相同类型的数据。我们将讨论数组的定义、初始化、访问元素、遍历数组以及数组的应用场景。此外,我们还将通过…...
9.回文数
回文数 将整型转换为字符型反转前一半是否等于后一半将数字本身反转输入一个整数 x,如果 x是一个回文整数,返回 true;否则,返回 false 。 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 例如,121 是回文,而 123 不是。 将整型转换为字符型 反转…...

一分钟在SpringBoot项目中使用EMQ
先展示最终的结果: 生产者端: RestController RequiredArgsConstructor public class TestController {private final MqttProducer mqttProducer;GetMapping("/test")public String test() {User build User.builder().age(100).sex(1).address("世界潍坊渤…...

SOME/IP 协议介绍(七)传输 CAN 和 FlexRay 帧
SOME/IP 不应仅用于传输 CAN 或 FlexRay 帧。但是,消息 ID 空间需要在两种用例之间进行协调。 传输 CAN/FlexRay 应使用完整的 SOME/IP 标头。 AUTOSAR Socket-Adapter 使用消息 ID 和长度来构建所需的内部 PDU,但不会查看其他字段。因此,必…...

与数组相关经典面试题
𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - :来于“云”的“羽球人”。…...
数据结构与算法面试系列-02
1. 一个整数,它加上100后是一个完全平方数,加上168又是一个完全平方数,请问该数是多少? 程序分析:在10万以内判断,先将该数加上100后再开方,再将该数加上168后再开方,如果开方后的结果满足如下条件,即是结果。请看具体分析: 程序代码如下: package com.yoodb.uti…...

CMake 完整入门教程(五)
CMake 使用实例 13.1 例子一 一个经典的 C 程序,如何用 cmake 来进行构建程序呢? //main.c #include <stdio.h> int main() { printf("Hello World!/n"); return 0; } 编写一个 CMakeList.txt 文件 ( 可看做 cmake 的…...
pgsql中with子句和直接查询差别
1、代码的可读性和维护性: 当查询较为复杂时,WITH子句可以将复杂的查询分解成多个简单的步骤,每个步骤都可以有一个易于理解的名字。这样做提高了代码的可读性,也便于后期维护。 2、代码的重用性: 在WITH子句中定义…...
Day 31 | 贪心算法 理论基础 、455.分发饼干 、 376. 摆动序列 、 53. 最大子序和
理论基础 文章讲解 455.分发饼干 题目 文章讲解 视频讲解 思路:从小饼干开始喂小胃口 class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int start 0;int count 0;for (int i 0; i < s.length &&a…...

vue3使用is动态切换组件报错Vue received a Component which was made a reactive object.
vue3使用is动态切换组件,activeComponent用ref定义报错 Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef ins…...

React16源码: React中LegacyContext的源码实现
LegacyContext 老的 contextAPI 也就是我们使用 childContextTypes 这种声明方式来从父节点为它的子树提供 context 内容的这么一种方式遗留的contextAPI 在 react 17 被彻底移除了,就无法使用了那么为什么要彻底移除这个contextAPI的使用方式呢?因为它…...

Gin 框架之jwt 介绍与基本使用
文章目录 一.JWT 介绍二.JWT认证与session认证的区别2.1 基于session认证流程图2.2 基于jwt认证流程图 三. JWT 的构成3.1 header : 头部3.2 payload : 负载3.2.1 标准中注册的声明 (建议但不强制使用)3.2.2 公共的声明3.2.3 私有的声明3.2.4 定义一个payload 3.3 signatrue : …...

从[redis:LinkedList]中学习链表
文章目录 adlistlistNodelistmacros[宏定义]listCreatelistInitNodelistEmptylistReleaselistAddNodeHeadlistLinkNodeHeadlistAddNodeTaillistLinkNodeTaillistInsertNodelistDelNodelistUlinkNodelistIndexredis3.2.100quicklistredis7.2.2quicklist redis的基本数据类型之一…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

基于单片机的宠物屋智能系统设计与实现(论文+源码)
本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢,连接红外测温传感器,可实时精准捕捉宠物体温变化,以便及时发现健康异常;水位检测传感器时刻监测饮用水余量,防止宠物…...