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

news2024/10/6 3:21:36

上一篇我们在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.coloradmin.cn/o/1904141.html

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

相关文章

谷粒商城学习笔记-使用renren-fast-vue框架时安装依赖包遇到的问题及解决策略

文章目录 1&#xff0c;npm error Class extends value undefined is not a constuctor or null2&#xff0c;npm warn cli npm v10.8.1 does not support Node.js v16.20.2.3&#xff0c;npm error code CERT_HAS_EXPIRED学习心得 这篇文章记录下使用renren-fast-vue&#xff…

花所Flower非小号排名20名下载花所Flower

1、Flower花所介绍 Flower花所是一家新兴的数字货币交易平台&#xff0c;致力于为全球用户提供安全、便捷的交易体验。平台以其强大的技术支持和丰富的交易产品闻名&#xff0c;为用户提供多样化的数字资产交易服务&#xff0c;涵盖了主流和新兴数字货币的交易需求。 2. Flowe…

SQL之delete、truncate和drop区别

MySQL删除数据的方式都有哪些&#xff1f; 常用的三种删除方式&#xff1a;通过 delete、truncate、drop 关键字进行删除&#xff1b;这三种都可以用来删除数据&#xff0c;但场景不同。 一、从执行速度上来说 drop > truncate >> DELETE;二、从原理上讲 1、DELET…

Guitar Pro8.2让你的吉他弹奏如虎添翼!

亲爱的音乐爱好者们&#xff0c;今天我要跟大家安利一个让我彻底沉迷其中的神器——Guitar Pro8.2&#xff01;这可不是一般的软件&#xff0c;它简直是吉他手们的福音。不管你是初学者还是老鸟&#xff0c;这个打谱软件都能给你带来前所未有的便利和价值。 让我们来聊聊Guita…

原理图设计工作平台:capture和capture CIS的区别在于有没有CIS模块

1环境:design entry CIS 2.参数设置命令options——preference&#xff08;7个选项卡colors/print&#xff0c;grid display&#xff0c;miscellaneous&#xff0c;pan and zoom&#xff0c;select&#xff0c;text editor和board simulation&#xff09; 1)颜色设置colors/p…

12 电商高并发缓存实战

序章 项目代码缓存的数据一致性 延时双删 淘汰缓存写数据库休眠1s,再次淘汰缓存缺点:如果mysql是主从复制,去从库中拿去数据,此时同步数据还未完成,拿到的数据是旧数据。 先更新 DB,后删除缓存 采用异步延时删除策略. ①利用消息队列进行删除的补偿。②Mysql 数据库更新操…

深入理解并发、线程与等待通知机制

目录 一、基础概念 进程和线程 进程 线程 Java 线程的无处不在 进程间的通信 进程间通信有几种方式&#xff1f; CPU 核心数和线程数的关系 上下文切换&#xff08;Context switch&#xff09; 并行和并发 二、认识 Java 里的线程 Java 程序天生就是多线程的 线程的…

python语句性能分析

1、for语句性能优于while import timeif __name__ __main__:start_time time.time()for i in range(10 ** 8):passend_time time.time()run_time end_time - start_timeprint(run_time)i 0start_time time.time()while i < 10 ** 8:i 1end_time time.time()run_tim…

【Spring Cloud】微服务的简单搭建

文章目录 &#x1f343;前言&#x1f384;开发环境安装&#x1f333;服务拆分的原则&#x1f6a9;单一职责原则&#x1f6a9;服务自治&#x1f6a9;单向依赖 &#x1f340;搭建案例介绍&#x1f334;数据准备&#x1f38b;工程搭建&#x1f6a9;构建父子工程&#x1f388;创建父…

Spring Boot的无缝衔接:深入解析与实践

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ &#x1f680;The begin&#x1f697;点点关注&#xff0c;收藏不迷路&#x1f6a9; 引言 在快速迭代的软件开发环境中&#xff0c;无缝衔接是提升开发效率、降低维护成本、增强系统稳定性的关键。Spring Boo…

嵌入式c语言5——位运算符

<<与>>是c语言中两个移位运算符&#xff0c;分别有乘以2与除以2的意义 位运算符还包括&#xff0c;与&&#xff0c;或|&#xff0c;均进行按位操作 同时&#xff0c;还可以进行取反以及异或操作

【C++】cout.self()函数

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 学习笔记&#xff0c;借鉴了部分大佬案例 &#x1f4e2;未来很长&#…

web前端开发——开发环境和基本知识

今天我来针对web前端开发讲解一些开发环境和基本知识 什么是前端 前端通常指的是网站或者Web应用中用户可以直接与之交互的部分&#xff0c;包括网站的结构、设计、内容和功能。它是软件开发中的一个专业术语&#xff0c;特别是指Web开发领域。前端开发涉及的主要技术包括HTML…

C++语言相关的常见面试题目(一)

1. const关键字的作用 答&#xff1a; 省流&#xff1a;&#xff08;1&#xff09;定义变量&#xff0c;主要为了防止修改 (2) 修饰函数参数&#xff1a;防止在函数内被改变 &#xff08;3&#xff09;修饰函数的返回值 &#xff08;4&#xff09;修饰类中的成员函数 2. Sta…

Windows远程桌面实现之十五:投射浏览器摄像头到xdisp_virt以及再次模拟摄像头(一)

by fanxiushu 2024-07-01 转载或引用请注明原始作者。 本文还是围绕xdisp_virt这个软件展开&#xff0c; 再次模拟成摄像头这个比较好理解&#xff0c;早在很久前&#xff0c;其实xdisp_virt项目中就有摄像头功能&#xff0c; 只是当时是分开的&#xff0c;使用起来…

centos docker 安装mysql:8.0.21 天坑记录

docker pull mysql:8.0.21 安装的mysql 8.0.21 版本&#xff0c;当创建表时只要创建表的字段大于10&#xff0c;就会报错 > 2013 - Lost connection to MySQL server during query 当删除一个字段&#xff0c;刚好9个字段时就可以创建成功&#xff0c;打印等于10个&#…

时间处理的未来:Java 8全新日期与时间API完全解析

文章目录 一、改进背景二、本地日期时间三、时区日期时间四、格式化 一、改进背景 Java 8针对时间处理进行了全面的改进&#xff0c;重新设计了所有日期时间、日历及时区相关的 API。并把它们都统一放置在 java.time 包和子包下。 Java5的不足之处&#xff1a; 非线程安全&…

Nginx auth 的权限验证

基本流程 整个流程为&#xff1b;以用户视角访问API开始&#xff0c;进入 Nginx 的 auth 认证模块&#xff0c;调用 SpringBoot 提供的认证服务。根据认证结果调用重定向到对应的 API 接口或者 404 页面。 查看版本保证有 Nginx auth 模块 由于 OpenAI 或者本身自己训练的一套…

实现多数相加,但是传的参不固定

一、情景 一般实现的加法和减法等简单的相加减函数的话。一般都是写好固定传的参数。比如&#xff1a; function add(a,b) {return a b;} 这是固定的传入俩个&#xff0c;如果是三个呢&#xff0c;有人说当然好办&#xff01; 这样写不就行了&#xff01; function add(a…

前端JS特效第22波:jQuery滑动手风琴内容切换特效

jQuery滑动手风琴内容切换特效&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xm…