Spring上下文启动的时候将实现ApplicationListener接口的Bean添加到事件监听者列表中,每次使用ApplicationEventPublisher发布ApplicationEvent时,都会通知对该事件感兴趣(监听该事件)的Bean。
ApplicationContext继承了ApplicationEventPublisher接口,从而拥有事件发布的能力。但是实际ApplicationContext事件发布委托给ApplicationEventMulticaster执行。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// ...省略
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// ...省略
}
相关关键类或注解
- ApplicationEventPublisher:发布事件
- ApplicationListener:事件监听者
- ApplicationEvent:事件
- EventListener:事件和事件监听绑定
- ApplicationEventMulticaster:发布事件
Spring容器在启动的过程中也会发布各种事件,相应的组件监听到之后完成各自的初始化工作,下面是Spring内置的事件。
- ContextStartedEvent:Spring上下文启动事件
- ContextRefreshedEvent:Spring上下文初始化或刷新事件
- ContextStoppedEvent:Spring上下文停止事件
- ContextClosedEvent:Spring上下文关闭事件
一、基于ApplicationListener实现事件监听
- 首先定一个事件,事件一定要继承ApplicationEvent
public class DemoEvent extends ApplicationEvent {
public DemoEvent(Object source) {
super(source);
}
}
- 然后定义一个类实现ApplicationListener接口
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("收到消息: " + event);
}
}
- 发布事件
applicationContext.publishEvent(new DemoEvent(new Object()));
// 收到消息: com.example.demo.applicationevent.DemoEvent[source=java.lang.Object@3cb173db]
虽然完成了事件监听,但是这种实现方案有点不太好,监听类必须实现特定的ApplicationListener接口,事件也必须继承ApplicationEvent类且一个类只能处理一个事件。接下来介绍第二种方式可以完全避免这种问题。
二、基于@EventListener实现事件监听
public class DemoEvent2 {
}
@Component
public class DemoListener2 {
@EventListener(DemoEvent2.class)
public void onDemoEvent(DemoEvent2 demoEvent2) {
System.out.println("@EventListener: " + demoEvent2);
}
}
// 输出日志
2020-04-28 23:52:59.187 INFO 4425 --- [ main] com.example.demo.DemoApplication : 发布DemoEvent2事件
2020-04-28 23:52:59.187 INFO 4425 --- [ main] c.e.demo.eventlistener.DemoListener2 : @EventListener: com.example.demo.eventlistener.DemoEvent2@75d7297d
基于@EventListener的监听类不需要实现ApplicationListener,事件也不需要继承ApplicationEvent类,且可以在类里面声明多个方法处理不同的事件。实际开发中更倾向于这种实现方案。
三、异步监听-@Async
从上面的输出日志中可以看出,事件发布和监听是处在同一个线程中,有时候我们可能需要实现异步监听,可以借助@Async和自定义ApplicationEventMulticaster两种方式实现消息的异步监听。
- @Async
修改代码如下
@Slf4j
@Component
@EnableAsync
public class DemoListener2 {
@Async
@EventListener(DemoEvent2.class)
public void onDemoEvent(DemoEvent2 demoEvent2) {
log.info("@EventListener: " + demoEvent2);
}
}
// 输出日志
2020-04-28 23:56:30.252 INFO 4508 --- [ main] com.example.demo.DemoApplication : 发布DemoEvent2事件
2020-04-28 23:56:30.310 INFO 4508 --- [ task-1] c.e.demo.eventlistener.DemoListener2 : @EventListener: com.example.demo.eventlistener.DemoEvent2@184bc84
添加注解启用异步,并在指定方法上面添加@Async注解。从日志中可以看出事件发布和监听已经处在两个不同的线程中。
- 自定义ApplicationEventMulticaster
@Slf4j
@Component
//@EnableAsync
public class DemoListener2 {
// @Async
@EventListener(DemoEvent2.class)
public void onDemoEvent(DemoEvent2 demoEvent2) {
log.info("@EventListener: " + demoEvent2);
}
@Bean("applicationEventMulticaster")
public ApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor("async-"));
// 异常处理
eventMulticaster.setErrorHandler(new ErrorHandler() {
@Override
public void handleError(Throwable t) {
log.error("事件监听异常", t);
}
});
return eventMulticaster;
}
}
// 输出日志
2020-04-29 00:01:27.733 INFO 4624 --- [ main] com.example.demo.DemoApplication : 发布DemoEvent2事件
2020-04-29 00:01:27.735 INFO 4624 --- [ async-16] c.e.demo.eventlistener.DemoListener2 : @EventListener: com.example.demo.eventlistener.DemoEvent2@56051dd7
从日志中可以看出,通过自定义ApplicationEventMulticaster方式也同样完成事件发布和监听处在两个不同的线程。
为什么声明一个这样的Bean就可以完成异步呢?
在开始的时候提到过ApplicationContext将事件发布委托给ApplicationEventMulticaster执行,接下来通过源码看ApplicationContext如何委托事件给ApplicationEventMulticaster。
AbstractApplicationContext获取ApplicationEventMulticaster对象
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
"call 'refresh' before multicasting events via the context: " + this);
}
return this.applicationEventMulticaster;
}
接下来看AbstractApplicationContext如何初始化applicationEventMulticaster对象
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
try {
// 省略无关代码
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
}
catch (BeansException ex) {
}
}
}
可以看出来在调用refresh的时候会初始化applicationEventMulticaster
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 {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(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() + "]");
}
}
}
初始化的是首先会尝试从Spring获取指定name的Bean,如果没有获取到,则新建一个实例,并注册到IoC容器中,到这里就明白为什么声明一个Bean之后就完成了异步的操作。因为我们提前声明了一个applicationEventMulticaster Bean对象,所以Spring会把这个对象当成默认的事件发布工具。自定义对象指定了线程池,所以事件发布和监听会处在不同的线程池中。
这种做法会导致由该对象发布的所有事件都是异步处理,实际开发过程中推荐使用@Async注解实现异步监听逻辑,这样可以针对性对指定事件监听异步处理。