type
status
date
Jul 26, 2024 07:54 AM
slug
summary
category
tags
password
icon
概述Spring 事件机制代码示例引入依赖UserRegisterEventUserServiceEmailService(监听Event第一种方式:实现 ApplicationListener 接口)CouponService(监听Event第二种方式:@EventListener注解)DemoControllerDemoApplication测试实际应用发送消息SmsSendMessage生产者 SmsProducer消费者 SmsSendConsumerSpring内置事件ApplicationContextEventRouteRefreshListenerRefreshRemoteApplicationEvent后记
概述
在设计模式中,观察者模式是一个比较常用的设计模式。维基百科解释如下:
https://zh.wikipedia.org/wiki/观察者模式观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。
在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦。以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:
- UserService 在完成自身的用户注册逻辑之后,仅仅只需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
- 其它 Service 可以自己订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。
注意:很多时候,我们会把观察者模式和发布订阅模式放在一起对比。简单来说,发布订阅模式属于广义上的观察者模式,在观察者模式的 Subject 和 Observer 的基础上,引入 Event Channel 这个中介,进一步解耦。如下图所示:
Spring 事件机制
Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:
- 事件
ApplicationEvent
:通过继承它,实现自定义事件。另外,通过它的source
属性可以获取事件源,timestamp
属性可以获得发生时间。
- 事件发布者
ApplicationEventPublisher
:通过它,可以进行事件的发布。
- 事件监听器
ApplicationListener
:通过实现它,进行指定类型的事件的监听。
注意:JDK 也内置了事件机制的实现,考虑到通用性,Spring 的事件机制是基于它之上进行拓展。因此,ApplicationEvent
继承自java.util.EventObject
,ApplicationListener
继承自java.util.EventListener
。
代码示例
引入依赖
在
pom.xml
文件中,引入相关依赖。UserRegisterEvent
创建
UserRegisterEvent
事件类,继承 ApplicationEvent
类,用户注册事件的代码如下:通过在 UserRegisterEvent 类中,定义成员变量
username
,将用户名附带上。UserService
创建
UserService
类,代码如下,其中:- 实现了
ApplicationEventPublisherAware
接口,从而将ApplicationEventPublisher
注入到其中。
- 在执行完注册逻辑后,调用
ApplicationEventPublisher
的#publishEvent(ApplicationEvent event)
方法,发布事件。
EmailService(监听Event第一种方式:实现 ApplicationListener
接口)
创建
EmailService
类,代码如下,其中- 实现
ApplicationListener
接口,通过E
泛型设置(监听)感兴趣的事件。
- 实现
#onApplicationEvent(E event)
方法,针对监听的UserRegisterEvent
事件,进行自定义处理。
- 设置
@Async
注解,声明异步执行。毕竟真实场景下,发送邮件可能比较慢,又是非关键逻辑。
CouponService(监听Event第二种方式:@EventListener
注解)
创建
CouponService
类,代码如下,其中:在方法addCoupon
上,添加 @EventListener
注解,并设置监听的事件为 UserRegisterEvent。DemoController
创建 DemoController 类,提供
/demo/register
注册接口。代码如下:DemoApplication
创建
DemoApplication
类,应用启动类。代码如下:测试
① 执行 DemoApplication 类,启动项目。
② 调用 http://127.0.0.1:8080/demo/register?username=frank 接口,进行注册。IDEA 控制台打印日志如下:
实际应用
在实际项目的应用中,可以使用
ApplicationContext
,就不用通过implements ApplicationEventPublisherAware
的方式来发布事件(Spring Event)了,因为ApplicationContext
接口已经继承了ApplicationEventPublisher
接口。发送消息SmsSendMessage
生产者 SmsProducer
消费者 SmsSendConsumer
Spring内置事件
在 Spring 框架中,自定义了非常多的自定义事件,让我们更容易的进行拓展。下面,我们来简单举一些例子。
ApplicationContextEvent
ApplicationContextEvent
是 Spring Context 相关的事件基类,如下图所示:友情提示:Spring Context 可以简单理解成 IoC 容器。
- ContextStartedEvent:Spring Context 启动完成事件。
- ContextStoppedEvent:Spring Context 停止完成事件。
- ContextClosedEvent:Spring Context 停止开始事件。
- ContextRefreshedEvent:Spring Context 初始化或刷新完成事件。
也就是说,在 Spring Context 的整个生命周期中,会发布相应的 ApplicationContextEvent 事件。
SpringApplicationEvent
是 Spring Boot Application(应用)相关的事件基类,如下图所示:- ApplicationStartingEvent:Application 启动开始事件。
- ApplicationEnvironmentPreparedEvent:Spring Environment 准备完成的事件。
- ApplicationContextInitializedEvent:Spring Context 准备完成,但是 Bean Definition 未加载时的事件
- ApplicationPreparedEvent:Spring Context 准备完成,但是未刷新时的事件。
- ApplicationReadyEvent:Application 启动成功事件。
- ApplicationFailedEvent:Application 启动失败事件。
也就是说,在 Application 的整个生命周期中,会发布相应的 SpringApplicationEvent 事件。
RouteRefreshListener
可以使用 Spring Cloud Gateway 通过监听 RefreshRoutesEvent 事件,结合 Nacos 作为配置中心,实现网关路由动态刷新的功能。
注意:Spring Cloud Zuul 也是通过监听 RoutesRefreshedEvent 事件,实现网关路由动态刷新的功能。
RefreshRemoteApplicationEvent
我们可以使用 Spring Cloud Config Client 通过监听 RefreshRemoteApplicationEvent 事件,结合 RabbitMQ 作为 Spring Cloud Bus 消息总线,实现本地配置刷新的功能。
后记
我已经完成了对 Spring 事件机制的学习。当然,还有一些功能,可以自己倒腾倒腾。
① 如果想要多个监听器按照指定顺序执行,可以通过实现
Ordered
接口,指定其顺序。② 如果想要监听多种 ApplicationContext 事件,可以实现 SmartApplicationListener 接口,具体示例可以看看 SourceFilteringListener 类。
③
@TransactionalEventListener
注解,可以声明在当前事务“结束”时,执行相应的监听逻辑。④ 可以通过实现
ApplicationEventMulticaster
接口,定义自定义的事件广播器,可以往里面添加和移除监听器,并发布事件给注册在其中的监听器。使用比较少,基本可以忽略。- 作者:Frank
- 链接:https://blog.franksteven.me//article/springboot_event
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。