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

【1】事件、发布与监听

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

① 事件

如下图所示,事件继承自​​EventObject​​​类,该类维护了事件最初发生在其上的对象-source。而我们通常自定义的事件实际应继承自抽象类​​ApplicationEvent​​​。比如常见的上下文刷新事件​​ContextRefreshedEvent​​​。
Spring中事件监听(通知)机制详解与实践_spring
比如我们可以自定义事件类如下:

// 定义一个事件
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);

/**
* Create a new {@code ApplicationListener} for the given payload consumer.
*/
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) {
// 这里写事件处理逻辑
}
}

该种方式将会被包装为一个​​ApplicationListenerMethodAdapter​​​,类似如下:
Spring中事件监听(通知)机制详解与实践_ide_02

这是因为​​EventListenerMethodProcessor​​​ 处理器会解析​​@EventListener​​​ 注解,将其所在类封装为一个​​ApplicationListener​​​(比如这里的​​ApplicationListenerMethodAdapter​​),然后放入容器中。

常见的ApplicationListener树结构示意图
Spring中事件监听(通知)机制详解与实践_监听器_03

③ 事件发布

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

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

publishEvent方法解释如下:

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

该接口不需要我们去实现,实际上如下图所示所有的容器都实现了该接口:
Spring中事件监听(通知)机制详解与实践_监听器_04
那么我们该如何做呢?只需要注入​​​ApplicationEventPublisher​​ 然后发布即可,如下所示:

@Autowired
ApplicationEventPublisher applicationEventPublisher;

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

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

【2】事件广播

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

这个逻辑是在​​AbstractApplicationContext​​的​​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
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​​方法的实现。这里我们来到了​​SimpleApplicationEventMulticaster​​的​​multicastEven​​方法。

@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,则会额外记录处理步骤;
  • ⑤ 否则,使用当前主线程直接调用监听器;

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
}

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

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

如下是其方法示意图:
Spring中事件监听(通知)机制详解与实践_事件监听机制_06
其内部类​​​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();
}
}
}

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

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

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;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}

方法逻辑主要分为三块:

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