Spring中事件监听(通知)机制详解与实践

关联博文:
Spring中事件监听(通知)机制详解与实践
SpringBoot中事件广播体系梳理
SpringBoot启动过程广播的事件有什么作用?

Spring中事件监听(也有说法叫事件通知)机制,其实本质是观察者模式的应用。当某个事件发生时,其会被广播出去,监听该实践的listener就会被触发然后执行响应的动作。该模式可良好应用于程序解耦,类似消息的发布订阅。

【1】事件、发布与监听

这个模式有三元素:事件、发布与监听。

① 事件

如下图所示,事件继承自EventObject类,该类维护了事件最初发生在其上的对象-source。而我们通常自定义的事件实际应继承自抽象类ApplicationEvent。比如常见的上下文刷新事件ContextRefreshedEvent
在这里插入图片描述
比如我们可以自定义事件类如下:

// 定义一个事件
public class MyEvent extends ApplicationEvent {
    private String message;
    public EventDemo(Object source, String message) {
        super(source);
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

② 监听

有了事件后,就需要有对应的监听对象–其实就是观察者。接口EventListener是一个顶级接口提供给其他监听器接口继承,如下所示其是一个空接口。

public interface EventListener {
}

针对ApplicationEvent事件而言,Spring提供了ApplicationListener功能接口供用户实现,如下所示:

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

	 // 处理监听到的事件
	void onApplicationEvent(E event);

	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}

}

那么我们自定义监听该如何实现呢?有两种方式:继承自ApplicationListener或者使用注解@EventListener

如下所示继承ApplicationListener:

// 定义一个事件监听者
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        // 这里写事件处理逻辑
    }
}

如下所示使用注解@EventListener

// 定义一个事件监听者
@Component
public class MyEventListener  {
    @EventListener
    public void onApplicationEvent(MyEvent event) {
        // 这里写事件处理逻辑
    }
}

使用注解 @EventListener该种方式将会被包装为一个ApplicationListenerMethodAdapter,类似如下:
在这里插入图片描述

这是因为EventListenerMethodProcessor 处理器会解析@EventListener 注解,将其所在类封装为一个ApplicationListener(比如这里的ApplicationListenerMethodAdapter),然后放入容器中。EventListenerMethodProcessor实现了SmartInitializingSingletonApplicationContextAware以及BeanFactoryPostProcessorj接口,有兴趣的可以看下其源码。

常见的ApplicationListener树结构示意图
在这里插入图片描述

③ 事件发布

有了事件与监听 ,那么还需要在某个时刻将事件广播出去触发监听动作。如何发布事件呢?Spring提供了ApplicationEventPublisher接口。

@FunctionalInterface
public interface ApplicationEventPublisher {
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}
	void publishEvent(Object event);
}

publishEvent方法解释如下:

  • 将此事件通知给所有注册到容器的并且匹配的监听器,事件可以是框架事件(例如ContextRefreshedEvent)或特定于应用程序的事件。
  • 这样的事件发布步骤实际是一种对于multicaster 的协调手段,其并不意味着同步、异步或者立即执行。
  • 对于那些需要长时间处理或者可能阻塞的操作,事件监听器被鼓励使用异步处理

该接口不需要我们去实现,实际上如下图所示所有的容器都实现了该接口:
在这里插入图片描述
那么我们该如何做呢?只需要注入ApplicationEventPublisher 然后发布即可,如下所示:

@Autowired
ApplicationEventPublisher applicationEventPublisher;

public void publishEvent(){
    MyEvent event = new MyEvent(this);
    applicationEventPublisher.publishEvent(event);
}

如何使用异步处理呢?使用@EnableAsync与@Async注解。

【2】事件广播

publishEvent方法会将我们的事件通知给监听器,这个场景叫做广播。也就是说,将该事件广播出去,但凡对该事件感兴趣的监听器均被通知到。spring是如何实现的呢?

① publishEvent

这个逻辑是在AbstractApplicationContextpublishEvent中实现的。 也就是说AbstractApplicationContext实现了ApplicationEventPublisher 接口的publishEvent方法哦。

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
	//如果早期事件不为null,则将事件放入早期事件集合中--说明广播器还没有实例化好
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
	// 获取广播器进行事件广播
		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);
		}
	}
}

上述方法首先对event进行了处理,尝试转换为ApplicationEvent或者PayloadApplicationEvent,如果是PayloadApplicationEvent则获取eventType

其次判断earlyApplicationEvents是否为空(也就是早期事件还没有被发布-说明广播器还没有实例化好),如果不为空则将当前事件放入否则获取ApplicationEventMulticaster调用其multicastEvent将事件广播出去。本文这里获取到的广播器实例是SimpleApplicationEventMulticaster

最后如果其parent不为null,则尝试调用父类的publishEvent方法。


