事件机制是Spring为企业级开发提供的神兵利器之一,它提供了一种低耦合、无侵入的解决方式,是我们行走江湖必备保命技能。但其实Spring事件的设计其实并不复杂,它由三部分组成:事件、发布器、监听器。事件是主体,发布器负责发布事件,监听器负责处理事件。
在简单了解Spring事件的机制之后,本文将从源码的角度出发,和大家一起探讨:Spring事件的核心工作机制,并看一下作为企业级开发工具,Spring事件是如何支持全局异常处理和异步执行的。最后会和大家讨论目前Spring事件机制的一些缺陷和问题,话不多说,我们开始吧。
1. Spring事件如何使用
所谓千里之行始于足下,在研究Spring的事件的机制之前,我们先来看一下Spring事件是如何使用的。通常情况下,我们使用自定义事件和内置事件,自定义事件主要是配合业务使用,自定义事件则多是做系统启动时的初始化工作或者收尾工作。
1.1 自定义事件的使用
- 定义自定义事件
自定义一个事件在使用上很简单,继承ApplicationEvent即可:
// 事件需要继承ApplicationEvent
public class MyApplicationEvent extends ApplicationEvent {
private Long id;
public MyApplicationEvent(Long id) {
super(id);
this.id = id;
}
public Long getId() {
return id;
}
}
- 发布自定义事件
现在自定义事件已经有了,该如何进行发布呢?Spring提供了ApplicationEventPublisher
进行事件的发布,我们平常使用最多的ApplicationContext
也继承了该发布器,所以我们可以直接使用applicationContext进行事件的发布。
// 发布MyApplicationEvent类型事件
applicationContext.publishEvent(new MyApplicationEvent(1L));
- 处理自定义事件
现在事件已经发布了,谁负责处理事件呢?当然是监听器了,Spring要求监听器需要实现ApplicationListener
接口,同时需要通过泛型参数指定处理的事件类型
。有了监听器需要处理的事件类型信息,Spring在进行事件广播的时候,就能找到需要广播的监听器了,从而准确传递事件了。
// 需要继承ApplicationListener,并指定事件类型
public class MyEventListener implements ApplicationListener<MyApplicationEvent> {
// 处理指定类型的事件
@Override
public void onApplicationEvent(MyApplicationEvent event) {
System.out.println(Thread.currentThread().getName() + "接受到事件:"+event.getSource());
}
}
1.2 Spring内置事件
1.2.1 ContextRefreshedEvent
在ConfigurableApplicationContext
的refresh()
执行完成时,会发出ContextRefreshedEvent
事件。refresh()是Spring最核心的方法,该方法内部完成的Spring容器的启动,是研究Spring的重中之重。在该方法内部,当Spring容器启动完成,会在finishRefresh()发出ContextRefreshedEvent事件,通知容器刷新完成。我们一起来看一下源码:
// ConfigurableApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
try {
// ...省略部分非关键代码
//完成普通单例Bean的实例化(非延迟的)
this.finishBeanFactoryInitialization(beanFactory);
// 初始化声明周期处理器,并发出对应的时间通知
this.finishRefresh();
}
}
protected void finishRefresh() {
// ...省略部分非核心代码
// 发布上下文已经刷新完成的事件
this.publishEvent(new ContextRefreshedEvent(this));
}
其实这是Spring提供给我们的拓展点,此时容器已经启动完成,容器中的bean也已经创建完成,对应的属性、init()、Aware回调等,也全部执行。很适合我们做一些系统启动后的准备工作,此时我们就可以监听该事件,作为系统启动后初始预热的契机。其实Spring内部也是这样使用ContextRefreshedEvent的, 比如我们常用的Spring内置的调度器,就是在接收到该事件后,才进行调度器的执行的。
public class ScheduledAnnotationBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
finishRegistration();
}
}
}
1.2.2 ContextStartedEvent
在ConfigurableApplicationContext
的start()
执行完成时,会发出ContextStartedEvent事件。
@Override
public void start() {
this.getLifecycleProcessor().start();
this.publishEvent(new ContextStartedEvent(this));
}
ContextRefreshedEvent
事件的触发是所有的单例bean创建完成后发布,此时实现了Lifecycle
接口的bean还没有回调start(),当这些start()
被调用后,才会发布ContextStartedEvent
事件。
1.2.3 ContextClosedEvent
在ConfigurableApplicationContext
的close()
执行完成时,会发出ContextStartedEvent事件。此时IOC容器已经关闭,但尚未销毁所有的bean。
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
this.doClose();
}
}
protected void doClose() {
// 发布ContextClosedEvent事件
this.publishEvent(new ContextClosedEvent(this));
}
1.2.4 ContextStoppedEvent
在ConfigurableApplicationContext
的stop()
执行完成时,会发出ContextStartedEvent事件。
@Override
public void stop() {
this.getLifecycleProcessor().stop();
this.publishEvent(new ContextStoppedEvent(this));
}
该事件在ContextClosedEvent事件触发之后才会触发,此时单例bean还没有被销毁,要先把他们都停掉才可以释放资源,销毁bean。
2. Spring事件是如何运转的
经过第一章节的探讨,我们已经清楚Spring事件是如何使用的,然而这只是皮毛而已,我们的目标是把Spring事件机制脱光扒净的展示给大家看。所以这一章节我们深入探讨一下,Spring事件的运行机制,重点我们看一下:
- 事件是怎么广播给监听器的?会不会发送阻塞?
- 系统中bean那么多,
ApplicationListener
是被如何识别为监听器的? - 监听器处理事件的时候,是同步处理还是异步处理的?
- 处理的时候发生异常怎么办,后面的监听器还能执行吗?
乍一看是不是问题还挺多,没事,不要着急,让我们一起来开启愉快的探索路程,看看Spring是怎么玩转事件的吧。
2.1 事件发布
在第一章节,我们直接通过applicationContext
发布了事件,同时也提到了,它之所以能发布事件,是因为它是ApplicationEventPublisher
的子类,因此是具备事件发布能力的。但按照接口隔离原则,如果我们只需要进行事件发布,applicationContext
提供的能力太多,还是推荐直接使用ApplicationEventPublisher
进行操作。
2.1.1 获取事件发布器的方式
我们先来ApplicationEventPublisher
的提供的能力,它是一个接口,结构如下:
@FunctionalInterface
public interface ApplicationEventPublisher {
//发布ApplicationEvent事件
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
//发布PayloadApplicationEvent事件
void publishEvent(Object event);
}
通过源码我们发现ApplicationEventPublisher
仅仅提供了事件发布的能力,支持自定义类型和PayloadApplicationEvent
类型(如果没有定义事件类型,默认包装为该类型)。那我们如何获取该发布器呢,我们最常使用的@Autowired
注入是否可以呢,试一下呗。
-
通过@Autowired 注入 ApplicationEventPublisher
通过debug,我们可以直观的看到:是可以的,而且注入的就是ApplicationContext实例。也就是说注入ApplicationContext
和注入ApplicationEventPublisher
是等价的,都是一个ApplicationContext实例。 -
通过ApplicationEventPublisherAware获取 ApplicationEventPublisher
除了@Autowired
注入,Spring还提供了使用ApplicationEventPublisherAware
获取ApplicationEventPublisher
的方式,如果实现了这个感知接口,Spring会在合适的时机,回调setApplicationEventPublisher()
,将applicationEventPublisher
传递给我们。使用起来也很方便。代码所示:
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public void login(String username, String password){
// 1: 进行登录处理
...
// 2: 发送登录事件,用于记录操作
applicationEventPublisher.publishEvent(new UserLoginEvent(userId));
}
// Aware接口回调注入applicationEventPublisher
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
现在我们已经知道通过@Autowired
和ApplicationEventPublisherAware
回调都能获取到事件发布器,两种有什么区别吗? 其实区别不大,主要是调用时机的细小差别,另外就是默写特殊场景下,@Autowired注入可能无法正常注入,实际开发中完成可以忽略不计。所以优先推荐小伙伴们使用ApplicationEventPublisherAware,如果觉得麻烦,使用@Autowired也未尝不可。
如果使是自动注入模型,是无法通过setter()注入ApplicationEventPublisher的,因为在prepareBeanFactory时已经指定忽略此接口的注入了(
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class)
)。顺便说一句,@Autowired
不算自动注入哦。
2.1.2 事件的广播方式
现在我们已经知道,可以通过ApplicationEventPublisher
发送事件了,那么这个事件发送后肯定是要分发给对应的监听器处理啊,谁处理这个分发逻辑呢?又是怎么匹配对应的监听器的呢?我们带着这两个问题来看ApplicationEventMulticaster
。
- 事件是如何广播的
要探查事件是如何广播的,需要跟随事件发布后的逻辑一起看一下:
@Override
public void publishEvent(ApplicationEvent event) {
this.publishEvent(event, null);
}
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// ...省略部分代码
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// 将事件广播给Listener
this.</