Spring源码十二:事件发布源码跟踪

上一篇我们在Spring源码十一:事件驱动中,介绍了spring refresh方法的initMessageSource方法与initApplicationEventMulticaster方法,举了一个简单的例子进行简单的使用的Spring为我们提供的事件驱动发布的示例。这一篇我们将继续跟踪源码,看看Spring事件是如何发布的。


事件是如何发布的呢?

{public static void main(String[] args) {ApplicationContext context = new MyselfClassPathXmlApplicationContext("applicationContext.xml");
//		JmUser jmUser = (JmUser)context.getBean("jmUser");
//		System.out.println(jmUser.getName());
//		System.out.println(jmUser.getAge());// 发布事件MyselfEvent event = new MyselfEvent("事件源:source", "This is a custom event");context.publishEvent(event);}
}

今天我们先从refresh方法中暂时抽离出来,接着上一篇我们给的示例往下看:

我们以MyselfClassPathXmlApplicationContext(继承ClassPathXmlApplicationContext

类)中的publishEvent方法作为入口,看下事件MyselfEvent是如何发布的呢:

我们继续进入重载的publishEvent方法中:

/**** Publish the given event to all listeners.* 					用于将指定的事件发布给所有监听器* @param event the event to publish (may be an {@link ApplicationEvent}*                 	参数表示要发布的事件,可以是 ApplicationEvent* or a payload object to be turned into a {@link PayloadApplicationEvent})*               	或一个负载对象(将被转换为 PayloadApplicationEvent)。* 	 ** @param eventType the resolved event type, if known* @since 4.2*/protected void publishEvent(Object event, @Nullable ResolvableType eventType) {Assert.notNull(event, "Event must not be null");// Decorate event as an ApplicationEvent if necessary// 判断传入的 event 是否为 ApplicationEvent 的实例://		如果是,则直接赋值给 applicationEvent。//		如果不是,则将其包装为 PayloadApplicationEvent。ApplicationEvent applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;}else {applicationEvent = new PayloadApplicationEvent<>(this, event);// 如果 eventType 为空且事件被包装为 PayloadApplicationEvent,则从包装的事件中获取事件类型if (eventType == null) {eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();}}// 事件分发// Multicast right now if possible - or lazily once the multicaster is initialized:// 判断 earlyApplicationEvents 是否不为空// 如果不为空,说明广播器还未初始化,则将事件添加到 earlyApplicationEvents 队列中,等待广播器初始化后再分发。// 如果为空,说明广播器已初始化, 立即分发事件。if (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else {// 直接通过 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// Publish event via parent context as well...// 如果当前上下文有父上下文 (this.parent 不为空),则同样向父上下文发布事件:if (this.parent != null) {//如果父上下文是 AbstractApplicationContext 的实例,则调用其 publishEvent 方法,同时传递事件和事件类型。// 如果不是,则直接调用父上下文的 publishEvent 方法。if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);}else {this.parent.publishEvent(event);}}}

上述代码注释已经很清晰了,我们简单总结下:这段代码的主要功能是将事件发布给所有注册的监听器,并在可能的情况下向父上下文传递事件。它首先确保事件对象不为空,然后检查和包装事件,最后通过事件广播器将事件分发给监听器。如果广播器未初始化,则将事件暂时存储,等待初始化后再分发。

根据我们我们示例代码,可以发现我们传入的event类型肯定是AppellationEvent的实例,毕竟MyselfEvent就是继承ApplicationEvent接口的。接着我们进入get ApplicationEventMulticaster.multicastEvent(),其中event我们已经分析过了:


ApplicationEventMulticaster

getApplicationEventMulticaster()方法获取的对象默认的是SimpleApplicationEventMulticast类:

继续进入:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {// 确定事件类型,如果传入的 eventType 不为空,则使用它;否则,使用默认的事件类型ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));// 获取任务执行器,如果没有设置,则为 nullExecutor executor = getTaskExecutor();// 遍历所有匹配的监听器for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {// 如果任务执行器不为空,则使用它异步执行监听器executor.execute(() -> invokeListener(listener, event));}else {// 如果任务执行器为空,则同步调用监听器invokeListener(listener, event);}}
}

如上图:

在方法multicastEvent中默认是没有设置线程池的,所以executor为空。接着往下看:

我们看到会通过getApplicationListeners方法获取Spring容器中的所有监听器,然后依次遍历处理这些监听器,从这里我们初步可以知道:

广播器的作用其实就是当一个事件发生时,通知Spring容器中的所有注册的监听器,然后让每个监听器自行决定是否要处理这个事件。

/*** Invoke the given listener with the given event.* @param listener the ApplicationListener to invoke* @param event the current event to propagate* @since 4.1*/protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {doInvokeListener(listener, event);}catch (Throwable err) {errorHandler.handleError(err);}}else {doInvokeListener(listener, event);}}

如上图所示:最终就会调用监听器中的onApplicationEvent方法执行监听器中的逻辑。

目前只有我们自定义的监听器才会处理事件MyselfEvent,所以其他的监听器就算执行了onApplicationEvent方法,也会选择无视这个事件。

