一文读懂Spring事件机制
Spring 事件机制是为同一ApplicationContext
中多个Bean
之间的通信而设计的,它是观察者模式的一个典型应用。
观察者模式在生活中随处可见,比如:在安装有交通信号灯的路口,行人需要实时观察信号灯的变化,从而判断是否需要前进、缓行或等待;而信号灯只负责发布信号变更事件,并不会干预行人,也就是说信号灯与行人之间是松耦合的关系。
为什么要搞清楚 Spring 事件机制呢?因为这玩意儿在 Spring 内部应用广泛,不搞清楚的话,看 Spring 相关源码会是一个拦路虎;此外,也可以在实际工作中解决相关问题,比如:笔者在工作中曾借助它来实现流程节点的自动流转,这些业务流程往往很长,但只有少数流程节点需要客户手动触发完成,其余节点需要自动流转。
1 从实战出发
聊到事件机制,那就不得不提事件处理模型三要素了,即事件源、事件和事件监听器。
1.1 定义事件
在 Spring 4.2 之前,所有事件均需要继承自ApplicationEvent
,而 ApplicationEvent 又继承自java.util.EventObject
;但在 Spring 4.2 之后,并不强制要求自定义事件继承 ApplicationEvent,因为 Spring 会将开发者自定义的事件类包装为一个PayloadApplicationEvent
,其实,PayloadApplicationEvent 同样继承自 ApplicationEvent。
1.2 定义事件监听器
// 事件监听器,即行人
@Component
@Slf4j
public class CustomTrafficSignalListener implements ApplicationListener<TrafficSignalEvent> {
@Override
public void onApplicationEvent(TrafficSignalEvent trafficSignalEvent) {
if (trafficSignalEvent instanceof RedSignalEvent) {
log.info("红灯亮,请所有行人和车辆停止前行");
} else if (trafficSignalEvent instanceof GreenSignalEvent) {
log.info("绿灯亮,请所有行人和车辆前行");
} else if (trafficSignalEvent instanceof YellowSignalEvent) {
log.info("黄灯亮,请所有行人和车辆缓行");
} else {
throw new IllegalStateException("未知的信号灯事件");
}
}
}
Spring 4.2 提供了org.springframework.context.event.EventListener
注解,不再需要实现org.springframework.context.ApplicationListener
接口。相信大家在实际工作中更倾向于使用@EventListener
的方式来定义事件监听器。
// 事件监听器,即行人
@Component
@Slf4j
public class FlexibleTrafficSignalListener {
@EventListener
public void onTrafficSignalEvent(TrafficSignalEvent event) {
if (event instanceof RedSignalEvent) {
log.info("红灯亮,请所有行人和车辆停止前行");
} else if (event instanceof GreenSignalEvent) {
log.info("绿灯亮,请所有行人和车辆前行");
} else if (event instanceof YellowSignalEvent) {
log.info("黄灯亮,请所有行人和车辆缓行");
} else {
throw new IllegalStateException("未知的信号灯事件");
}
}
}
1.3 定义事件源
// 交通信号灯,即事件源
@Component
public class TrafficSignal implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void doSignal() {
TrafficSignalEvent redSignalEvent = new RedSignalEvent(this);
applicationEventPublisher.publishEvent(redSignalEvent);
}
}
1.4 运行结果
CustomTrafficSignalListener
2021-08-16 20:59:19.728 INFO 11908 --- [main] p.d.e.e.CustomTrafficSignalListener : 红灯亮,请所有行人和车辆停止前行
FlexibleTrafficSignalListener
2021-08-16 20:59:22.135 INFO 17653 --- [main] p.d.e.e.FlexibleTrafficSignalListener : 红灯亮,请所有行人和车辆停止前行
2 异步事件监听器
默认情况下,事件监听器基于 同步机制 来处理事件,这意味着ApplicationEventPublisher
的publishEvent()
方法会阻塞,直到所有事件监听器都处理完毕。如果你想指定某一事件监听器基于 异步机制 来处理事件,可以使用@Async
注解。当然,也可以通过配置自定义的ApplicationEventMulticaster
使得所有事件监听器都基于异步机制处理事件。值得一提的是,全局异步监听所涉及的这一自定义ApplicationEventMulticaster
有个坑,即该Bean的名称必须是applicationEventMulticaster
,否则不会生效,具体原因下面会分析。
2.1 配置自定义事件广播器
@Configuration
public class ApplicationEventMulticasterConfig {
@Bean
public ApplicationEventMulticaster applicationEventMulticaster() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("event-pool-%d").build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
0,
16,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(24),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy()
);
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
simpleApplicationEventMulticaster.setTaskExecutor(threadPoolExecutor);
return simpleApplicationEventMulticaster;
}
}
2.2 运行结果
2021-08-16 22:05:50.053 INFO 13652 --- [event-pool-11] p.d.e.e.FlexibleTrafficSignalListener : 红灯亮,请所有行人和车辆停止前行
3 由Spring所发布的预定义事件
Spring 提供了一些预定义的事件,如下图所示:
但有些事件在 ApplicationContext 创建之前就已触发,该如何观测它们呢?
3.1 定义事件监听器
@Slf4j
public class SpringBuiltInEventsListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
log.info("event = {}", event);
}
}
3.2 向SpringApplication注册事件监听器
@SpringBootApplication(scanBasePackages = "pers.duxiaotou")
public class DuXiaoTouSpringAopApp {
public static void main(String[] args) throws Throwable {
SpringApplication springApplication = new SpringApplication(DuXiaoTouSpringAopApp.class);
springApplication.addListeners(new SpringBuiltInEventsListener());
springApplication.run(args);
}
}
3.3 运行结果
2021-08-18 17:14:43.182 INFO 15624 --- [ main] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:43.291 INFO 15624 --- [ main] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.context.event.ApplicationContextInitializedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:43.417 INFO 15624 --- [ main] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.context.event.ApplicationPreparedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:45.865 INFO 15624 --- [ event-pool-0] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@4ca6ee9b]
2021-08-18 17:14:45.872 INFO 15624 --- [ event-pool-0] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54d18072, started on Wed Aug 18 17:14:43 CST 2021]
2021-08-18 17:14:45.873 INFO 15624 --- [ event-pool-0] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:45.873 INFO 15624 --- [ event-pool-0] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54d18072, started on Wed Aug 18 17:14:43 CST 2021]
2021-08-18 17:14:45.875 INFO 15624 --- [ event-pool-1] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@2a5b3fee]
2021-08-18 17:14:45.875 INFO 15624 --- [ event-pool-1] p.d.e.e.SpringBuiltInEventsListener : event = org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@54d18072, started on Wed Aug 18 17:14:43 CST 2021]
4 实现原理
在 Spring 事件机制中,ApplicationEventPublisher
负责发布事件,其只定义了两个publishEvent()
方法,如下:
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
ApplicationEventPublisher 的实现类几乎都以ApplicationContext
结尾,这难道有什么玄机吗?Spring 事件机制是为同一ApplicationContext
中多个Bean
之间的通信而设计的,而 ApplicationContext 在 Spring 中扮演IoC容器
的角色,负责Bean全生命周期管理,那发布事件当然交由ApplicationContext最合适了啊;其中最为重要的非AbstractApplicationContext
莫属!废话不多说,上代码:
public abstract class AbstractApplicationContext
extends DefaultResourceLoader
implements ConfigurableApplicationContext {
// 事件广播器的名称必须指定为applicationEventMulticaster,源码中就是这么限定的
// 如果不按规矩来,那自定义的事件广播器屁用没有
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
// 事件广播器,负责向所有事件监听器广播事件
private ApplicationEventMulticaster applicationEventMulticaster;
private final Object startupShutdownMonitor = new Object();
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
@Override
public void publishEvent(Object event) {
publishEvent(event, null);
}
// 发布事件核心逻辑
protected void publishEvent(Object event, ResolvableType eventType) {
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
} else {
// 如果所发布的事件不是ApplicationEvent类型,那么默认将其包装为ApplicationEvent类型
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
// 通过事件广播器广播事件,广播给谁?事件监听器
this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);
}
/**
* 这个方法容易忽略,请重点关注以下两个方法,相信看了之后应该能解决你的疑惑···
* 1) initApplicationEventMulticaster()
* 2) registerListeners()
*/
@Override
public void refresh() {
synchronized (this.startupShutdownMonitor) {
// ...
// 初始化事件广播器
initApplicationEventMulticaster();
// 注册事件监听器
registerListeners();
// ...
}
}
// 初始化事件广播器
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
// 如果开发者自定义了名称为applicationEventMulticaster的事件广播器,则优先使用开发者自定义的
this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
} else {
// 如果开发者没有自定义或者定义了名称不是applicationEventMulticaster的事件广播器,则默认使用SimpleApplicationEventMulticaster
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
// 注册事件监听器
protected void registerListeners() {
// 将事件监听器注册到事件广播器中
// 但这里的事件监听器专指静态事件监听器,即META-INF下spring.factories配置文件中的事件监听器
// 比如:spring-boot、jasypt-spring-boot等模块中均配置了静态的事件监听器
for (ApplicationListener<?> listener : getApplicationListeners()) {
this.applicationEventMulticaster.addApplicationListener(listener);
}
// 同样是将事件监听器注册到事件广播器中
// 但这里主要指那些实现ApplicationListener接口的事件监听器哦
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
this.applicationEventMulticaster.addApplicationListenerBean(listenerBeanName);
}
}
}
从 AbstractApplicationContext 的源码来看,主要交代了两件事:
- AbstractApplicationContext 是实现事件发布的真正大佬,但发布事件并没有直接与事件监听器耦合,而是通过事件广播器来桥接;
- 当启动 Spring Boot 时会触发针对 ApplicationContext 的
refresh()
操作,而refresh()
首先会初始化事件广播器,然后注册事件监听器;注意:registerListeners()
中所涉及的getApplicationListeners()
方法其实只是直接拿到事件监听器列表而已,真正从spring.factories
配置文件中读取事件监听器的触发时机是在实例化SpringApplication
的时候。眼尖的朋友可能会问:由@EventListener
定义的事件监听器是如何注册到事件广播器中的呢?的确,这里不涉及基于注解模型的事件监听器的注册工作,这些事件监听器由EventListenerMethodProcessor
来完成注册工作,最终会将其封装为ApplicationListenerMethodAdapter
类型的监听器,但依然是ApplicationListener的子类。
接下来,自然要来认识一下ApplicationEventMulticaster
大佬了。事件广播器不仅负责广播事件,同时主导Spring和开发者所定义的事件监听器的维护工作。
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void addApplicationListenerBean(String listenerBeanName);
void removeApplicationListener(ApplicationListener<?> listener);
void removeApplicationListenerBean(String listenerBeanName);
void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);
void removeApplicationListenerBeans(Predicate<String> predicate);
void removeAllListeners();
void multicastEvent(ApplicationEvent event);
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
Spring 事件机制默认采用SimpleApplicationEventMulticaster
来作为事件广播器,其源码过于直白,这里就直接奉上源码不再解读了。
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
// 线程池,用于实现异步监听器
private Executor taskExecutor;
private ErrorHandler errorHandler;
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event);
Executor executor = getTaskExecutor();
// 事件广播器会遍历所有事件监听器,逐一向其发布事件
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
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 {
listener.onApplicationEvent(event);
} catch (ClassCastException ex) {
throw ex;
}
}
}
5 总结
6 参考文档
- https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-events
- https://refactoring.guru/design-patterns/observer