② multicastEvent

我们继续看下multicastEvent方法的实现。这里我们来到了SimpleApplicationEventMulticastermulticastEven方法。

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 解析事件类型
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	// 尝试获取任务执行器
	Executor executor = getTaskExecutor();
	
	// 获取合适的ApplicationListener,循环调用监听器的onApplicationEvent方法
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		// 判断executor 是否不为null
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		// 判断applicationStartup 
		else if (this.applicationStartup != null) {
			StartupStep invocationStep = this.applicationStartup.start("spring.event.invoke-listener");
			invokeListener(listener, event);
			invocationStep.tag("event", event::toString);
			if (eventType != null) {
				invocationStep.tag("eventType", eventType::toString);
			}
			invocationStep.tag("listener", listener::toString);
			invocationStep.end();
		}
		// 否则,直接调用listener.onApplicationEvent
		else {
			invokeListener(listener, event);
		}
	}
}

代码逻辑分析如下:

  • ① 获取事件类型与TaskExecutor;
  • ② getApplicationListeners获取合适的监听器,也就是对当前事件类型感兴趣的;然后进行遍历
    • ③ 如果executor不为null,则交给executor去调用监听器;
    • ④ 如果applicationStartup不为null,则会额外记录处理步骤;
    • ⑤ 否则,使用当前主线程直接调用监听器;

getApplicationListeners如何获取合适的监听器?如果监听器是GenericApplicationListener则触发其supportsEventTypesupportsSourceType方法,否则使用GenericApplicationListenerAdapter包装然后判断其 是SmartApplicationListener则根据其supportsEventType方法判断,否则根据监听器实现的接口泛型来判断。

// AbstractApplicationEventMulticaster
protected boolean supportsEvent(
		ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {

	GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
			(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
	return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

// GenericApplicationListenerAdapter
public boolean supportsEventType(ResolvableType eventType) {
	if (this.delegate instanceof SmartApplicationListener) {
		Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
		return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
	}
	else {
		return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
	}
}

③ invokeListener

invokeListener做了什么呢?我们继续往下看。

// 该方法增加了错误处理逻辑,然后调用doInvokeListener
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);
	}
}

// doInvokeListener 直接调用listener.onApplicationEvent
@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
	//...
	}
}

两个方法流程逻辑很清晰,关键的问题是listener.onApplicationEvent(event);是直接调用你的监听器方法吗?非也,要看此时的listener是什么。比如当你使用注解@EventListener声明监听器的时候,这里的listener就是ApplicationListenerMethodAdapter实例。

ApplicationListenerMethodAdapter为例,其onApplicationEvent方法会调用processEvent方法最终调用Object result = doInvoke(args);而doInvoke最终是反射调用你的监听器(方法)。

@Override
public void onApplicationEvent(ApplicationEvent event) {
	processEvent(event);
}
public void processEvent(ApplicationEvent event) {
	Object[] args = resolveArguments(event);
	if (shouldHandle(event, args)) {
		Object result = doInvoke(args);
		if (result != null) {
			handleResult(result);
		}
		else {
			logger.trace("No result object given - no result to handle");
		}
	}
}

@Nullable
protected Object doInvoke(Object... args) {
// 获取包装的bean,也就是目标bean,也就是你的监听器
	Object bean = getTargetBean();
	// Detect package-protected NullBean instance through equals(null) check
	if (bean.equals(null)) {
		return null;
	}
	// 设置方法可以访问
	ReflectionUtils.makeAccessible(this.method);
	try {
	// 反射调用目标方法
		return this.method.invoke(bean, args);
	}
	//...一堆catch
}

在这里插入图片描述

【3】广播器ApplicationEventMulticaster

这里我们主要分析ApplicationEventMulticaster接口、抽象类AbstractApplicationEventMulticaster以及具体实现SimpleApplicationEventMulticaster

① ApplicationEventMulticaster

该接口实现将会管理一系列ApplicationListener并发布事件给监听器。

我们看下该接口源码,如下所示该接口提供了添加/移除监听器以及广播事件给监听器的行为。

public interface ApplicationEventMulticaster {

	 // 添加监听器
	void addApplicationListener(ApplicationListener<?> listener);

	 // 添加一个监听器 beanName
	void addApplicationListenerBean(String listenerBeanName);

	 // 从通知列表移除掉一个监听器
	void removeApplicationListener(ApplicationListener<?> listener);


	 // 从通知列表移除掉一个 监听器 bean name
	void removeApplicationListenerBean(String listenerBeanName);

