观察者模式
在说事件机制之前,我们先聊一下观察者模式,因为 Spring 的事件机制本质上是观察者模式的一种实现。
我们都知道,有一种设计模式叫观察者模式,它用于建立对象之间一对多的依赖关系,当一个对象的状态发生变化时, 所有依赖它的对象都会得到通知并自动更新。这种模式常用于需要实现对象之间松耦合的场景, 其中一个对象(被观察者)的状态变化会影响到其他多个对象(观察者)。
观察者模式的主要角色
- 被观察者(
Subject
):也称为主题或者可观察对象,它维护了一个观察者列表,可以添加、删除和通知观察者。当其状态发生变化时,会通知所有注册的观察者。 - 观察者(
Observer
):观察者是依赖于被观察者的对象,它定义了一个方法,用于在被观察者状态发生变化时进行更新操作。
观察者模式的工作流程
- 被观察者对象注册观察者:观察者通过某种方式向被观察者注册,通常是将自己添加到被观察者的观察者列表中。(建立起观察者与被观察者的关联)
- 被观察者状态变化
- 通知观察者:被观察者遍历观察者列表,调用每个观察者的更新方法(
onEvent
、handle
...) - 观察者更新:每个观察者根据被观察者的通知进行相应的更新操作,执行与状态变化相关的任务
观察者模式的优点
解耦性:被观察者和观察者之间的关系是松耦合的,提高了代码的可维护性和扩展性。 可以轻松添加或删除观察者,可以在不修改被观察者的情况下增加新的观察者。
Spring 中的事件
Spring 的事件机制是 Spring 框架中的一个重要特性,基于观察者模式实现,它可以实现应用程序中的解耦,提高代码的可维护性和可扩展性。
Spring 的事件机制包括事件、事件发布、事件监听器等几个基本概念:
- 事件:事件是一个抽象的概念,它代表着应用程序中的某个动作或状态的发生。
- 事件发布:是事件发生的地方,它负责发布事件,从而通知事件监听器。
- 事件监听器:事件的接收者,它负责处理事件并执行相应的操作。
在 Spring 的事件机制中,事件源和事件监听器之间通过事件进行通信,从而实现了代码的解耦。
如上图所示,在观察者模式的实现中,往往还会有一个
Dispatcher
的角色, 由它来通知观察者,在 Spring
中,ApplicationContext
就扮演了这个角色。
如何定义事件
在 Spring 中,我们可以通过继承 ApplicationEvent
来自定义一个事件:
1 | import org.springframework.context.ApplicationEvent; |
我们会发现,ApplicationEvent
有一个必选的参数
source
,这个参数在实践中往往传递
this
,也就是事件发生处的对象,这个参数不能为
null
。
如何监听事件?
在 Spring 中,监听事件的方式有两种:
- 实现
ApplicationListener
,需要指定它要监听的事件类型
1 | import org.springframework.context.ApplicationListener; |
注意:我们需要添加 @Component
注解以便 Spring
可以注册这个观察者。
- 使用
@EventListener
注解
1 | import com.example.springeventdemo.event.MyEvent; |
我们可以使用 @EventListener
注解在托管 bean
的任何方法上注册事件监听器。 需要监听的事件通过方法的参数来指定。
如何发布事件
我们可以使用 ApplicationEventPublisher
来发布一个事件,也就是通知所有的观察者:
1 | import com.example.springeventdemo.event.MyEvent; |
也可以使用
ConfigurableApplicationContext
,不过这个接口其实也是继承了
ApplicationEventPublisher
接口。
事件异步处理
有时候,我们的一些事件是可以异步处理的,比如注册成功之后给用户发送验证邮件, 注册成功我们就可以返回了,而发送验证邮件的这一步操作可以异步进行处理, 从而加快接口的响应速度。
在 Spring 中,我们可以使用 @Async
注解来将一个
EventListener
标记为异步处理的:
1 | import org.springframework.context.event.EventListener; |
但是,要使用 @Async
我们必须在我们的主程序类中加上
@EnableAsync
注解:
1 | import org.springframework.boot.SpringApplication; |
使用
@Async
注解的监听器,会放到跟请求不同的线程中处理。
指定 EventListener 的处理条件
我们可以通过 EventListener
的 condition
属性来决定监听器是否需要执行:
使用
SpEL
表达式,当然我们也可以把判断写到方法体内。
1 | // 在 myEvent 的 foo 属性等于 'bar' 的时候才会触发 |
使用 condition
而不是写到方法体中的原因是:
- 解耦和可配置性:通过将条件与事件监听器声明分离,你可以在不修改监听器代码的情况下更改条件。这使得在不同的环境或不同的配置下轻松切换监听器的行为成为可能。
- 动态切换行为:允许你根据应用程序或配置来动态决定是否触发事件监听器。这对于需要根据运行时条件来启动或禁用监听器的情况非常有用。
- 可测试性:可以为不同的条件编写单元测试,以确保条件的正确性。
- 统一管理:当有多个监听器时,将条件集中管理在
condition
属性中可以提高代码的可读性,因为你可以轻松查看每个监听器的条件而无需查看每个监听器的具体实现。
监听多个事件
我们可以通过 EventListener
的 classes
属性来指定要监听的多个事件:
1 |
|
这个时候,我们的参数类型就需要修改一下了。
指定事件监听器的执行顺序
我们可以通过 @Order
注解来指定一个事件的不同监听器的执行顺序:
1 | import org.springframework.context.event.EventListener; |
控制事件在事务提交前执行
有时候,我们会在代码中通过 @Transactional
来使用事务:
1 |
|
假设我们在这个方法中有很多代码,然后其中穿插地发布了一些事件,但是我们希望这些事件在整个事务后才去触发监听器的处理逻辑,
这个时候我们就需要使用 @TransactionalEventListener
来注解我们的事件监听器,而不是使用 @EventListener
:
发布事件:
1 |
|
事件监听器:
1 | import org.springframework.stereotype.Component; |
上面的代码会输出:
1 | before save |
@TransactionalEventListener
为我们提供了一个
phase
参数,让我们可以控制事件监听器的执行时机,它有以下可选值:
TransactionPhase.BEFORE_COMMIT
:事务提交前TransactionPhase.AFTER_COMMIT
:事务提交后TransactionPhase.AFTER_ROLLBACK
:事务回滚后TransactionPhase.AFTER_COMPLETION
:事务完成后
tips:
@TransactionalEventListener
并不是给我们监听事务的,只是控制事件在事务提交过程中的某一时刻触发。