17. 事件 Event
17.1 Java 事件 - 监听器模型
事件 - 监听器模型 是观察者模式的扩展
- 被观察的对象(消息发送者) - Observable
- 观察者 - Observer
- 事件对象 - EventObject
- 事件监听器 - EventListener
17.2 面向接口的事件 - 监听器设计模式
17.3 面向注解的事件 - 监听器设计模式
17.4 Spring 标准事件 ApplicationEvent
17.5 基于接口的 Spring 事件监听器
Java 标准事件监听器 EventListener 扩展
- 实现接口 - ApplicationListener
- 设计特点 - 单一类型事件处理
- 处理方法 - onApplicationEvent(ApplicationEvent)
- 事件类型 - ApplicationEvent
ApplicationListener 接口的源码如下所示
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
创建 ApplicationListener 监听器需要以下 3 步:
- 实现 ApplicationListener 接口,其中的泛型表示监听的事件类型
- 重写
onApplicationEvent方法 - 将监听器添加到 applicationContext 中
这样在 Spring 应用上下文启动和关闭时,会发送事件,回调该方法。
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new GenericApplicationContext();
// 添加监听器
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("接收到spring事件: " + event);
}
});
applicationContext.refresh();
applicationContext.close();
}
输出结果,接收到两个事件,应用上下文启动事件 ContextRefreshedEvent,应用上下文关闭事件 ContextClosedEvent
接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.GenericApplicationContext@300ffa5d, started on Fri May 28 03:41:45 CST 2021]
接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.support.GenericApplicationContext@300ffa5d, started on Fri May 28 03:41:45 CST 2021]
17.6 基于注解的 Spring 事件监听器
- 监听器注解 - @EventListener
- 注解目标 - 方法
- 配合 @Async + @EnableAsync 可以实现异步监听
- 配合 @Order 可以控制顺序
@EventListener 只需要标记方法,就可以起到实现 ApplicationListener 接口重写方法一样的作用,在触发事件后,会回调该方法
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(EventListenerDemo.class);
// 方法一: 实现ApplicationListener, 创建监听器
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("ApplicationListener - 接收到spring事件: " + event);
}
});
applicationContext.refresh();
applicationContext.close();
}
// 方法二: 注解标记响应方法, 创建监听器
@EventListener
public void remind(ApplicationEvent event) {
System.out.println("@EventListener - 接收到spring事件: " + event);
}
输出结果
@EventListener - 接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 03:53:14 CST 2021]
ApplicationListener - 接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 03:53:14 CST 2021]
@EventListener - 接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 03:53:14 CST 2021]
ApplicationListener - 接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 03:53:14 CST 2021]
@EventListener 还可以监听指定事件,并且配合 @Async 注解,可以实现异步监听。
@EnableAsync // 激活注解 @Async
public class EventListenerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(EventListenerDemo.class);
// 方法一: 实现ApplicationListener, 创建监听器
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
println("ApplicationListener - 接收到spring事件: " + event);
}
});
applicationContext.refresh();
applicationContext.close();
}
// 方法二: 注解标记响应方法, 创建监听器
@EventListener
public void remind(ApplicationEvent event) {
println("@EventListener - 接收到spring事件: " + event);
}
// 只监听spring关闭 close 事件
@EventListener
public void remind(ContextClosedEvent event) {
println("@EventListener - 接收到spring [close]事件: " + event);
}
@EventListener
@Async // 异步
public void remind(ContextRefreshedEvent event) {
println("@EventListener - 接收到spring [refresh-async]事件: " + event);
}
public static void println(Object printable) {
System.out.printf("[线程: %s]: %s\n", Thread.currentThread().getName(), printable);
}
}
输出结果,异步的使用 Spring 创建的线程进行输出,标记的只监听 close 事件的方法,也接收到了事件。
[线程: SimpleAsyncTaskExecutor-1]: @EventListener - 接收到spring [refresh-async]事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:03:01 CST 2021]
[线程: main]: @EventListener - 接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:03:01 CST 2021]
[线程: main]: ApplicationListener - 接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:03:01 CST 2021]
[线程: main]: @EventListener - 接收到spring [close]事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:03:01 CST 2021]
[线程: main]: @EventListener - 接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:03:01 CST 2021]
[线程: main]: ApplicationListener - 接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:03:01 CST 2021]
17.7 注册 Spring ApplicationListener
- 将 ApplicationListener 作为 Spring bean 注册
- 通过 ConfigurableApplicationContext#addApplicationListener 注册
- @EventListener 会自动将监听器注册到容器
- ApplicationListener 监听器注册到容器示例:
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ApplicationListenerRegisterDemo.class);
// 方法一: 通过 ConfigurableApplicationContext#addApplicationListener 注册监听器
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("ApplicationListener - 接收到spring事件: " + event);
}
});
// 通过注册 bean 的方式注册监听器
applicationContext.register(MyApplicationListener.class);
applicationContext.refresh();
applicationContext.close();
}
static class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("MyApplicationListener - 接收到spring事件: " + event);
}
}
输出结果,可以看到,两种方式注册的事件监听器 ApplicationListener 都生效了
ApplicationListener - 接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:19:09 CST 2021]
MyApplicationListener - 接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:19:09 CST 2021]
ApplicationListener - 接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:19:09 CST 2021]
MyApplicationListener - 接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2e817b38, started on Fri May 28 04:19:09 CST 2021]
- 监听器注册源码分析,向容器注册监听器,调用的
addApplicationListener()源码如下所示,会将注册的监听器添加到 applicationEventMulticaster。发布事件时,会获取所有监听器,然后通知所有关注了该事件类型的监听器。@EventListener 标记的监听器也会注册到容器,只不过监听器是由 Spring 创建的对象并注册到容器的,最终也是调用下面的方法注册。
// 代码块2
AbstractApplicationContext.java
// 应用上下文中保存了一个广播器
private ApplicationEventMulticaster applicationEventMulticaster;
// 将注册的监听器添加到applicationEventMulticaster
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
if (this.applicationEventMulticaster != null) {
// 向广播器中添加监听器
this.applicationEventMulticaster.addApplicationListener(listener);
}
this.applicationListeners.add(listener);
}
17.8 Spring 事件发布器
- 通过 ApplicationEventPublisher 发布 Spring 事件
- 依赖注入
- 通过 ApplicationEventMulticaster 发布 Spring 事件
- 依赖注入
- 依赖查找
ApplicationEventPublisher 源码如下所示
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
ApplicationEventMulticaster 源码如下所示
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void addApplicationListenerBean(String listenerBeanName);
void removeApplicationListener(ApplicationListener<?> listener);
void removeApplicationListenerBean(String listenerBeanName);
void removeAllListeners();
void multicastEvent(ApplicationEvent event);
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
- 通过实现 ApplicationEventPublisherAware 接口,获取 ApplicationEventPublisher 并发布 Spring 事件
- 实现 ApplicationEventPublisherAware 接口
- 注册监听器
- 创建事件 ApplicationEvent,通过
applicationEventPublisher.publishEvent()发布事件
public class ApplicationEventPublisherDemo implements ApplicationEventPublisherAware {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ApplicationEventPublisherDemo.class);
// 添加监听器
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("接收到spring事件: " + event);
}
});
applicationContext.refresh();
applicationContext.close();
}
/**
* 重写ApplicationEventPublisherAware的方法
* 使用 ApplicationEventPublisher 发布事件
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
// 发布事件
applicationEventPublisher.publishEvent(new ApplicationEvent("hello world") {
});
// 发布事件, 重载方法, 发布的是 Payload 事件
applicationEventPublisher.publishEvent("hello listener...");
}
}
输出结果,可以看到监听到了我们发布的 Spring 事件 hello world
接收到spring事件: org.geekbang.event.ApplicationEventPublisherDemo$2[source=hello world]
接收到spring事件: org.springframework.context.PayloadApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1a6c5a9e, started on Fri May 28 04:41:25 CST 2021]
接收到spring事件: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1a6c5a9e, started on Fri May 28 04:41:25 CST 2021]
接收到spring事件: org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@1a6c5a9e, started on Fri May 28 04:41:25 CST 2021]
- 通过实现 @Autowired 依赖注入 ApplicationEventPublisher,然后发布事件
- 使用 @Autowired 依赖注入 ApplicationEventPublisher
- 使用 @Listener 注册监听器,这一步可替换为
applicationContext.addApplicationListener()注册监听器 - 通过
applicationEventPublisher.publishEvent()发布 Payload 事件
public class ApplicationEventPublisherDemo2{
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ApplicationEventPublisherDemo2.class);
applicationContext.refresh();
ApplicationEventPublisherDemo2 bean = applicationContext.getBean(ApplicationEventPublisherDemo2.class);
// 获取依赖注入的 ApplicationEventPublisher
ApplicationEventPublisher applicationEventPublisher = bean.applicationEventPublisher;
// 发布 payload 事件
applicationEventPublisher.publishEvent("hi listener...");
}
// 注册监听器
@EventListener
public void onApplicationEvent(PayloadApplicationEvent event){
System.out.println("接收到spring事件: " + event.getPayload());
}
}
这种方式与上一步唯一的区别就是获取 ApplicationEventPublisher 的方式不同,上一种是通过事件 Aware 接口,这一种是通过 @Autowired 依赖注入,至于创建注册监听器的方式,发布事件的类型,上下两个代码块都可以进行替换。
输出结果
接收到spring事件: hi listener...
17.9 Spring 层次性上下文事件传播
创建一个父容器,创建一个子容器,然后注册监听器,查看事件的传播机制。
public static void main(String[] args) {
// 1. 创建 patent Spring 应用上下文
AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
parentContext.setId("parent-context");
// 注册监听器
parentContext.register(MyListener.class);
// 2. 创建 current Spring 应用上下文
AnnotationConfigApplicationContext currentContext = new AnnotationConfigApplicationContext();
currentContext.setId("current-context");
// 3. 让 current 继承 parent
currentContext.setParent(parentContext);
// 注册监听器
currentContext.register(MyListener.class);
// 4. 启动两个应用上下文
parentContext.refresh();
currentContext.refresh();
}
static class MyListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.printf("监听到 Spring 应用上下文[ID : %s] 的 ContextRefreshedEvent 事件\n",
event.getApplicationContext().getId());
}
}
输出结果,第一条是 parent 容器 refresh 时触发的事件,第二条是 current 容器 refresh 时触发的事件,第三条是 current 容器触发事件后,还会进行层次性查找父容器,再在父容器中发布一次事件。
监听到 Spring 应用上下文[ID : parent-context] 的 ContextRefreshedEvent 事件
监听到 Spring 应用上下文[ID : current-context] 的 ContextRefreshedEvent 事件
监听到 Spring 应用上下文[ID : current-context] 的 ContextRefreshedEvent 事件
源码分析,在发布事件时,若容器存在父容器,会在父容器中再发布一次。
AbstractApplicationContext.java
// 代码块1
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
ApplicationEvent applicationEvent;
// 如果事件是ApplicationEvent类及其子类, 强转为ApplicationEvent
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
} else {
// 如果不是, 则创建一个Payload事件
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 发布事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// 如果存在父容器, 则在父容器中也要发布一次事件
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
17.10 Spring 内建事件
Spring 内建事件 ApplicationContextEvent 的 4 大派生事件
- ContextRefreshedEvent - Spring 应用上下文就绪事件
- ContextStartedEvent - Spring 应用上下文启动事件
- ContextStopedEvent - Spring 应用上下文停止事件
- ContextClosedEvent - Spring 应用上下文关闭事件
AbstractApplicationContext 类实现了 ApplicationEventPublisher 接口,重写了 publishEvent() 方法来发布事件,在应用上下文 refresh() 完毕会发布 ContextRefreshedEvent 事件,源码如下所示:
AbstractApplicationContext.java
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
// 该方法会在refresh() 最后一步被调用
protected void finishRefresh() {
clearResourceCaches();
initLifecycleProcessor();
getLifecycleProcessor().onRefresh();
// 发布ContextRefreshedEvent事件
publishEvent(new ContextRefreshedEvent(this));
LiveBeansView.registerApplicationContext(this);
}
@Override
public void start() {
getLifecycleProcessor().start();
// 发布ContextStartedEvent事件
publishEvent(new ContextStartedEvent(this));
}
@Override
public void stop() {
getLifecycleProcessor().stop();
// 发布ContextStoppedEvent事件
publishEvent(new ContextStoppedEvent(this));
}
protected void doClose() {
if (this.active.get() && this.closed.compareAndSet(false, true)) {
try {
// 发布ContextClosedEvent事件
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// ......
}
}
17.11 Payload 事件
Payload 事件是为了简化 Spring 事件发送,更关注事件内容,比如发送一段字符串,发送一个对象,都可以使用 Payload 事件,如名称所示,Payload 是负责运载发送的事件的。
- 发送方法 - ApplicationEventPublisher#publishEvent(Object object)
public class PayLoadEventDemo implements ApplicationEventPublisherAware {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(PayLoadEventDemo.class);
// 添加监听器
applicationContext.addApplicationListener(
(ApplicationListener<PayloadApplicationEvent>) event
-> System.out.println("接收到spring事件: " + event.getPayload())
);
applicationContext.refresh();
applicationContext.close();
}
// 实现ApplicationEventPublisherAware的方法, 发布事件
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
// 发布 Payload 事件
applicationEventPublisher.publishEvent("hello ...");
}
}
输出结果
接收到spring事件: hello ...
17.12 自定义 Spring 事件
- 自定义 Spring 事件
public class MySpringEvent extends ApplicationEvent {
public MySpringEvent(Object source) {
super(source);
}
}
- 自定义监听器
public class MySpringEventListener implements ApplicationListener<MySpringEvent> {
@Override
public void onApplicationEvent(MySpringEvent event) {
System.out.println("监听到事件 " + event);
}
}
- 发布自定义事件
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.addApplicationListener(new MySpringEventListener());
applicationContext.refresh();
// 发布事件
applicationContext.publishEvent(new MySpringEvent("hello world..."));
}
输出结果
监听到事件 org.geekbang.event.MySpringEvent[source=hello world...]
17.13 依赖注入 ApplicationEventPublisher
依赖注入 ApplicationEventPublisher 有两种方式
- 通过 @Autowired 依赖注入 ApplicationEventPublisher
- 通过实现 ApplicationEventPublisherAware 接口,回调获取 ApplicationEventPublisher
通过 @Autowired 依赖注入 ApplicationEventPublisher 的代码示例
public class ApplicationEventPublisherDemo2{
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ApplicationEventPublisherDemo2.class);
applicationContext.refresh();
ApplicationEventPublisherDemo2 bean = applicationContext.getBean(ApplicationEventPublisherDemo2.class);
// 获取依赖注入的 ApplicationEventPublisher
ApplicationEventPublisher applicationEventPublisher = bean.applicationEventPublisher;
// 发布 payload 事件
applicationEventPublisher.publishEvent("hi listener...");
}
// 注册监听器
@EventListener
public void onApplicationEvent(PayloadApplicationEvent event){
System.out.println("接收到spring事件: " + event.getPayload());
}
}
- 通过实现 ApplicationEventPublisherAware 接口,回调获取 ApplicationEventPublisher
public class ApplicationEventPublisherDemo implements ApplicationEventPublisherAware {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ApplicationEventPublisherDemo.class);
// 添加监听器
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("接收到spring事件: " + event);
}
});
applicationContext.refresh();
applicationContext.close();
}
/**
* 重写ApplicationEventPublisherAware的方法
* 使用 ApplicationEventPublisher 发布事件
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
// 发布事件
applicationEventPublisher.publishEvent(new ApplicationEvent("hello world") {
});
// 发布事件, 重载方法, 发布的是 Payload 事件
applicationEventPublisher.publishEvent("hello listener...");
}
}
实现 Aware 接口获取 ApplicationEventPublisher 的原理在生命周期章节已经讲过,再回顾一下,
// 重写的BeanPostProcessor接口的方法,该方法会在所有bean初始化前回调
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 只对着6种接口的实现类bean进行处理
if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){
return bean;
}
if (acc != null) {
// 跳过
}
else {
// 调用bean重写的Aware方法
invokeAwareInterfaces(bean);
}
return bean;
}
// 调用Aware接口的实现类重写的方法
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
// 若bean实现了ApplicationEventPublisherAware接口
if (bean instanceof ApplicationEventPublisherAware) {
// 回调重写的setApplicationEventPublisher()方法
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
17.14 依赖查找 ApplicationEventMulticaster
依赖查找 ApplicationEventMulticaster 的条件
- Bean 名称 - applicationEventMulticaster
- Bean 类型 - ApplicationEventMulticaster
Spring 框架在启动refresh()时会调用下面的方法,将广播器进行初始化。
AbstractApplicationContext.java
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 当前容器存在bean applicationEventMulticaster,则依赖查找该bean
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
}else {
// 当前容器不存在bean applicationEventMulticaster,创建一个Multicaster注册到容器
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
源码分析,前面章节都会向容器注册监听器,调用的 addApplicationListener() 源码如下所示,会将注册的监听器添加到 applicationEventMulticaster。发布事件时,会获取所有监听器,然后通知所有关注了该事件类型的监听器。
// 代码块2
AbstractApplicationContext.java
// 应用上下文中保存了一个广播器
private ApplicationEventMulticaster applicationEventMulticaster;
// 将注册的监听器添加到applicationEventMulticaster
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
if (this.applicationEventMulticaster != null) {
// 向广播器中添加监听器
this.applicationEventMulticaster.addApplicationListener(listener);
}
this.applicationListeners.add(listener);
}
17.15 ApplicationEventPublisher 事件广播
- 接口 - ApplicationEventMulticaster
- 抽象类 - AbstractApplicationEventMulticaster
- 实现类 - SimpleApplicationEventMulticaster
SpringBoot 和 SpringCloud 都使用了 SimpleApplicationEventMulticaster 来广播事件,
17.16 同步和异步事件广播
1. 基于接口发布异步事件
Spring 的同步和异步事件都是基于 SimpleApplicationEventMulticaster
- 模式切换 - setTaskExecutor(java.util.concurrent.Executor) 方法
- 默认模式:同比
- 异步模式:
- 设计缺陷:非基于接口的契约编程
- 先创建一个监听器,用于监听广播器发布的事件,并作出响应
public class MySpringEventListener implements ApplicationListener<MySpringEvent> {
@Override
public void onApplicationEvent(MySpringEvent event) {
System.out.printf("线程[%s]监听到事件 %s", Thread.currentThread().getName(), event);
}
}
- 使用 Spring 广播器 ApplicationEventMulticaster 发布异步事件,需要提供一个线程池来执行异步操作,下面的代码演示了如何为 Spring 广播器设置线程池,用于异步发布事件。
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
// 注册监听器
applicationContext.addApplicationListener(new MySpringEventListener());
applicationContext.refresh();
// 获取广播器, 将广播器设置为异步模式
ApplicationEventMulticaster multicaster = applicationContext.getBean(
AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
ApplicationEventMulticaster.class);
// 设置为异步广播事件, 设置后发布事件的线程就不再是main线程了
if (multicaster instanceof SimpleApplicationEventMulticaster) {
SimpleApplicationEventMulticaster simpleMulticaster = (SimpleApplicationEventMulticaster) multicaster;
ExecutorService executor = Executors.newSingleThreadExecutor();
// (重点)为广播器设置用于执行异步任务的线程池taskExecutor
simpleMulticaster.setTaskExecutor(executor);
// 在spring容器关闭时, 停止线程池, 否则程序无法正常退出
simpleMulticaster.addApplicationListener((ApplicationListener<ContextClosedEvent>) event -> {
if (!executor.isShutdown()) {
executor.shutdown();
}
});
}
// 发布事件
applicationContext.publishEvent(new MySpringEvent("hello world..."));
applicationContext.close();
}
输出结果,第 1 行表示监听到了事件,而且是异步线程监听到事件并作出了响应,第 2 行表示程序正常退出。如果代码中没有手动关闭线程池,那么第 2 行不会显示,程序也不会停止。
线程[pool-1-thread-1]监听到事件 org.geekbang.event.MySpringEvent[source=hello world...]
Process finished with exit code 0
- 源码分析,示例代码中使用应用上下文 applicationContext 发布了事件,实际上是获取应用上下文中的 applicationEventMulticaster,然后广播事件,源码如下所示
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// 判断事件类型,若不是ApplicationEvent类型, 则构建为Payload类型
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// (重点) 获取Spring广播器,广播事件
// 见下方代码块
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// 省略向父容器中发布事件
}
- 源码分析,applicationEventMulticaster 广播事件分为两种模式:
- 同步模式,若未对广播器设置
setTaskExecutor()执行异步任务的线程池 executor,则认为是同步模式,发布事件会直接回调监听器 - 异步模式,示例代码中对广播器设置了
setTaskExecutor()执行异步任务的线程池 executor,因此发布事件,会使用线程池来回调监听器,达到异步发布的效果 - 回调监听器,都是回调的实现了 ApplicationListener 接口的
onApplicationEvent()方法
- 同步模式,若未对广播器设置
SimpleApplicationEventMulticaster.java
// 用于执行异步任务的线程池
private Executor taskExecutor;
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 解析事件event的类型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取用于异步监听的线程池,实例代码中手动设置了, setTaskExecutor()
Executor executor = getTaskExecutor();
// 遍历监听器, 根据事件类型获取关注该事件类型的监听器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 异步事件,设置了executor
if (executor != null) {
// 将任务提交给线程池executor执行,
// 好处是回调监听器方法如果阻塞了,也不影响下面的代码执行
// 回调监听器监听方法,见doInvokeListener()
executor.execute(() -> invokeListener(listener, event));
} else {
// 同步事件,未设置executor,
// 回调监听器监听方法,见doInvokeListener()
invokeListener(listener, event);
}
}
}
// 回调监听器的onApplicationEvent()方法
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 回调监听器重写的onApplicationEvent()方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
//.....
}
}
2. 基于注解发布异步事件
-
基于注解 @EventListener
-
默认为同步,使用 @Async + @EnableAsync 切换为异步
-
好处:不需要实现接口和注册监听器
- 基于注解发布异步事件示例代码如下,
@EnableAsync
public class AnnotatedAsyncEventHandlerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AnnotatedAsyncEventHandlerDemo.class);
applicationContext.refresh();
// 发布事件
applicationContext.publishEvent(new MySpringEvent("hello world..."));
applicationContext.close();
}
@EventListener
@Async
public void onApplicationEvent(MySpringEvent event) {
System.out.printf("线程[%s]监听到事件 %s", Thread.currentThread().getName(), event);
}
@Bean
public Executor taskExecutor() {
return Executors.newSingleThreadExecutor();
}
}
输出结果,如果示例代码中没有手动创建 taskExecutor bean,那么就会打印日志说明,如果创建了 taskExecutor bean,则只输出第 3 行内容。
五月 29, 2021 12:54:59 上午 org.springframework.aop.interceptor.AsyncExecutionAspectSupport getDefaultExecutor
信息: No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either
线程[pool-1-thread-1]监听到事件 org.geekbang.event.MySpringEvent[source=hello world...]
- 源码分析,发布异步事件前,会先获取线程池,如果未手动设置线程池,则会创建一个默认线程池 SimpleAsyncTaskExecutor。
AsyncExecutionInterceptor.java
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
// 获取用于异步执行的线程池, 见下个代码块
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
// 若未获得线程池,则创建一个线程池
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
- 源码分析,获取用于执行异步任务的线程池,会从 Spring 容器中查找
AsyncExecutionAspectSupport.java
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
try {
// 根据类型依赖查找
return beanFactory.getBean(TaskExecutor.class);
}
catch (NoUniqueBeanDefinitionException ex) {
try {
// 存在多个TaskExecutor,则根据名称"taskExecutor"再查找
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}catch (NoSuchBeanDefinitionException ex2) {
if (logger.isInfoEnabled()) {
logger.info("More than one TaskExecutor bean found within the context, and none is named " +
"'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +
"as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
}
}
}
catch (NoSuchBeanDefinitionException ex) {
try {
// 容器中不存在TaskExecutor,则查找名称为"taskExecutor"类型为Executor的bean
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}catch (NoSuchBeanDefinitionException ex2) {
// 打印日志,示例代码中打印的信息
logger.info("No task executor bean found for async processing: " +
"no bean of type TaskExecutor and no bean named 'taskExecutor' either");
}
}
}
return null;
}
- @EventListener 如何注册到容器?
Spring 框架在启动时,肯定会将 @EventListener 标记的方法所在类,创建为 bean 对象并注册到容器,然后进行初始化,在初始化完成时,会回调 SmartInitializingSingleton#afterSingletonsInstantiated 方法,然后调用下面的方法 processBean(),创建一个监听器 ApplicationListenerMethodAdapter,并注册到 Spring 应用上下文。
EventListenerMethodProcessor 实现了 SmartInitializingSingleton 接口,重写了 afterSingletonsInstantiated 方法,在方法中调用了下面的方法 processBean(),在初始化完成时会回调该方法,具体细节见 Bean 生命周期章节 9.14 Bean 初始化完成
EventListenerMethodProcessor.java
// 这里肯定是处理的示例代码类, 类型targetType为AnnotatedAsyncEventHandlerDemo.class
private void processBean(final String beanName, final Class<?> targetType) {
// 目标类中存在@EventListener注解
if (!this.nonAnnotatedClasses.contains(targetType) &&
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
!isSpringContainerClass(targetType)) {
Map<Method, EventListener> annotatedMethods = null;
try {
// 获取所有被@EventListener标记的方法
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
} catch (Throwable ex) {}
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
}else {
ConfigurableApplicationContext context = this.applicationContext;
List<EventListenerFactory> factories = this.eventListenerFactories;
// 遍历被@EventListener标记的方法
for (Method method : annotatedMethods.keySet()) {
// 这里只有一个DefaultEventListenerFactory
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
// 创建一个ApplicationListenerMethodAdapter,封装了bean名称,回调方法
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// (重点)将创建的监听器注册到容器,后面发布事件时,会获取监听器并通知
context.addApplicationListener(applicationListener);
break;
}
}
}
}
}
}
17.17 事件异常处理
Spring 错误处理接口 - ErrorHandler
- 使用场景
- Spring 事件 - ApplicationEventMulticaster
- Spring 本地调度 - ConcurrentTaskScheduler,ThreadPoolTaskScheduler
- 错误处理器的作用是,当发生异常时,程序不会终止。示例代码为广播器设置了错误处理器,当监听器响应时发生了异常,ErrorHandler 则会进行处理,Spring 应用上下文正常运行,不会因为发生了异常而终止了程序。
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
// 注册监听器
applicationContext.addApplicationListener((ApplicationListener<PayloadApplicationEvent>) event -> {
System.out.println(event.getPayload());
throw new RuntimeException("自定义异常...");
});
ApplicationEventMulticaster multicaster = applicationContext.getBean(
AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
ApplicationEventMulticaster.class);
SimpleApplicationEventMulticaster simpleMulticaster = (SimpleApplicationEventMulticaster) multicaster;
// 为广播器设置错误处理器
simpleMulticaster.setErrorHandler(e -> {
System.err.println(e.getMessage());
});
// 发布事件
applicationContext.publishEvent("hello world...");
System.out.println("applicationContext 正常运行....");
applicationContext.close();
}
输出结果,发布事件后监听到了,打印事件信息,发生了异常,程序正常运行,ErrorHandler 处理异常打印异常信息
hello world...
applicationContext 正常运行....
自定义异常...
- 源码分析,广播器发布事件后,回调监听器的事件处理方法,若处理方法发生了异常,会 catch 住并交给 ErrorHandler 进行处理,即回调自定义的 ErrorHandler 的异常处理方法。
SimpleApplicationEventMulticaster.java
// 错误处理器
private ErrorHandler errorHandler;
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
// 获取ErrorHandler
ErrorHandler errorHandler = getErrorHandler();
// 当设置了ErrorHandler,若回调监听器方法时出现了异常,则catch并交给ErrorHandler处理
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}catch (Throwable err) {
// 回调ErrorHandler错误处理方法
errorHandler.handleError(err);
}
}else {
// 若未设置ErrorHandler,若回调监听器方法时出现了异常,则终止程序
doInvokeListener(listener, event);
}
}
17.18 事件 - 监听器实现原理
17.19 SpringBoot SpringCloud 中的事件
- SpringBoot 中的事件
| 事件类型 | 触发时机 |
|---|---|
| ApplicationStartingEvent | 当 SpringBoot 应用启动时 |
| ApplicationStartedEvent | 当 SpringBoot 应用已启动完成 |
| ApplicationEnvironmentPreparedEvent | 当 SpringBoot Environment 实例已准备完成 |
| ApplicationPreparedEvent | 当 SpringBoot 应用准备好时 |
| ApplicationReadyEvent | 当 SpringBoot 应用完全可用 |
| ApplicationFailedEvent | 当 SpringBoot 应用启动失败 |
- SpringCloud 中的事件
| 事件类型 | 触发时机 |
|---|---|
| EnvironmentChangeEvent | 当 Environment 实例配置属性发生变化时 |
| HearbeatEvent | 当 DiscoveryClient 客户端发送心跳时 |
| InstancePreRegisteredEvent | 当服务实例注册前 |
| InstanceRegisteredEvent | 当服务实例注册后 |
| RefreshEvent | 当 RefreshEndpoint 被调用时 |
| RefreshScopeRefreshedEvent | 当 Refresh Scope Bean 刷新后 |
SpringBoot 和 SpringCloud 中的事件都是集成了 ApplicationEvent,与前面我们自定义事件的方式一致,发布事件也是通过 ApplicationEventPublisher.publishEvent() 发布事件。
17.20 面试题
-
Spring 事件的核心接口和组件
答:1. Spring 事件 - ApplicationEvent
-
Spring 事件监听器 - ApplicationListener
-
Spring 事件发布器 - ApplicationEventPublisher
-
Spring 事件广播器 - ApplicationEventMulticaster
-
-
Spring 同步事件和异步事件处理的使用场景
答:Spring 同步事件,Spring 中经常使用,如容器启动事件 ContextRefreshedEvent
Spring 异步事件,主要是 @EventListener 和 @Async 配合,实现异步处理,不阻塞主线程,比如耗时较长的数据计算任务等。
-
@EventListener 的工作原理
答:参考 17.16.2.4 章节,
- 注册监听器:Spring 框架在 bean 初始化完成时,会回调方法,创建监听器并注册到容器
- 回调 @EventListener 标记的方法:当事件发布后,会通知所有关注该事件类型的监听器,然后回调监听器方法