今天看啥  ›  专栏  ›  JayminAuthor

[Spring]Spring中的事件监听器模式

JayminAuthor  · 掘金  ·  · 2021-02-08 17:11
阅读 43

[Spring]Spring中的事件监听器模式

1. 事件监听器模式的重要因素

  • Event Object: 事件,事件源会将事件进行发布。Spring中的事件对象为ApplicationEvent.
  • Event Listener: 事件监听器,负责处理订阅的事件. Spring中对应的事件监听器接口为ApplicationListener.
  • Event Source: 事件源,负责发布事件并通知事件监听器。Spring中对应的事件源接口为ApplicationEventPublisher.

关于事件监听器模式,如果你不够熟悉,可以在我的上篇博客得到解答->点我前往

2. ApplicationEvent

2.1 接口清单

值得一提的是,Spring中的ApplicationEvent是内置事件源的,这意味着在监听器中可以获取事件源,而Spring中的事件源通常为容器本身.
同时,事件发生的同时,会记录当前系统时间戳

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened. */
	private final long timestamp;


	/**
	 * Create a new ApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})<br>
	 * 创建一个应用事件,其中source为事件源,并且不能为空.
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}


	/**
	 * Return the system time in milliseconds when the event happened.<br>
	 * 返回当前事件发生时的系统时间
	 */
	public final long getTimestamp() {
		return this.timestamp;
	}

}
复制代码

2.2 UML

UML

看类图可以知道,ApplicationEvent继承自JDK的EventObject,同时还有一个抽象子类ApplicationContextEvent,为什么要再抽象出这个子类呢,因为Object本身的含义太过广泛,Spring为了定义容器事件,强制约束source对象本身的类型为ApplicationContext.
最后从这个抽象类,衍生了一系列单一职责的事件。分别对应容器的关闭、刷新、启动、停止等阶段.

2.3 PayloadApplicationEvent

Spring 4.2 后推出一个一个基于泛型对事件进行包装的类,在此之前,发布的Event都必须继承自ApplicationEvent.加入Payload机制后,在发布事件的时候,可以传输任意的Object,Spring内部都会用PayloadApplicationEvent对事件进行包装.

具体的改进在org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object)可以做更加深入的了解。

default

3. ApplicationListener

3.1 接口清单

函数式接口,同时监听的事件需为继承自ApplicationEvent的类,如果Spring为4.2后的,可以不受这个限制,因为内部使用PayloadApplicationEvent进行了包装,在事件源发布事件时,会触发onApplicationEvent通知监听器。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 * 处理应用事件
	 */
	void onApplicationEvent(E event);

}
复制代码

3.2 UML

UML

  • SmartApplicationListener: 支持事件消费排序与事件类型匹配,Spring3.0开始支持,需要实现的方法为boolean supportsEventType(Class<? extends ApplicationEvent> eventType);,注意这里推断的对象是Class.
  • GenericApplicationListener: 支持事件消费排序与事件类型匹配,Spring4.2开始支持,需要实现的方法为 boolean supportsEventType(ResolvableType eventType);,注意这里推断的对象是ResolvableType.

3.3 注解支持-@EventListener

Spring4.2后,对事件监听器增加了注解的支持,无需实现接口,只需要通过@EventListener来直接在需要响应的方法上标注即可,配合Async@Order注解还可以支持异步与消费顺序声明.

注意,注解标注的方法上,最好声明为void,如果返回类型为数组或者集合,Spring会将每个元素作为新的事件进行发布。

4.ApplicationEventPublisher&ApplicationEventMulticaster

4.1 ApplicationEventPublisher

4.1.1 接口清单

通常在SpringIOC中,容器本身会作为ApplicationEventPublisher去进行事件的发布.
同时,开发者还可以通过Aware接口访问到ApplicationEventPublisher实例.来发布自定义事件.

@FunctionalInterface
public interface ApplicationEventPublisher {
    // 通知所有与此应用程序注册的匹配侦听器一个应用程序事件。
    // 事件可以是框架事件(例如ContextRefreshedEvent)或特定于应用程序的事件。
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

    // 通知所有与此应用程序注册的匹配侦听器事件。
    // 如果指定的事件不是ApplicationEvent,则将其包装在PayloadApplicationEvent中。
	void publishEvent(Object event);

}
复制代码
4.1.2 UML

UML

这里再次验证,容器即为IOC的ApplicationEventPublisher.

4.2 ApplicationEventMulticaster

Spring中的publisher只提供了发布事件的接口,然而一个事件监听器模式少不了注册监听器这件事情,ApplicationEventMulticaster就是为了解决这件事而产生的。

4.2.1 接口清单
public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);

	void removeApplicationListenerBean(String listenerBeanName);

	void removeAllListeners();

	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}
复制代码

从接口清单我们可以看到,ApplicationEventMulticaster支持添加监听器、移除监听器、发布事件。

4.2.2 UML

UML

  • AbstractApplicationEventMulticaster: 内部持有一个defaultRetriever成员变量,该变量为ListenerRetriever,其内部使用Set存储ApplicationListenerAbstractApplicationEventMulticaster添加监听器的操作为this.defaultRetriever.applicationListeners.add(listener);.有兴趣的读者可以自行扩展阅读.
  • SimpleApplicationEventMulticaster: 继承自AbstractApplicationEventMulticaster,内置private Executor taskExecutor;-任务执行器。默认情况下,监听器都以同步的方式进行,但是会由于个别监听器速度过慢,导致任务进度阻塞,因此该事件发布器也支持了以线程池来提交异步任务的方式消费事件.
	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);
			}
		}
	}
复制代码

4.3 为什么要同时定义ApplicationEventMulticaster和ApplicationEventPublisher?

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)中,我们可以看到,容器内部是怎么发送事件的.

	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,则使用PayloadApplicationEvent进行包装
			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 {
		    // 重点,可以看到publishEvent其实是通过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);
			}
		}
	}
复制代码

简单地说,就是ApplicationEventPublisher是一个简单的事件发布接口,只负责声明入口,而ApplicationEventMulticaster负责处理真正的事件发布逻辑。这其实是一种委托的思想.你可以在Spring随处发现这种现象,如Registry接口其实真正的执行者为DefaultListableBeanFactory.

总结

  • Spring中的事件监听器模式扩展自JDK中的事件监听器模型。
  • 事件在早期版本必须继承自ApplicationEvent,4.2后Spring引入了PayloadApplicationEvent进行兼容,支持以Object的形式发送事件.
  • 事件源在IOC中通常为容器本身.
  • 事件监听器支持实现接口和注解形式进行实现,同时可以使用@Order注解来指定消费顺序。
  • 避免在事件监听器中调用事件源的发布事件,引起循环引用的问题。
  • Spring以委托的思想,建立了事件发布者的接口视图->ApplicationEventPublisher,其中真正的执行者则为ApplicationEventMulticaster接口的实现类.
  • ApplicationEventMulticaster提供了异步的支持。



原文地址:访问原文地址
快照地址: 访问文章快照