Spring中的事件发布机制原理解析

 更新时间:2023年11月09日 08:29:17   作者:留兰香丶  
这篇文章主要介绍了Spring中的事件发布机制原理解析,当我们关心spring容器什么时候刷新,或者想在spring容器刷新的时候做一些事情,监听关心的事件,主要就是在ApplicationListener中写对应的事件,需要的朋友可以参考下

Spring事件发布机制原理

在 IoC 容器启动流程中有一个 finishRefresh 方法,具体实现如下:

	protected void finishRefresh() {
		clearResourceCaches();
		initLifecycleProcessor();
		getLifecycleProcessor().onRefresh();
		// 向所有监听 ContextRefreshedEvent 事件的监听者发布事件
		publishEvent(new ContextRefreshedEvent(this));
		LiveBeansView.registerApplicationContext(this);
	}

这里我们只关注 publishEvent 方法,这个方法用于发布 IoC 刷新完成事件,事件时如何发布的呢?下面我们一起来看一下。

一、原理分析

	@Override
	public void publishEvent(ApplicationEvent event) {
		publishEvent(event, null);
	}

	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");

		// Decorate event as an ApplicationEvent if necessary
		ApplicationEvent applicationEvent;
		// 根据事件类型进行包装
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		} else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
			}
		}

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		} else {
			// 获取 ApplicationEventMulticaster,将事件广播出去
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		// Publish event via parent context as well...
		// 判断是否存在父容器,如果存在则将事件也发布到父容器的监听者
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			} else {
				this.parent.publishEvent(event);
			}
		}
	}

通过上面方法可以看出 Spring 的事件是通过 ApplicationEventMulticaster 广播出去的,这个 ApplicationEventMulticaster 在 IoC 启动流程 initApplicationEventMulticaster 方法中初始化。如果该容器还存在父容器,那也会以同样的形式将事件发布给父容器的监听者。

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		// 解析事件类型
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		// 根据事件与事件类型获取所有监听者
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			// 获取异步执行器
			Executor executor = getTaskExecutor();
			if (executor != null) {
				// 如果执行器部位 null,则异步执行将事件发布给每一个监听者
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				// 同步发布事件
				invokeListener(listener, event);
			}
		}
	}

发布事件前会先获取所有已注册的监听器,而监听器早已在 IoC 启动流程的 registerListeners 方法中注册。获取到所有事件监听器之后,就可以进行事件发布了。发布的时候分为异步执行与顺序执行,默认情况下 executor 是没有初始化的,因此是顺序执行。

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

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			// 执行监听器的 onApplicationEvent 方法
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isTraceEnabled()) {
					logger.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

到这里 Spring 的事件通知机制流程就结束了,总的来说还是比较好理解的。

二、事件通知 demo

了解了事件通知机制的基本原理后,下面我们来写个 demo 体验一下监听器是如何使用的。

// 定义一个 Event
public class EventDemo extends ApplicationEvent {

    private static final long serialVersionUID = -8363050754445002832L;

    private String message;

    public EventDemo(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

// 定义一个监听器1
public class EventDemo1Listener implements ApplicationListener<EventDemo> {
    public void onApplicationEvent(EventDemo event) {
        System.out.println(this + " receiver " + event.getMessage());
    }
}

// 定义一个监听器2
public class EventDemo2Listener implements ApplicationListener<EventDemo> {
    public void onApplicationEvent(EventDemo event) {
        System.out.println(this + " receiver " + event.getMessage());
    }
}

// 定义一个事件发布者
public class EventDemoPublish {
    public void publish(ApplicationEventPublisher applicationEventPublisher, String message) {
        EventDemo eventDemo = new EventDemo(this, message);
        applicationEventPublisher.publishEvent(eventDemo);
    }
}

在 XML 中配置 bean:

    <bean id="eventDemoPublish" class="com.jas.mess.event.EventDemoPublish"/>
    <bean id="eventDemo1Listener" class="com.jas.mess.event.EventDemo1Listener"/>
    <bean id="eventDemo2Listener" class="com.jas.mess.event.EventDemo2Listener"/>

编写测试类:

    @Test
    public void eventTest() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);
        applicationContext.getBean("eventDemoPublish", EventDemoPublish.class).publish(applicationContext, "hello world");
    }

控制台输出:

在这里插入图片描述

不知道你有没有注意到,在发布事件的时候我们传的发布者是 applicationContext,applicationContext 本身继承自 ApplicationEventPublisher 接口,因此它本身也是一个事件发布者。

到此这篇关于Spring中的事件发布机制原理解析的文章就介绍到这了,更多相关Spring事件发布机制原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot 普通类调用Bean对象的一种方式推荐

    SpringBoot 普通类调用Bean对象的一种方式推荐

    这篇文章主要介绍了SpringBoot 普通类调用Bean对象的一种方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • jxls2.4.5如何动态导出excel表头与数据

    jxls2.4.5如何动态导出excel表头与数据

    这篇文章主要介绍了jxls2.4.5如何动态导出excel表头与数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • 利用Java简单实现一个代码行数统计器方法实例

    利用Java简单实现一个代码行数统计器方法实例

    这篇文章主要给大家介绍了关于如何利用Java简单实现一个代码行数统计器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • SpringCloud Config分布式配置中心使用教程介绍

    SpringCloud Config分布式配置中心使用教程介绍

    springcloud config是一个解决分布式系统的配置管理方案。它包含了 client和server两个部分,server端提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client端通过接口获取数据、并依据此数据初始化自己的应用
    2022-12-12
  • java中调用super的实例讲解

    java中调用super的实例讲解

    在本篇文章里小编给大家分享了一篇关于java中调用super的实例讲解内容,有兴趣的朋友们可以学习下。
    2020-12-12
  • Java springboot yaml语法注解

    Java springboot yaml语法注解

    这篇文章主要介绍了SpringBoot中的yaml语法及静态资源访问问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • java开发中嵌套类的详解及实例

    java开发中嵌套类的详解及实例

    这篇文章主要介绍了 java开发中嵌套类的详解及实例的相关资料,一般把定义内部类的外围类成为包装类(enclosing class)或者外部类,需要的朋友可以参考下
    2017-07-07
  • 关于RocketMQ使用事务消息

    关于RocketMQ使用事务消息

    RocketMQ是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生产、存储、消费全过程API的软件系统。消息即数据。一般消息的体量不会很大,需要的朋友可以参考下
    2023-05-05
  • 拦截器获取request的值之后,Controller拿不到值的解决

    拦截器获取request的值之后,Controller拿不到值的解决

    这篇文章主要介绍了拦截器获取request的值之后,Controller拿不到值的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • MyBatis-Plus 如何单元测试的实现

    MyBatis-Plus 如何单元测试的实现

    这篇文章主要介绍了MyBatis-Plus 如何单元测试的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08

最新评论