程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。
Spring提供了强大的扩展体系,其中本篇介绍的事件机制是其扩展体系中一个主要分支。本篇先介绍Spring事件机制原理,再总结下spring和spring boot启动过程的主要事件,接着通过示例展示了自定义监听器的4种主要的注册方式,最后通过示例介绍了自定义事件的发布方式。
1. spring事件机制原理
1.1 观察者模式
1.1.1 模式介绍
观察者(Observer)模式: 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
参与者角色:
- Subject: 需要被观察的对象,抽象类
- ConcreteSubject:具体的需要被观察的对象
- Observer: 观察者接口,定义一个有变更时的触发接口
- ConcreteObserver: 具体观察者,实现具体的操作逻辑
观察者模式基本类图如下
1.1.2 观察者模式设计要点
-
Observer设计要点
关于Observer接口方法设计有2种方式:- 以变更类型、事件类型为方法名,如onStateChage()、onNameChange()、onStart()、onClose()等等。这种方式实现简单,但适合变更类型确定的场景。比如spring boot启动事件的监听器
SpringApplicationRunListener
的方法设计就采用这种。 - 以抽象事件类型为参数,如onAbstractXxxEvent(AbstractXxxEvent e)。这种方式扩展性强,适合变更类型不确定的场景,但实现方式复杂。spring的
ApplicationContext
容器就是采用这种方式实现。
- 以变更类型、事件类型为方法名,如onStateChage()、onNameChange()、onStart()、onClose()等等。这种方式实现简单,但适合变更类型确定的场景。比如spring boot启动事件的监听器
-
Subject设计要点,单一职责原则,一种类型Observer只处理一种类型Subject,比如Spring中
ApplicationContext
容器的观察者为ApplicationListener
,spring boot的SpringApplication
程序的观察者为SpringApplicationRunListener
。
1.2 spring事件机制
1.2.1 spring事件设计介绍
spring事件的实现是观察者模式的应用,spring事件监听器类图如下,下面同时也总结了spring的事件监听器接口与spring容器、spring boot有关的类图关系
1.2.2 与观察者模式对比
根据上面类图与观察者类图对比,spring各个类与观察者类图的类映射关系如下:
spring类 | 观察者类/作用 |
---|---|
ApplicationListener | Observer,用于监听spring容器状态 |
ApplicationEvent | Observer观察ApplicationContext 容器状态变更的具体类型,比如容器刷新完成事件ContextRefreshedEvent |
ApplicationContext | Subject,是ApplicationListener 监听的主体对象 |
ApplicationEventPublisher | 定义Subject的notify相关的方法, 面向应用代码的事件发布器,应用代码可以使用该接口发布事件 |
ApplicationEventMulticaster | 多播器:把Subject发布的事件分发给具体的ApplicationListener 监听器 |
SpringApplicationRunListener | Observer,用于监听springboot应用程序SpringApplication 状态,主要实现为EventPublishingRunListener |
SpringApplication | Subject,是SpringApplicationRunListener 监听的主体对象 |
SpringApplicationRunListeners | 多播器:把springboot应用启动事件分发给各个SpringApplicationRunListener 监听器对象 |
1.2.3 多播器ApplicationEventMulticaster
在Spring事件机制的实现中,ApplicationEventMulticaster
多播器是核心部分。它也是外观模式的应用,它的功能实现说明如下:
- 它把对监听器的管理和通知接口从Subject类中分离,这样完全实现了
ApplicationContext
与监听器的解耦。 ApplicationEventMulticaster
面向接口编程,可以管理任何ApplicationListener对象。- 还可以根据ApplicationEvent事件类型匹配适合的监听器,从而实现监听器之间的独立性。
- 客户端应用代码可以直接通过
ApplicationEventMulticaster
对象发送ApplicationEvent事件。
spring中多播器的实现类为SimpleApplicationEventMulticaster
,它分发事件的代码如下:
// SimpleApplicationEventMulticaster源码
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, null);
}
@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null && listener.supportsAsyncExecution()) {
// 如果设置线程池对象、监听器支持异步执行,则异步执行监听器
try {
executor.execute(() -> invokeListener(listener, event));
} catch (RejectedExecutionException ex) {
// Probably on shutdown -> invoke listener locally instead
invokeListener(listener, event);
}
} else {
// 默认同步执行监听器
invokeListener(listener, event);
}
}
}
源码说明有2点:
- 执行
getApplicationListeners
方法会把事件类型与监听器ApplicationListener
的泛型类型进行匹配(感兴趣可以翻阅其源码),最终返回所有匹配的监听器。 - 默认同步执行各个监听器,如果向多播器设置了执行线程池对象且监听器支持异步执行时才会异步执行监听器。
2. spring boot 启动过程中的事件
以下按启动时触发顺序依次排列说明:
ApplicationEvent实现类 | Event Source类 | 何时触发 |
---|---|---|
*ApplicationStartingEvent | SpringApplication | BootstrapRegistry创建完成,SpringApplicationRunListener加载完后触发 |
*ApplicationEnvironmentPreparedEvent | SpringApplication | 环境变量ConfigurableEnvironment加载完成后触发,该事件会触发EnvironmentPostProcessor实现执行 |
*ApplicationContextInitializedEvent | SpringApplication | 执行所有ApplicationContextInitializer的initialize方法完成后触发 |
*ApplicationPreparedEvent | SpringApplication | 基本的BeanDefinition已经被加载,准备刷新容器前触发 |
ServletWebServerInitializedEvent | TomcatWebServer | 所有bean都创建完成后由WebServerStartStopLifecycle生命周期监控对象触发 |
*ContextRefreshedEvent | AnnotationConfigServletWebServerApplicationContext | 所有bean都创建完成后触发 |
*ApplicationStartedEvent | SpringApplication | 容器刷新完成后触发 |
AvailabilityChangeEvent | AnnotationConfigServletWebServerApplicationContext | 与ApplicationStartedEvent事件同级别 |
*ApplicationReadyEvent | SpringApplication | 所有ApplicationRunner和CommandLineRunner扩展执行完后触发 |
*ContextClosedEvent | AnnotationConfigServletWebServerApplicationContext | spring容器将关闭时触发,在执行Lifecycle#stop方法和bean的desdtroy方法前触发 |
*ApplicationFailedEvent | AnnotationConfigServletWebServerApplicationContext | 任何时候异常就会触发 |
上面事件ContextRefreshedEvent来自spring-context模块,其它都来自spring-boot模块。其中带*
的是关键事件,可以根据需要,监听这些事件来完成你的业务处理,比如:
- 手动添加扩展实现,如监听ApplicationEnvironmentPreparedEvent或实现EnvironmentPostProcessor接口,可以添加自己的ApplicationContextInitializer对象
- 从配置中心拉取配置,如监听ApplicationContextInitializedEvent,添加自己的配置,如从配置中心获取配置
3. spring boot中事件监听器有几种注册方式
大方向有4种方式:
- spring context提供了2种方式:一种是向spring容器注册监听器bean,如
@Compnent
注解的监听器;另一种是使用@EventListener
注解到bean的方法上。 - spring boot提供了2种方式:一种是在spring.factories文件注册,另一种是使用SpringApplication对象的addListeners方法手动添加。
spring context提供的方式和spring boot提供的方式有什么不同?
- spring context提供的方式注册的监听器不能监听到spring boot应用启动时的事件,比如
ApplicationStartingEvent
、ApplicationEnvironmentPreparedEvent
,因为spring boot启动时发出的这些事件是在spring容器创建监听器bean之前发生的。 - 从
ContextRefreshedEvent
事件后两种方式注册监听器都可监听到。因为这个时候spring容器已经完成监听器bean的创建。
下面通过示例分别介绍这4种方式的使用说明。
3.1 spring context提供的方式
3.1.1 实现ApplicationListener
接口并注入Spring容器。
可通过@Compnent
注解把bean交由spring容器,也可通过其它方式如@Bean注解,如下示例
@Component
public class ApplicationListenerImpl implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("get event: " + event);
}
}
3.1.2 通过@EventListener
注解到bean的方法上
如下示例
@Component
public class EventListenerByAnnotation {
@EventListener
public void on(ApplicationEvent event) {
EventLogger.write("event_annotation.txt", event);
}
}
与上面方式的不同是,上面的方式更先执行,这种@EventListener
注解的方式更后执行。因为两种监听器的注册顺序不一样,前者更先,后者更后。
3.2 spring boot提供的方式
3.2.1 通过在META-INF/spring.factories
文件中添加ApplicationListener
实现类
监听器示例代码如下:
public class ApplicationListenerImpl implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("get event: " + event);
}
}
ApplicationListenerImpl
无需标注@Component
注解,后面只需要在META-INF/spring.factories
文件添加如下内容即可:
# 文件:spring.factories
org.springframework.context.ApplicationListener=com.example.ApplicationListenerImpl
这种方式就可以监听spring boot的事件,如上前面表格。
3.2.2 使用SpringApplication对象的addListeners方法手动添加ApplicationListener对象
这种方式和上面方式一样,不同的就是上面是通过配置文件,而该种方式是通过写代码实现,示例代码如下:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
// 手动添加监听器
application.addListeners(new ApplicationListenerImpl());
// 启动spring boot应用
application.run(args);
}
}
4. 自定义事件和监听器
- 定义事件和注册监听器,示例代码如下:
// 定义事件
public class MyApplicationEvent extends ApplicationEvent {
public MyApplicationEvent(Object source) {
super(source);
}
}
// 定义监听器,并使用@Component注解
@Component
public class MyEventListener implements ApplicationListener<MyApplicationEvent> {
@Override
public void onApplicationEvent(MyApplicationEvent event) {
System.out.println(event);
}
}
- 选择一个时间点发布事件。比如下面代码是在获得
ApplicationContext
对象是发布自定义事件。
@Component
public class MyEventPublisher implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.publishEvent(new MyApplicationEvent(applicationContext));
}
}
5. 注意事项
- spring中的事件默认是同步处理,等所有匹配的监听器都执行完后才会执行后面步骤。详见
SimpleApplicationEventMulticaster
多播器multicastEvent
方法源码。 - 在自定义事件中避免出现事件依赖环,否则会出现
StackOverflowError
异常。即A监听器发布B事件,B监听器发布C事件,C监听器发布A事件。