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功能接口供用户实现,如下所示:
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:
// 定义一个事件监听者
public class MyEventListener implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent event) {
// 这里写事件处理逻辑
}
}
如下所示使用注解@EventListener
:
// 定义一个事件监听者
public class MyEventListener {
public void onApplicationEvent(MyEvent event) {
// 这里写事件处理逻辑
}
}
该种方式将会被包装为一个ApplicationListenerMethodAdapter
,类似如下:
这是因为EventListenerMethodProcessor
处理器会解析@EventListener
注解,将其所在类封装为一个ApplicationListener
(比如这里的ApplicationListenerMethodAdapter
),然后放入容器中。
常见的ApplicationListener树结构示意图
③ 事件发布
有了事件与监听 ,那么还需要在某个时刻将事件广播出去触发监听动作。如何发布事件呢?Spring提供了ApplicationEventPublisher接口。
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
publishEvent方法解释如下:
- 将此事件通知给所有注册到容器的并且匹配的监听器,事件可以是框架事件(例如ContextRefreshedEvent)或特定于应用程序的事件。
- 这样的事件发布步骤实际是一种对于multicaster 的协调手段,其并不意味着同步、异步或者立即执行。
- 对于那些需要长时间处理或者可能阻塞的操作,事件监听器被鼓励使用异步处理
该接口不需要我们去实现,实际上如下图所示所有的容器都实现了该接口:
那么我们该如何做呢?只需要注入ApplicationEventPublisher
然后发布即可,如下所示:
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, 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
方法。
public void multicastEvent(final ApplicationEvent event, 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
({"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最终是反射调用你的监听器(方法)。
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");
}
}
}
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, 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( Executor taskExecutor) {
this.taskExecutor = taskExecutor;
}
Spring提供了两个任务执行器供你使用:
- 同步执行器
org.springframework.core.task.SyncTaskExecutor
- 异步执行器
org.springframework.core.task.SimpleAsyncTaskExecutor
核心属性
// 任务执行器,可以使监听器不在主线程中执行
private Executor taskExecutor;
// 错误处理器
private ErrorHandler errorHandler;
// 应用启动记录
private ApplicationStartup applicationStartup;
ApplicationStartup 这个玩意很有一起,主要是用来标记/记录程序处理步骤。核心容器及其基础结构组件可以使用ApplicationStartup 标记应用程序启动期间的步骤,并收集有关执行上下文或其处理时间的数据。
事件广播
这个其实在上面我们已经分析过了,这里可以再看下源码。简单来说该方法就是解析事件类型、尝试获取任务执行器,然后调用父类的getApplicationListeners
方法获取监听器进行遍历循环调用invokeListener(listener, event);
。
public void multicastEvent(final ApplicationEvent event, 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
方法。
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
如果不为空就广播出去