	 // 移除掉该广播器管理的所有监听器
	void removeAllListeners();

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
	 * if possible as it provides better support for generics-based events.
	 * @param event the event to multicast
	 */
	 // 广播事件给合适的监听器 建议使用末尾方法其对泛型提供了更好支持
	void multicastEvent(ApplicationEvent event);

// 广播事件给合适的监听器,如果eventType为null,将会根据event 实例构建一个默认的type
	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

② AbstractApplicationEventMulticaster

该抽象类实现了ApplicationEventMulticaster接口,提供了基础的监听器注册/移除以及查找能力。

其中defaultRetriever 用来管理监听器并进行查找,而retrieverCache 则是为了更快进行查找。

private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();

final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);

如下是其方法示意图:
在这里插入图片描述
其内部类DefaultListenerRetriever维护了两个常量集合用来保存监听器与监听器 bean Name。Set表示集合内部元素不可重复。

public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

抽象类并未提供广播事件的功能,留给子类SimpleApplicationEventMulticaster实现。

③ SimpleApplicationEventMulticaster

默认情况下,该广播器将会把事件广播给所有的监听器,让监听器自己忽略他们不感兴趣的事件。默认情况下,所有监听器将会在当前线程中会调用。这允许恶意侦听器阻塞整个应用程序的危险,但会增加最小的开销。指定额外的任务执行器TaskExecutor,使监听器在不同线程中执行,例如从线程池执行,将是一个良好的方案。

setTaskExecutor方法允许你实例化SimpleApplicationEventMulticaster时,指定额外的任务执行器。这样监听器将不会在当前被调用的线程中执行。

public void setTaskExecutor(@Nullable Executor taskExecutor) {
	this.taskExecutor = taskExecutor;
}

Spring提供了两个任务执行器供你使用:

  • 同步执行器 org.springframework.core.task.SyncTaskExecutor
  • 异步执行器 org.springframework.core.task.SimpleAsyncTaskExecutor

核心属性

// 任务执行器,可以使监听器不在主线程中执行
@Nullable
private Executor taskExecutor;

// 错误处理器
@Nullable
private ErrorHandler errorHandler;

// 应用启动记录
@Nullable
private ApplicationStartup applicationStartup;

ApplicationStartup 这个玩意很有一起,主要是用来标记/记录程序处理步骤。核心容器及其基础结构组件可以使用ApplicationStartup 标记应用程序启动期间的步骤,并收集有关执行上下文或其处理时间的数据。

事件广播

这个其实在上面我们已经分析过了,这里可以再看下源码。简单来说该方法就是解析事件类型、尝试获取任务执行器,然后调用父类的getApplicationListeners方法获取监听器进行遍历循环调用invokeListener(listener, event);

@Override
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 if (this.applicationStartup != null) {
			StartupStep invocationStep = this.applicationStartup.start("spring.event.invoke-listener");
			invokeListener(listener, event);
			invocationStep.tag("event", event::toString);
			if (eventType != null) {
				invocationStep.tag("eventType", eventType::toString);
			}
			invocationStep.tag("listener", listener::toString);
			invocationStep.end();
		}
		else {
			invokeListener(listener, event);
		}
	}
}

【4】广播器与监听器的注册

其实这里要说的是什么时候广播器被初始化?什么时候监听器被注册到了容器。这个发生在spring容器的初始化过程中,确切地说是SpringMVC容器还是IOC容器要看你当前项目环境是什么。这里我们重点不在于这里,我们看如下AbstractApplicationContext.refresh方法。

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			beanPostProcess.end();

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			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();
			contextRefresh.end();
		}
	}
}

这里我们主要看initApplicationEventMulticasterregisterListeners();方法。前者就是初始化事件广播器,后置则是注册监听。

① initApplicationEventMulticaster

源码如下所示,如果beanFactory有bean applicationEventMulticaster则获取该bean实例。否则就实例化一个SimpleApplicationEventMulticaster 实例作为applicationEventMulticaster 并调用beanFactory.registerSingleton注册。

public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME 
= "applicationEventMulticaster";

protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		simpleApplicationEventMulticaster.setApplicationStartup(getApplicationStartup());
		this.applicationEventMulticaster = simpleApplicationEventMulticaster;

		// 将事件广播器作为单例bean注册到BeanFactory中
		beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
		if (logger.isTraceEnabled()) {
			logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
					"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
		}
	}
}

② registerListeners

实例化完事件广播器后,触发了registerListeners。这里会搜集ApplicationListener交给广播器实例。

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

	// 这里将earlyApplicationEvents 置为了null!
	this.earlyApplicationEvents = null;
	if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
		for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
			getApplicationEventMulticaster().multicastEvent(earlyEvent);
		}
	}
}

方法逻辑主要分为三块:

  • getApplicationListeners获取监听器然后注册到广播器中;
  • getBeanNamesForType获取bean Name数组String[] listenerBeanNames 然后注册到广播器中;
  • ③ 处理以前的事件,先将earlyApplicationEvents 赋予null,然后判断earlyEventsToProcess 如果不为空就广播出去
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值