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));

    // 获取任务执行器,如果没有设置,则为 null
    Executor 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 {

    @Override
    public 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方法中:


	@Override
	public 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、初始化BeanFactory
			ConfigurableListableBeanFactory 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、实例化:非兰加载Bean
				finishBeanFactoryInitialization(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.mfbz.cn/a/777466.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

创建React项目

使用 create-react-app快速搭建开发环境 create-react-app 是一个快速创建React开发环境的工具&#xff0c;底层由Webpack构建&#xff0c;封装了配置细节&#xff0c;开箱即用。 安装npx npx是一个由Node.js官方提供的用于快速执行npm包中的可执行文件的工具&#xff0c;np…

如何给gitlab其他访问者创建账号并增加权限

嗨&#xff0c;今天创建了项目之后&#xff0c;我想把项目链接发送给其他人&#xff0c;让他下载这个项目&#xff0c;结果发现对方打开显示登录的界面&#xff0c;没错&#xff0c;他要想使用这个git下载项目&#xff0c;首先他的有一个git账号 接下来我找有权限的相关人员给他…

C语言之常用内存函数以及模拟实现

目录 前言 一、memcpy的使用和模拟实现 二、memmove的使用和模拟实现 三、memset的使用和模拟实现 四、memcmp的使用和模拟实现 总结 前言 本文主要讲述C语言中常用的内存函数&#xff1a;memcpy、memmove、memset、memcmp。内容不多&#xff0c;除了了解如何使用&#x…

前端根据目录生成模块化路由routes

根据约定大于配置的逻辑&#xff0c;如果目录结构约定俗成&#xff0c;前端是可以根据目录结构动态生成路由所需要的 route 结构的&#xff0c;这个过程是要在编译时 进行&#xff0c;生成需要的代码&#xff0c;保证运行时的代码正确即可 主流的打包工具都有对应的方法读取文…

在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——聚合与搜索(三)

#在生产环境中部署Elasticsearch&#xff1a;最佳实践和故障排除技巧——聚合与搜索&#xff08;三&#xff09; 前言 文章目录 前言- 聚合和分析- 执行聚合操作- 1. 使用Java API执行聚合操作- 2. 使用CURL命令执行聚合操作- 1. 使用Java API执行度量操作- 2. 使用CURL命令执…

Java——继承(Inheritance)

一、继承简要介绍 1、继承是什么 在Java中&#xff0c;继承是一种面向对象编程的重要特性&#xff0c;它允许一个类&#xff08;子类或派生类&#xff09;继承另一个类&#xff08;父类或基类&#xff09;的属性和方法。继承的目的是实现代码的重用和设计的层次化。 子类通常…

探索LlamaIndex:如何用Django打造高效知识库检索

简介 LlamaIndex&#xff08;前身为 GPT Index&#xff09;是一个数据框架&#xff0c;为了帮助我们去建基于大型语言模型&#xff08;LLM&#xff09;的应用程序。 主要用于处理、构建和查询自定义知识库。 它支持多种数据源格式 excel&#xff0c;txt&#xff0c;pdf&…

DaViT(ECCV 2022,Microsoft)

paper&#xff1a;DaViT: Dual Attention Vision Transformers official implementation&#xff1a;https://github.com/dingmyu/davit third-party implementation&#xff1a;https://github.com/huggingface/pytorch-image-models/blob/main/timm/models/davit.py 出发点…

独家揭秘!格行随身WiFi‘骄傲’宣言背后的震撼行业的真相!随身WiFi行业内黑马

近几年以来&#xff0c;随行WiFi产品呈现爆发式增长&#xff0c;随行WiFi的火爆&#xff0c;是技术进步带给消费者的一种“福利”&#xff0c;各大直播间也充斥着品牌各异的随身WiFi。但真正脱颖而出、赢得消费者信赖的优质品牌却凤毛麟角。而其中最受欢迎的格行随身WiFi也因设…

Java语言+后端+前端Vue,ElementUI 数字化产科管理平台 产科电子病历系统源码

Java语言后端前端Vue,ElementUI 数字化产科管理平台 产科电子病历系统源码 Java开发的数字化产科管理系统&#xff0c;已在多家医院实施&#xff0c;支持直接部署。系统涵盖孕产全程&#xff0c;包括门诊、住院、统计和移动服务&#xff0c;整合高危管理、智能提醒、档案追踪等…

Stream练习

运用点&#xff1a; 流内数据类型转换(map)、filter、limit、skip、concat(让两个流合并) 题目&#xff1a; 操作1、2&#xff1a; ArrayList<String> manList new ArrayList<>();ArrayList<String> womanList new ArrayList<>();Collections.addAl…

C++之static关键字

文章目录 前提正文多重定义extern关键字使用staticstatic 全局变量(在.cpp文件中定义)static变量存放在哪里static变量可不可以放在.h文件中 static 函数static局部变量static 成员变量static 成员函数 总结参考链接 前提 好吧&#xff0c;八股&#xff0c;我又回来了。这次想…

8.14 矢量图层面要素2.5D渲染

文章目录 前言2.5D渲染QGis设置面符号为2.5D二次开发代码实现2.5D 总结 前言 本章介绍矢量图层面要素2.5D渲染的使用说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 2.5D渲染 2.5D渲染可以将多边形渲染为类3D效果。 QGis设置面符号为2.5D 以"hou…

数据库7.4

第二次作业 1.登陆数据库 2.创建数据库zoo 3.修改数据库zoo字符集为gbk 4.选择当前数据库为zoo 5.查看创建数据库zoo信息 6.删除数据库zoo C:\Windows\System32>mysql -uroot -p20040830Nmx mysql> create database zoo; alter database zoo character set gbk; mys…

Java springboot校园管理系统源码

Java springboot校园管理系统源码-014 下载地址&#xff1a;https://download.csdn.net/download/xiaohua1992/89364089 技术栈 运行环境&#xff1a;jdk8 tomcat9 mysql5.7 windows10 服务端技术&#xff1a;Spring Boot Mybatis VUE 使用说明 1.使用Navicati或者其它工…

VBA初学:零件成本统计之三(获取材料外协的金额)

第三步&#xff0c;从K3的数据库中获取金额 我这里是使用循环&#xff0c;通过任务单号将金额汇总出来&#xff0c;如果使用数组的话&#xff0c;还要按任务单写GROUP&#xff0c;还要去对应&#xff0c;不如循环直接一点 获取材料和外协金额的表格Sub getje()Dim rowcount A…

【JAVA入门】Day13 - 代码块

【JAVA入门】Day13 - 代码块 文章目录 【JAVA入门】Day13 - 代码块一、局部代码块二、构造代码块三、静态代码块 在 Java 中&#xff0c;两个大括号 { } 中间的部分叫一个代码块&#xff0c;代码块又分为&#xff1a;局部代码块、构造代码块、静态代码块三种。 一、局部代码块…

Linux应用---信号

写在前面&#xff1a;在前面的学习过程中&#xff0c;我们学习了进程间通信的管道以及内存映射的方式。这次我们介绍另外一种应用较为广泛的进程间通信的方式——信号。信号的内容比较多&#xff0c;是学习的重点&#xff0c;大家一定要认真学&#xff0c;多多思考。 一、信号概…

ASP.NET Core----基础学习01----HelloWorld---创建Blank空项目

文章目录 1. 创建新项目--方式一&#xff1a; blank2. 程序各文件介绍&#xff08;Project name &#xff1a;ASP.Net_Blank&#xff09;&#xff08;1&#xff09;launchSettings.json 启动方式的配置文件&#xff08;2&#xff09;appsettings.json 基础配置file参数的读取&a…

Vue 前端修改页面标题无需重新打包即可生效

在public文件夹下创建config.js文件 index.html页面修改 其他页面的标题都可以用window.title来引用就可以了&#xff01;