看下调用栈:

主要就是通过广播器ApplicationEventMulticaster和监听器ApplicationListener来实现的,大家也可以理解为是发布-订阅模式,ApplicationEventMulticaster用来广播发布事件,ApplicationListener监听订阅事件,每种监听器负责处理一种或多种事件:我们自定义的监听器因为指定了泛型,所以,只能处理指定类型的事件,稍微修改一下如下代码所示:

package org.springframework.listener;import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.event.MyselfEvent;
import org.springframework.stereotype.Component;/*** 自定义监听*/
@Component
public class MyselfCommonListener implements ApplicationListener {@Overridepublic void onApplicationEvent(ApplicationEvent event) {// 处理事件if (event instanceof MyselfEvent ){MyselfEvent event1 = (MyselfEvent) event;event1.event();System.out.println("监听接受事件");System.out.println("Received event with message: " + event1.getMessage());}}
}

而且,如果大家冷静分析一下会发现,其实Spring这套发布订阅的模式,采用的就是设计模式中的观察者模式,ApplicationEventMulticaster作为广播事件的subject,属于被观察者,ApplicationListener作为Observer观察者,最终是用来处理相应的事件的。


 注册ApplicationListener

上一节咱们跳出了refresh方法,今天咱们继续回到refresh方法中:

@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing. 1、初始化上下文信息,替换占位符、必要参数的校验prepareRefresh();// Tell the subclass to refresh the internal bean factory. 2、解析类Xml、初始化BeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 这一步主要是对初级容器的基础设计// Prepare the bean factory for use in this context. 	3、准备BeanFactory内容:prepareBeanFactory(beanFactory); // 对beanFactory容器的功能的扩展:try {// Allows post-processing of the bean factory in context subclasses. 4、扩展点加一:空实现,主要用于处理特殊Bean的后置处理器postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context. 	5、spring bean容器的后置处理器invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation. 	6、注册bean的后置处理器registerBeanPostProcessors(beanFactory);// Initialize message source for this context.	7、初始化消息源initMessageSource();// Initialize event multicaster for this context.	8、初始化事件广播器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses. 9、扩展点加一:空实现;主要是在实例化之前做些bean初始化扩展onRefresh();// Check for listener beans and register them.	10、初始化监听器registerListeners();// Instantiate all remaining (non-lazy-init) singletons.	11、实例化:非兰加载BeanfinishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.	 12、发布相应的事件通知finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

/*** Add beans that implement ApplicationListener as listeners.* Doesn't affect other listeners, which can be added without being beans.*/protected void registerListeners() {// Register statically specified listeners first.// 获取所有监听器,并且将这些监听器注册到广播器中for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let post-processors apply to them!// 获取所有ApplicationListener类型监听器注册到广播器中String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// Publish early application events now that we finally have a multicaster...// 发布Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (earlyEventsToProcess != null) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}

上述内容可以看到这里就是将各种监听器注册到广播器中,然后通过广播器通知各个监听器,监听器自行处理事件。

总结

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

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

相关文章

vue3项目 前端blocked:mixed-content问题解决方案

一、问题分析 blocked:mixed-content其实浏览器不允许在https页面里嵌入http的请求&#xff0c;现在高版本的浏览器为了用户体验&#xff0c;都不会弹窗报错&#xff0c;只会在控制台上打印一条错误信息。一般出现这个问题就是在https协议里嵌入了http请求&#xff0c;解决方法…

【JavaEE】多线程进阶

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; 文章目录 1.锁策略1.1悲观锁和乐观锁1.2重量级锁和轻量级锁1.3自旋锁和挂起等待锁1.4可…

nodejs + vue3 模拟 fetchEventSouce进行sse流式请求

先上效果图: 前言: 在GPT爆发的时候,各项目都想给自己的产品加上AI,蹭上AI的风口,因此在最近的一个需求,就想要给项目加入Ai的功能,原本要求的效果是,查询到对应的数据后,完全展示出来,也就是常规的post请求,后来这种效果遇到了一个很现实的问题:长时间的等待。我…

集成测试技术栈

前端 浏览器操作&#xff1a;playwright、selenium 后端 testcontainercucumbervitestcypressmsw

论文解析——FTRANS: Energy-Efficient Acceleration of Transformers using FPGA

作者及发刊详情 Li B , Pandey S , Fang H ,et al.FTRANS: energy-efficient acceleration of transformers using FPGA[J].ACM, 2020.DOI:10.1145/3370748.3406567. 摘要 正文 主要工作贡献 与CPU和GPU在执行Transformer和RoBERTa相比&#xff0c;提出的FTRANS框架获得了…

入门PHP就来我这(高级)13 ~ 图书添加功能

有胆量你就来跟着路老师卷起来&#xff01; -- 纯干货&#xff0c;技术知识分享 路老师给大家分享PHP语言的知识了&#xff0c;旨在想让大家入门PHP&#xff0c;并深入了解PHP语言。 今天给大家接着上篇文章编写图书添加功能。 1 添加页面 创建add.html页面样式&#xff0c;废…

acwing 291.蒙德里安的梦想

解法&#xff1a; 核心&#xff1a;先放横着的&#xff0c;再放竖着的。 总方案数&#xff0c;等于只放横着的小方块的合法方案数。 如何判断当前方案是否合法&#xff1f;所有剩余位置&#xff0c;能否填充满竖着的小方块。 即按列来看&#xff0c;每一列内部所有连续的空着的…

PTA甲级1005:Spell It Right

错误代码&#xff1a; #include<iostream> #include<vector> #include<unordered_map> using namespace std;int main() {unordered_map<int, string> map {{0, "zero"}, {1, "one"}, {2, "two"}, {3, "three&qu…

EN-SLAM:Implicit Event-RGBD Neural SLAM解读

论文路径&#xff1a;https://arxiv.org/pdf/2311.11013.pdf 目录 1 论文背景 2 论文概述 2.1 神经辐射场&#xff08;NeRF&#xff09; 2.2 事件相机&#xff08;Event Camera&#xff09; 2.3 事件时间聚合优化策略&#xff08;ETA&#xff09; 2.4 可微分的CRF渲染技术…

网络安全设备——防火墙

网络安全设备防火墙是一种用来加强网络之间访问控制的特殊网络互联设备。以下是对防火墙的详细解释&#xff1a; 一、定义与基本概念 定义&#xff1a;防火墙是指设置在不同网络&#xff08;如可信任的企业内部网和不可信的公共网&#xff09;或网络安全域之间的一系列部件的…

ts-01.泛型(函数和接口)

泛型 泛的意思是:漂浮, 比如泛舟; 泛型: 类型漂浮未定 > 动态类型. 用于: 函数 接口 类 T extends string | number 泛型约束 function a<T any, K> (: number, value: T) { // 泛型参数设置默认值anyconst arr Array<T>(l).fill(value) // [foo, foo, foo] }…

论文研读|AI生成图像检测发展历程及研究现状

前言&#xff1a;本篇博客系统性梳理AI生成图像检测的研究工作。 「人工智能生成图像检测」研究及发展现状介绍 参考资料 https://fdmas.github.io/AIGCDetect/针对AIGC检测的鲁棒性测试——常见攻击手段汇总论文研读&#xff5c;以真实图像为参考依据的AIGC检测论文研读&…

实验五 图像增强—空域滤波

一、实验目的 了解图像平滑滤波器&#xff08;均值滤波和中值滤波&#xff09;和图像锐化算子&#xff08;Sobel算子、Prewitt算子、Laplacian算子&#xff09;在工程领域中的应用&#xff1b;理解图像平滑滤波器和图像锐化算子的工程应用范围&#xff1b;掌握图像平滑滤波器和…

Netty学习(Netty入门)

概述 Netty是什么 Netty的地位 Netty的优势 HelloWorld public class HelloClient {public static void main(String[] args) throws InterruptedException {// 1. 启动类new Bootstrap()// 2. 添加 EventLoop.group(new NioEventLoopGroup())// 3. 选择客户端 channel 实现.…

如何恢复未保存的 Excel 文件

您是否曾经在处理 Excel 工作表时&#xff0c;电脑突然崩溃&#xff1f;您首先想到的是“进度保存了吗&#xff1f;”或“我是否按了 CtrlS 来保存文件&#xff1f;”这种压力是难以想象的&#xff0c;因为意外断电或电脑崩溃可能会让您所有的辛苦工作付诸东流。 无论对于学生…

前端技术(三)—— javasctipt 介绍:jQuery方法和点击事件介绍(补充)

6. 常用方法 ● addClass() 为jQuery对象添加一个或多个class <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&…

Educational Codeforces Round 167 (Rated for Div. 2)(A~C)题解

A. Catch the Coin 解题思路: 最终&#x1d465;一定会相等&#xff0c;我们考虑直接到下面接住他。 #include<bits/stdc.h> using namespace std; typedef long long ll; #define N 1000005 ll dp[N], w[N], v[N], h[N]; ll dis[1005][1005]; ll a, b, c, n, m, t; ll…

反编译kasada

继续研究反编译 这次的网站是 一个航司网站 他有 akamai和 kasada 两种防护 akamai 没啥好说的 结构分析 最开始有个长字符串 处理成 一个十几万的数组 通过 r.W[0] 走什么分支 还有数据的存取 M是一个98个函数组成的数组 代表不同的执行逻辑 这里给他转成了 switch case…

pygame 音乐粒子特效

代码 import pygame import numpy as np import pymunk from pymunk import Vec2d import random import librosa import pydub# 初始化pygame pygame.init()# 创建屏幕 screen pygame.display.set_mode((1920*2-10, 1080*2-10)) clock pygame.time.Clock()# 加载音乐文件 a…

RAID的实现

软RAID&#xff0c;在实际工作中使用较少&#xff0c;性能太次。 mdadm工具&#xff0c;主要在虚拟机上使用&#xff0c; 硬RAID 用一个单独的芯片&#xff0c;这个芯片的名字叫做RAID卡&#xff0c;数据在RAID中进行分散的时候&#xff0c;用的就是RAID卡。 模拟RAID-5工作…