夯实Spring系列|第二十二章:Spring 事件(Events)
文章目录
- 夯实Spring系列|第二十二章:Spring 事件(Events)
- 前言
- 1.项目环境
- 2.Java 事件/监听编程模型
- 3.面向接口的事件/监听器设计模式
- 4.面向注解的事件/监听器设计模式
- 5.Spring 标准事件-ApplicationEvent
- 6.基于接口的 Spring 事件监听器
- 7.基于注解的 Spring 事件监听器
- 8.注册 Spring ApplicationListener
- 9.Spring 事件发布器
- 10.Spring 层次性上下文事件传播
- 11.Spring 内建事件
- 12.Spring 4.2 PayLoad 事件
- 13.自定义 Spring 事件
- 14.依赖注入 ApplicationEventPublisher
- 15.依赖查找 ApplicationEventMulticaster
- 16.ApplicationEventPublisher 底层实现
- 17.同步和异步 Spring 事件广播
- 18.Spring 4.1 事件异常处理
- 19.Spring 事件/监听器实现原理
- 20.Spring Boot 事件
- 21.Spring Cloud 事件
- 22.面试
- 23.参考
前言
本章主要是了解 Spring Event 事件底层实现的相关细节以及一些重要的使用场景。
1.项目环境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模块:event
2.Java 事件/监听编程模型
设计模式 - 观察者模式扩展
- 可观察对象(消息发送者)- java.util.Observable
- 观察者 - java.util.Observer
标准化接口
- 事件对象 - java.util.EventObject
- 事件监听器 - java.util.EventListener
示例:
public class ObservableDemo {
public static void main(String[] args) {
EventObservable observable = new EventObservable();
// 添加观察者(监听者)
observable.addObserver(new EventObserver());
// 手动设置发布消息(事件)
observable.notifyObservers("Hello,World");
}
static class EventObservable extends Observable {
public void setChanged() {
super.setChanged();// 必须通过手动设置的方式修改 changed 状态位
}
public void notifyObservers(Object obj) {
setChanged();
super.notifyObservers(new EventObject(obj));
clearChanged();
}
}
static class EventObserver implements Observer {
@Override
public void update(Observable o, Object event) {
EventObject eventObject = (EventObject) event;
System.out.println("收到事件:" + eventObject);
}
}
}
执行结果:
收到事件:java.util.EventObject[source=Hello,World]
3.面向接口的事件/监听器设计模式
Java 技术规范 | 事件接口 | 监听器接口 |
---|---|---|
JavaBeans | java.beans.PropertyChangeEvent | java.beans.PropertyChangeListener |
Java AWT | java.awt.event.MouseEvent | java.awt.event.MouseListener |
Java Swing | javax.swing.event.MenuEvent | javax.swing.event.MenuListener |
Java Preference | java.util.prefs.PreferenceChangeEvent | java.util.prefs.PreferenceChangeListener |
基本模式(约定俗成):
-
所有的 Event 都会继承 EventObject
-
所有的 Listener 都会继承 EventListener
4.面向注解的事件/监听器设计模式
扩展视野,看一看 JavaEE 以及 Java 里面一些相关事件/监听注解
Java 技术规范 | 事件注解 | 监听器注解 |
---|---|---|
Servlet 3.0+ | @javax.servlet.annotation.WebListener | |
JPA 1.0+ | @javax.persistence.PostPersist | |
Java Common | @PostConstruct | |
EJB 3.0+ | @javax.ejb.PrePassivate | |
JSF 2.0+ | @javax.face.event.ListenerFor |
5.Spring 标准事件-ApplicationEvent
Java 标准事件 java.util.EventObject 扩展
- 扩展特性:事件发生事件戳
Spring 应用上下文 ApplicationEvent 扩展 - ApplicationContextEvent
- Spring 应用上下文(ApplicationContext)作为事件源
- 具体实现
- org.springframework.context.event.ContextClosedEvent
- org.springframework.context.event.ContextRefreshedEvent
- org.springframework.context.event.ContextStartedEvent
- org.springframework.context.event.ContextStoppedEvent
ApplicationEvent 源码
- 继承了 EventObject 基础上,增加了 timestamp 时间戳
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationContextEvent 源码
- 通过构造方法将 ApplicationContext 应用上下文传入到 ApplicationEvent 中,方便使用
public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
6.基于接口的 Spring 事件监听器
Java 标准事件监听器 java.util.EventListener 扩展
- 扩展接口 - org.springframework.context.ApplicationListener
- 设计特点:单一类型事件处理
- 处理方法:onApplicationEvent(ApplicationEvent)
- 事件类型:org.springframework.context.ApplicationEvent
示例
public class ApplicationListenerDemo {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 向 Spring 应用上下文中注册时间
context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("接收到 Spring 事件:" + event);
}
});
context.refresh(); // 对应事件 ContextRefreshedEvent
context.start();// 对应事件 ContextStartedEvent
context.stop();// 对应事件 ContextStoppedEvent
context.close();// 对应事件 ContextClosedEvent
}
}
执行结果:
接收到 Spring 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
接收到 Spring 事件:org.springframework.context.event.ContextStartedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
接收到 Spring 事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
接收到 Spring 事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.support.GenericApplicationContext@129a8472, started on Thu Jun 18 21:03:40 CST 2020]
7.基于注解的 Spring 事件监听器
Spring 注解 - @org.springframework.context.event.EventListener
特性 | 说明 |
---|---|
设计特点 | 支持多 ApplicationEvent 类型无需接口约束 |
注解目标 | 方法 |
是否支持异步执行 | 支持 |
是否支持泛型类型事件 | 支持 |
是否支持顺序控制 | 支持,配合 @Order 注解控制 |
示例
- 接收所有类型
@EventListener
public void onApplicationEvent(ApplicationEvent event){
System.out.println("@EventListener - 接收到 Spring 事件:" + event);
}
- 具体事件类型
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event){
System.out.println("@EventListener - 接收到 [ContextRefreshedEvent] 事件:" + event);
}
@EventListener
public void onApplicationEvent(ContextStartedEvent event){
System.out.println("@EventListener - 接收到 [ContextStartedEvent] 事件:" + event);
}
@EventListener
public void onApplicationEvent(ContextStoppedEvent event){
System.out.println("@EventListener - 接收到 [ContextStoppedEvent] 事件:" + event);
}
@EventListener
public void onApplicationEvent(ContextClosedEvent event){
System.out.println("@EventListener - 接收到 [ContextClosedEvent] 事件:" + event);
}
- 异步执行
@EventListener
@Async
public void onApplicationEvent(ContextStoppedEvent event){
System.out.println("@EventListener(Async 异步) - 接收到 [ContextStoppedEvent] 事件:" + event);
}
执行结果:
@EventListener(Async 异步) - 接收到 [ContextStoppedEvent] 事件:org.springframework.context.event.ContextStoppedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2752f6e2, started on Thu Jun 18 21:28:25 CST 2020]
- 顺序控制
@EventListener
@Order(2)
public void onApplicationEvent(ContextRefreshedEvent event){
System.out.println("@EventListener(onApplicationEvent) - 接收到 [ContextRefreshedEvent] 事件:" + event);
}
@EventListener
@Order(1)
public void onApplicationEvent1(ContextRefreshedEvent event){
System.out.println("@EventListener(onApplicationEvent1) - 接收到 [ContextRefreshedEvent] 事件:" + event);
}
执行结果:
@EventListener(onApplicationEvent1) - 接收到 [ContextRefreshedEvent] 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2752f6e2, started on Thu Jun 18 21:32:43 CST 2020]
@EventListener(onApplicationEvent) - 接收到 [ContextRefreshedEvent] 事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2752f6e2, started on Thu Jun 18 21:32:43 CST 2020]
8.注册 Spring ApplicationListener
方法一:通过 ConfigurableApplicationContext API 注册
// 方法一:通过 ConfigurableApplicationContext API 注册
context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("ApplicationListener - 接收到 Spring 事件:" + event);
}
});
方法二:ApplicationListener 作为 Spring Bean 注册
// 方法二:基于 ApplicationListener 注册为 Spring Bean
context.register(MyApplicationListener.class);
...
/**
* 这里的泛型设计可以指定具体类型,或者父类
*/
static class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent>{
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("MyApplicationListener - 接收到 Spring 事件:" + event);
}
}
9.Spring 事件发布器
方法一:通过 ApplicationEventPublisher 发布 Spring 事件
- 获取 ApplicationEventPublisher
- 依赖注入
方法二:通过 ApplicationEventMulticaster 发布 Spring 事件
- 获取 ApplicationEventMulticaster
- 依赖注入
- 依赖查找
14 依赖注入 ApplicationEventPublisher - ApplicationEventPublisher 示例
17.同步和异步 Spring 事件广播 - ApplicationEventMulticaster 示例
10.Spring 层次性上下文事件传播
发生场景说明
当 Spring 应用出现多层次 Spring 应用上下文(ApplicationContext)时,如 Spring WebMVC、Spring Boot、SpringCloud 场景下,由子 ApplicationContext 发起 Spring 事件可能会传递到其 Parent ApplicationContext(直到 Root)的过程。
如何避免
- 定位 Spring 事件源(ApplicationContext)进行过滤处理
10.1 示例
public class HierarchicalSpringEventPropagateDemo {
public static void main(String[] args) {
// 1.创建 parent Spring 应用上下文
AnnotationConfigApplicationContext parentApplicationContext = new AnnotationConfigApplicationContext();
parentApplicationContext.setId("parent-context");
// 2.创建 current Spring 应用上下文
AnnotationConfigApplicationContext currentApplicationContext = new AnnotationConfigApplicationContext();
currentApplicationContext.setId("current-context");
// 3.current -> parent
currentApplicationContext.setParent(parentApplicationContext);
// 将 MyListener 注册应用上下文
// currentApplicationContext.addApplicationListener(new MyListener());
parentApplicationContext.register(MyListener.class);
currentApplicationContext.register(MyListener.class);
// 启动应用上下文
parentApplicationContext.refresh();
currentApplicationContext.refresh();
}
static class MyListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
System.out.printf("上下文[id:%s]接收到事件:[%s]\n", applicationContext.getId(), event);
}
}
}
执行结果:
上下文[id:parent-context]接收到事件:[org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@506e6d5e, started on Fri Jun 19 14:45:06 CST 2020]]
上下文[id:current-context]接收到事件:[org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2a70a3d8, started on Fri Jun 19 14:45:07 CST 2020, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@506e6d5e]]
上下文[id:current-context]接收到事件:[org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@2a70a3d8, started on Fri Jun 19 14:45:07 CST 2020, parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@506e6d5e]]
可以看到 current-context 接收到了两次 ContextRefreshedEvent 事件。
10.2 源码分析
发送事件的调用链路大致如下:
还是从 AbstractApplicationContext#refresh 启动应用上下文的方法开始
org.springframework.context.support.AbstractApplicationContext#refresh
- org.springframework.context.support.AbstractApplicationContext#finishRefresh
- org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)
- org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
- org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)
10.3 源码调试
第一次 [parent-context] 进来,403 行发送 ContextRefreshedEvent 事件
这里可以看到当前 ApplicationContext 的 id 为 parent-context
第二次 [current-context] 进来,可以看到当前 ApplicationContext 的父应用上下文 id
this.parent.getId()
= parent-context
如果判断当前的应用上下文有 parent 应用上下文,那么 parent 应用上下文再 publishEvent
发送一次事件。
10.4 解决方案
如何来解决这个事件被重复处理的问题呢?
可以增加一个缓存来存储已经处理过的事件,如果判断已经处理过了,就不再处理
代码如下:
static class MyListener implements ApplicationListener<ContextRefreshedEvent> {
private static Set<ContextRefreshedEvent> processedEvents = new LinkedHashSet<>();
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(processedEvents.add(event)){
ApplicationContext applicationContext = event.getApplicationContext();
System.out.printf("上下文[id:%s]接收到事件:[%s]\n", applicationContext.getId(), event);
}
}
}
11.Spring 内建事件
ApplicationContextEvent 派生事件
- ContextRefreshedEvent:Spring 应用上下文就绪事件
- ContextStartedEvent:Spring 应用上下文启动事件
- ContextStoppedEvent:Spring 应用上下文停止事件
- ContextClosedEvent:Spring 应用上下文关闭事件
部分源码
- org.springframework.context.support.AbstractApplicationContext#start
可以看到调用 start()
或者 stop()
,会通过 publishEvent 来发布事件
@Override
public void start() {
getLifecycleProcessor().start();
publishEvent(new ContextStartedEvent(this));
}
@Override
public void stop() {
getLifecycleProcessor().stop();
publishEvent(new ContextStoppedEvent(this));
}
12.Spring 4.2 PayLoad 事件
Spring PayLoad 事件 - org.springframework.context.PayloadApplicationEvent
- 使用场景:简化 Spring 事件发送,关注事件源主体
- 发送方法
- ApplicationEventPublisher#publishEvent
示例
第一种:
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
applicationEventPublisher.publishEvent(new ApplicationEvent("hello,world"){
});
// 改造成 PayLoadApplicationEvent
applicationEventPublisher.publishEvent("hello,world");
// 发送 PayLoadApplicationEvent
applicationEventPublisher.publishEvent(new PayloadApplicationEvent(this,"hello,world"));
}
第二种:
applicationEventPublisher.publishEvent(new MyPayLoadApplicationEvent(this,"hello,world"));
自定义 MyPayLoadApplicationEvent 类继承 PayloadApplicationEvent
static class MyPayLoadApplicationEvent<String> extends PayloadApplicationEvent<String>{
/**
* Create a new PayloadApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* @param payload the payload object (never {@code null})
*/
public MyPayLoadApplicationEvent(Object source, String payload) {
super(source, payload);
}
}
执行结果:
ApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.ApplicationListenerDemo$2[source=hello,world]
ApplicationListener - 接收到 Spring 事件:org.springframework.context.PayloadApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@7f690630, started on Fri Jun 19 16:50:00 CST 2020]
ApplicationListener - 接收到 Spring 事件:org.springframework.context.PayloadApplicationEvent[source=com.huajie.thinking.in.spring.event.ApplicationListenerDemo@710c2b53]
ApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.ApplicationListenerDemo$MyPayLoadApplicationEvent[source=com.huajie.thinking.in.spring.event.ApplicationListenerDemo@710c2b53]
13.自定义 Spring 事件
扩展 org.springframework.context.ApplicationEvent
实现 org.springframework.context.ApplicationListener
注册 org.springframework.context.ApplicationListener
示例
自定义 Spring 事件
public class MySpringEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param message the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public MySpringEvent(String message) {
super(message);
}
@Override
public Object getSource() {
return String.valueOf(super.getSource());
}
public Object getMessage() {
return getSource();
}
}
自定义监听用来监听我们的 MySpringEvent事件
public class MyApplicationListener implements ApplicationListener<MySpringEvent> {
@Override
public void onApplicationEvent(MySpringEvent event) {
System.out.println("MyApplicationListener - 接收到 Spring 事件:" + event);
}
}
测试
public class CustomizedSpringEventDemo {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 1.添加自定义 Spring 事件监听器
context.addApplicationListener(new MyApplicationListener());
// 2.启动 Spring 应用上下文
context.refresh();
// 3.发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("hello,world"));
// 4.关闭应用上下文
context.close();
}
}
执行结果:
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]
14.依赖注入 ApplicationEventPublisher
通过 ApplicationEventPublisherAware 回调接口
通过 @Autowired ApplicationEventPublisher
示例
public class InjectionApplicationEventPublisherDemo implements ApplicationEventPublisherAware, ApplicationContextAware {
@Autowired
private ApplicationEventPublisher autowiredApplicationEventPublisher;
@Autowired
private ApplicationContext autowiredApplicationContext;
@PostConstruct
public void init(){
// #3
autowiredApplicationEventPublisher.publishEvent(new MySpringEvent("the event from @Autowired-ApplicationEventPublisher"));
// #4
autowiredApplicationContext.publishEvent(new MySpringEvent("the event from @Autowired-ApplicationContext"));
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(InjectionApplicationEventPublisherDemo.class);
//添加 Spring 事件监听
context.addApplicationListener(new MyApplicationListener());
context.refresh();
context.close();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
applicationEventPublisher.publishEvent(new MySpringEvent("the event from ApplicationEventPublisherAware"));// #1
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.publishEvent(new MySpringEvent("the event from ApplicationContextAware"));// #2
}
}
执行结果:
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from ApplicationEventPublisherAware]
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from ApplicationContextAware]
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from @Autowired-ApplicationEventPublisher]
MyApplicationListener - 接收到 Spring 事件:com.huajie.thinking.in.spring.event.MySpringEvent[source=the event from @Autowired-ApplicationContext]
事件发布的顺序
- 1.ApplicationEventPublisherAware
- 2.ApplicationContextAware
- 3.@Autowired-ApplicationEventPublisher
- 4.@Autowired-ApplicationContext
后面两个的发布顺序和编码顺序相关,而前面两个的顺序和以下源码相关,回调接口的顺序决定了事件发布的顺序
AbstractApplicationContext#refresh
- AbstractApplicationContext#prepareBeanFactory
- ApplicationContextAwareProcessor#invokeAwareInterfaces
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);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
15.依赖查找 ApplicationEventMulticaster
依赖查找的方式
-
Bean 名称:“applicationEventMulticaster”
-
Bean 类型:org.springframework.context.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);
}
和 ApplicationEventPublisher 接口相比
-
多了添加 ApplicationListener 和 删除 ApplicationListener 的相关接口
-
而发布事件接口 multicastEvent(ApplicationEvent),广播事件,相当于 发布-订阅 这种模式,适用一对多的场景
初始化方法同样是在应用上下启动的过程中
AbstractApplicationContext#refresh
- AbstractApplicationContext#initApplicationEventMulticaster
源码分为两部分
-
一是判断 ApplicationEventMulticaster Bean 是否存在当前上下文中,如果存在就通过依赖查找的方式找到该对象赋值给当前的 applicationEventMulticaster
-
二是不存在ApplicationEventMulticaster Bean,那么创建一个默认的 ApplicationEventMulticaster,通过 registerSingleton 注册到当前的应用上下文中
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 如果当前上下文存在 “applicationEventMulticaster”
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 {
// 新建 applicationEventMulticaster 对象
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() + "]");
}
}
}
16.ApplicationEventPublisher 底层实现
16.1 底层实现
- 接口:org.springframework.context.event.ApplicationEventMulticaster
- 抽象类:org.springframework.context.event.AbstractApplicationEventMulticaster
- 实现类:org.springframework.context.event.SimpleApplicationEventMulticaster
16.2 源码分析
其实 ApplicationEventPublisher 发布事件功能底层调用的是 ApplicationEventMulticaster 的方法。
基于两点可以说明
1.ApplicationContext 继承了 ApplicationEventPublisher,所以 ApplicationContext 的子类其实就是 ApplicationEventPublisher
2.AbstractApplicationContext#publishEvent() 发布事件方法 403 行
// 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);
}
从名称就可以看到实际上是获取的 ApplicationEventMulticaster 对象,再调用的 multicastEvent 来发布事件。
getApplicationEventMulticaster() 代码如下:
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;
}
16.3 earlyApplicationEvents 是用来做什么的?
这里还有一个细节 earlyApplicationEvents 这个是用来做什么的?看名称意思是一个早期的应用事件。
其实这个判断主要是为了解决一个Spring 启动过程中生命周期的一个 bug,防止在 ApplicationEventMulticaster 初始化之前使用这个对象.。
我们可以用 InjectionApplicationEventPublisherDemo 这个示例稍微修改一下实现一个 BeanPostProcessor 接口如下:
public class InjectionApplicationEventPublisherDemo implements ApplicationEventPublisherAware, ApplicationContextAware, BeanPostProcessor {...}
启动代码进行调试
相关的代码在 AbstractApplicationContext#refresh 启动应用上下文的过程中
registerBeanPostProcessors、initApplicationEventMulticaster 、registerListeners 这三个步骤中
假设某个类在 registerBeanPostProcessors 这个生命周期执行的过程中调用了 ApplicationEventMulticaster 对象,但是这个对象还没有初始化。
这个时候调用发布事件方法会进入到这个逻辑中
将事件添加到 earlyApplicationEvents 这个集合中,而且会跳过 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
这段逻辑,因为此时 ApplicationEventMulticaster 还没有初始化。
然后等 initApplicationEventMulticaster() 初始化 ApplicationEventMulticaster 对象之后,在 registerListeners() 方法中重新发布这个事件。
17.同步和异步 Spring 事件广播
17.1 基于实现类 - org.springframework.context.event.SimpleApplicationEventMulticaster
- 模式切换:SimpleApplicationEventMulticaster#setTaskExecutor
- 默认模式:同步
- 异步模式:如 java.util.concurrent.ThreadPoolExecutor
- 设计缺陷:非基于接口契约编程
代码示例
public class AsyncEventHandlerDemo {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 1.添加自定义 Spring 事件监听器
context.addApplicationListener(new MyApplicationListener());
// 2.启动 Spring 应用上下文
context.refresh();
// 依赖查找 ApplicationEventMulticaster
ApplicationEventMulticaster applicationEventMulticaster = context.getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME,
ApplicationEventMulticaster.class);
if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = (SimpleApplicationEventMulticaster) applicationEventMulticaster;
ExecutorService taskExecutor = Executors.newSingleThreadExecutor(
new CustomizableThreadFactory("my-spring-event-thread-pool")
);
// 同步执行 -> 异步执行
simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor);
// 添加应用上下文关闭的 listener 监听事件 关闭线程池
applicationEventMulticaster.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (!taskExecutor.isShutdown()) {
taskExecutor.shutdown();
}
}
});
}
// 3.发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("hello,world"));
// 4.关闭应用上下文
context.close();
}
}
执行结果:
MyApplicationListener - 接收到 Spring 事件:[线程:my-spring-event-thread-pool1]:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]
17.2 基于注解 - @org.springframework.context.event.EventListener
- 模式切换
- 默认模式:同步
- 异步模式:标准 @org.springframework.scheduling.annotation.Async
- 实现限制:无法直接实现同步/异步动态切换
代码示例:
@EnableAsync
public class AnnotatedAsyncEventHandlerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 1.注册当前类作为 Configuration Class
context.register(AnnotatedAsyncEventHandlerDemo.class);
// 2.启动 Spring 应用上下文
context.refresh();
// 3.发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("hello,world"));
// 4.关闭应用上下文
context.close();
}
@EventListener
@Async
public void onApplicationEvent(MySpringEvent event) {
System.out.println(String.format("基于 @EventListener - 接收到 Spring 事件:[线程:%s]:%s\n",
Thread.currentThread().getName(), event));
}
}
执行结果:
六月 26, 2020 9:42:17 上午 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
基于 @EventListener - 接收到 Spring 事件:[线程:SimpleAsyncTaskExecutor-1]:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]
可以看到线程已经不是 main 线程,被切换成 SimpleAsyncTaskExecutor-1。但是和上面基于实现类的例子有些不同,看控制台的信息没有一个 name 为 ‘taskExecutor’ 的 Bean。
在上面示例的基础上,我们定义一个 name = ‘taskExecutor’ 的 Spring Bean
@Bean
public Executor taskExecutor(){
return Executors.newSingleThreadExecutor(
new CustomizableThreadFactory("my-spring-event-thread-pool")
);
}
执行结果:
基于 @EventListener - 接收到 Spring 事件:[线程:my-spring-event-thread-pool1]:com.huajie.thinking.in.spring.event.MySpringEvent[source=hello,world]
18.Spring 4.1 事件异常处理
Spring 3.0 错误处理接口 - org.springframework.util.ErrorHandler
使用场景
- Spring 事件(Events)
- SimpleApplicationEventMulticaster Spring 4.1 开始支持
- Spring 本地调度(Scheduling)
- org.springframework.scheduling.concurrent.ConcurrentTaskScheduler
- org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
示例
我们在 AsyncEventHandlerDemo 的基础之上进行改造
- 设置一个自定义的 ErrorHandler
simpleApplicationEventMulticaster.setErrorHandler(e -> {
System.out.println("当 Spring 事件异常时,原因:" + e.getMessage());
});
- 故意抛出异常
// 故意抛出异常
context.addApplicationListener(new ApplicationListener<MySpringEvent>() {
@Override
public void onApplicationEvent(MySpringEvent event) {
throw new RuntimeException("故意抛出异常");
}
});
执行结果:
当 Spring 事件异常时,原因:故意抛出异常
19.Spring 事件/监听器实现原理
核心类 - org.springframework.context.event.SimpleApplicationEventMulticaster
- 设计模式:观察者模式扩展
- 被观察者 - org.springframework.context.ApplicationListener
- API 添加
- 依赖查找
- 通知对象 - org.springframework.context.ApplicationEvent
- 被观察者 - org.springframework.context.ApplicationListener
- 执行模式:同步/异步
- 异常处理:org.springframework.util.ErrorHandler
- 泛型处理:org.springframework.core.ResolvableType
源码分析
我们来分析下整个事件发布和监听的过程,看看如何通过具体的事件类型来过滤和监听。
首先我们看看核心类 SimpleApplicationEventMulticaster 继承了 AbstractApplicationEventMulticaster
- org.springframework.context.event.AbstractApplicationEventMulticaster 部分代码如下:
public abstract class AbstractApplicationEventMulticaster
implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
...
最关键的设计在于 retrieverCache 这个缓存设计,我们分别看看这个 Map 中的 Key 和 Value 是什么。
Key:ListenerCacheKey 中有一个 eventType 表示事件的类型
private static final class ListenerCacheKey implements Comparable<ListenerCacheKey> {
private final ResolvableType eventType;
@Nullable
private final Class<?> sourceType;
...
Value:ListenerRetriever 数据结构
private class ListenerRetriever {
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
...
从源代码中可以看到 ListenerRetriever 内部也是集合对象,表示一个 ListenerRetriever 包含多个 ApplicationListener 对象。
联系起来,我们可以猜想这个 retrieverCache Map 对象存储的应该是事件类型和监听的对应关系集合。
源码调试
我们用之前的一个例子来进行调试
public class CustomizedSpringEventDemo {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// 1.添加自定义 Spring 事件监听器
context.addApplicationListener(new MyApplicationListener());
// 2.启动 Spring 应用上下文
context.refresh();
// 3.发布自定义 Spring 事件
context.publishEvent(new MySpringEvent("hello,world"));
// 4.关闭应用上下文
context.close();
}
}
将断点打在 AbstractApplicationEventMulticaster#addApplicationListener 中,当示例执行到第 6 行时,会进入到此方法中,可以看到此时的参数就是我们自定义的 MyApplicationListener,将 MyApplicationListener 加入到 defaultRetriever 集合中。
继续往下走,然后在应用上下启动过程中 AbstractApplicationContext#refresh 方法调用 initApplicationEventMulticaster 方法来初始化 ApplicationEventMulticaster 对象,之后到 finishRefresh() 方法中,publishEvent(new ContextRefreshedEvent(this));
发布 ContextRefreshedEvent 事件,我们将断点打在 AbstractApplicationEventMulticaster#getApplicationListeners() 178 行
可以看到将 eventType=ContextRefreshedEvent 封装成一个 ListenerCacheKey,在 198 行 this.retrieverCache.put(cacheKey, retriever);
将 key 和对应的 listener 放到 retrieverCache 缓存中。
同理,当我们发布自定义事件 MySpringEvent 的时候也经历了同样的过程,最后 retrieverCache 缓存的结果如下:
存放了两个对象,而对象的 Key 值分别是 MySpringEvent 和 ContextRefreshedEvent。
发布事件调用的方法如下:
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 {
invokeListener(listener, event);
}
}
}
当程序执行到 3.发布自定义 Spring 事件 的时候,此时getApplicationListeners(event, type)
会做两件事
- 将 MySpringEvent 放到 retrieverCache 缓存中上面已经分析过了
- 根据 MySpringEvent 类型返回 MyApplicationListener 对象
继续往下走会调用 SimpleApplicationEventMulticaster#invokeListener 执行监听器中的 onApplicationEvent 方法
最后执行我们自定义 MyApplicationListener 中的方法
如何根据 MySpringEvent 事件类型来匹配对应的 ApplicationListener?
AbstractApplicationEventMulticaster#retrieveApplicationListeners 中的关键代码如下:
for (ApplicationListener<?> listener : listeners) {
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
allListeners.add(listener);
}
}
20.Spring Boot 事件
以下的事件继承 SpringApplicationEvent ,而 SpringApplicationEvent 继承 ApplicationEvent,所以 Spring Boot 的事件源自于 Spring 中的事件。
事件类型 | 发生时机 |
---|---|
ApplicationStartingEvent | 当 Spring Boot 应用启动时 |
ApplicationStartedEvent | 当 Spring Boot 应用已启动时 |
ApplicationEnviromentPreparedEvent | 当 Spring Boot Enviroment 实例已经准备时 |
ApplicationPreparedEvent | 当 Spring Boot 应用预备时 |
ApplicationReadyEvent | 当 Spring Boot 应用完全可用时 |
ApplicationFailedEvent | 当 Spring Boot 应用启动失败时 |
21.Spring Cloud 事件
同样的 Spring Cloud 相关的事件也都是继承 ApplicationEvent,所以 Spring Cloud 的事件也源自于 Spring 中的事件。
事件类型 | 发生时机 |
---|---|
EnvironmentChangeEvent | 当 Environment 示例配置属性发生变化时 |
HeartbeatEvent | 当 DiscoveryClient 客户端发送心跳时 |
InstancePreRegisteredEvent | 当服务实例注册前 |
InstanceRegisteredEvent | 当服务实例注册后 |
RefreshEvent | 当 RefreshEndpoint 被调用时 |
RefreshScopeRefreshedEvent | 当 Refresh Scope Bean 刷新后 |
22.面试
22.1 Spring 事件核心接口/组件?
-
Spring 事件 - org.springframework.context.ApplicationEvent
-
Spring 事件监听器 - org.springframework.context.ApplicationListener
-
Spring 事件发布器 - org.springframework.context.ApplicationEventPublisher
-
Spring 事件广播器 - org.springframework.context.event.ApplicationEventMulticaster
22.2 Spring 同步和异步事件处理的使用场景?
- Spring 同步事件 - 绝大多数 Spring Event 使用场景,如 ContextRefresbEvent
- Spring 异步事件 - 主要 @EventListener 与 @Async 配合,实现异步处理,不阻塞主线程,比如长事件的数据计算任务等。不要轻易调整 SimpleApplicationEventMulticaster 中关联的 taskExecutor 对象,除非使用者非常了解 Spring 事件机制,否认容易出现异常行为。
23.参考
- 极客时间-小马哥《小马哥讲Spring核心编程思想》