点击上方“芋道源码”,选择“设为星标”
管她前浪,还是后浪?
能浪的浪,才是好浪!
每天 10:33 更新文章,每天掉亿点点头发...
源码精品专栏
原创 | Java 2021 超神之路,很肝~
中文详细注释的开源项目
RPC 框架 Dubbo 源码解析
网络应用框架 Netty 源码解析
消息中间件 RocketMQ 源码解析
数据库中间件 Sharding-JDBC 和 MyCAT 源码解析
作业调度中间件 Elastic-Job 源码解析
分布式事务中间件 TCC-Transaction 源码解析
Eureka 和 Hystrix 源码解析
Java 并发源码
1. 概述
2. Spring 事件机制
3. 入门示例
4. Spring 内置事件
666. 彩蛋
本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-54 目录。
原创不易,给点个 Star 嘿,一起冲鸭!
在设计模式中,观察者模式 是一个比较常用的设计模式。维基百科解释如下:
FROM https://zh.wikipedia.org/wiki/观察者模式
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。
此种模式通常被用来实时事件处理系统。
在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦 。以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:
UserService 在完成自身的用户注册逻辑之后,仅仅只需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
其它 Service 可以自己 订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。
友情提示:很多时候,我们会把观察者模式 和发布订阅模式 放在一起对比。
简单来说,发布订阅模式属于广义上 的观察者模式,在观察者模式的 Subject 和 Observer 的基础上,引入 Event Channel 这个中介 ,进一步解耦。如下图所示:
对比 进一步的讨论,胖友可以瞅瞅《观察者模式和发布订阅模式有什么不同?》的讨论。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:
事件 ApplicationEvent:通过继承 它,实现自定义事件。另外,通过它的 source
属性可以获取事件源 ,timestamp
属性可以获得发生时间。
事件发布者 ApplicationEventPublisher:通过它,可以进行事件的发布。
事件监听器 ApplicationListener:通过实现 它,进行指定类型的事件的监听。
友情提示:JDK 也内置了事件机制的实现,考虑到通用性,Spring 的事件机制是基于它之上进行拓展。因此,ApplicationEvent 继承自
java.util.EventObject
,ApplicationListener 继承自java.util.EventListener
。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
示例代码对应仓库:
lab-54-demo
。
看完一些基础的概念,我们来撸个 Spring 事件机制的入门示例,具体的场景还是以用户注册 为例子。新建 lab-54-demo
项目,最终项目如下图:
在 pom.xml
文件中,引入相关依赖。
引入 spring-boot-starter-web
依赖的原因,是稍后会提供示例 API 接口,方便测试。
创建 UserRegisterEvent 事件类,继承 ApplicationEvent 类,用户注册事件。代码如下:
public class UserRegisterEvent extends ApplicationEvent {/*** 用户名*/private String username;public UserRegisterEvent(Object source) {super(source);}public UserRegisterEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;}}
通过在 UserRegisterEvent 类中,定义成员变量 username
,将用户名附带上。
创建 UserService 类,用户 Service。代码如下:
@Service
public class UserService implements ApplicationEventPublisherAware { // <1>private Logger logger &#61; LoggerFactory.getLogger(getClass());private ApplicationEventPublisher applicationEventPublisher;&#64;Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher &#61; applicationEventPublisher;}public void register(String username) {// ... 执行注册逻辑logger.info("[register][执行用户({}) 的注册逻辑]", username);// <2> ... 发布applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));}}
<1>
处&#xff0c;实现 ApplicationEventPublisherAware 接口&#xff0c;从而将 ApplicationEventPublisher 注入到其中。
<2>
处&#xff0c;在执行完注册逻辑后&#xff0c;调用 ApplicationEventPublisher 的 #publishEvent(ApplicationEvent event)
方法&#xff0c;发布「3.2 UserRegisterEvent」事件。
创建 EmailService 类&#xff0c;邮箱 Service。代码如下&#xff1a;
&#64;Service
public class EmailService implements ApplicationListener
<1>
处&#xff0c;实现 ApplicationListener 接口&#xff0c;通过 E
泛型设置感兴趣的事件。
<2>
处&#xff0c;实现 #onApplicationEvent(E event)
方法&#xff0c;针对监听的 UserRegisterEvent 事件&#xff0c;进行自定义处理。
【可以不加】<3>
处&#xff0c;锦上添花&#xff0c;设置 &#64;Async
注解&#xff0c;声明异步执行。毕竟实际场景下&#xff0c;发送邮件可能比较慢&#xff0c;又是非关键逻辑。
友情提示&#xff1a;对
&#64;Async
注解感兴趣的胖友&#xff0c;可以阅读《芋道 Spring Boot 异步任务入门》文章。
创建 CouponService 类&#xff0c;优惠劵 Service。代码如下&#xff1a;
&#64;Service
public class CouponService {private Logger logger &#61; LoggerFactory.getLogger(getClass());&#64;EventListener // <1>public void addCoupon(UserRegisterEvent event) {logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());}}
<1>
处&#xff0c;在方法上&#xff0c;添加 &#64;EventListener
注解&#xff0c;并设置监听的事件为 UserRegisterEvent。这是另一种使用方式&#xff01;
创建 DemoController 类&#xff0c;提供 /demo/register
注册接口。代码如下&#xff1a;
&#64;RestController
&#64;RequestMapping("/demo")
public class DemoController {&#64;Autowiredprivate UserService userService;&#64;GetMapping("/register")public String register(String username) {userService.register(username);return "success";}}
创建 DemoApplication 类&#xff0c;应用启动类。代码如下&#xff1a;
&#64;SpringBootApplication
&#64;EnableAsync // 开启 Spring 异步的功能
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}
① 执行 DemoApplication 类&#xff0c;启动项目。
② 调用 http://127.0.0.1:8080/demo/register?username&#61;yudaoyuanma 接口&#xff0c;进行注册。IDEA 控制台打印日志如下&#xff1a;
# UserService 发布 UserRegisterEvent 事件
2020-04-06 13:09:39.145 INFO 18615 --- [nio-8080-exec-1] c.i.s.l.eventdemo.service.UserService : [register][执行用户(yudaoyuanma) 的注册逻辑]
# CouponService 监听处理该事件
2020-04-06 13:09:39.147 INFO 18615 --- [nio-8080-exec-1] c.i.s.l.eventdemo.service.CouponService : [addCoupon][给用户(yudaoyuanma) 发放优惠劵]
# EmailService 监听处理该事件
2020-04-06 13:09:39.154 INFO 18615 --- [ task-1] c.i.s.l.eventdemo.service.EmailService : [onApplicationEvent][给用户(yudaoyuanma) 发送邮件]
在 Spring 框架中&#xff0c;自定义了非常多的自定义事件&#xff0c;让我们更容易的进行拓展。下面&#xff0c;我们来简单举一些例子。
ApplicationContextEvent 是 Spring Context 相关的事件基类&#xff0c;如下图所示&#xff1a;
友情提示&#xff1a;Spring Context 可以简单理解成 IoC 容器。
ContextStartedEvent&#xff1a;Spring Context 启动完成 事件。
ContextStoppedEvent&#xff1a;Spring Context 停止完成 事件。
ContextClosedEvent&#xff1a;Spring Context 停止开始 事件。
ContextRefreshedEvent&#xff1a;Spring Context 初始化或刷新完成 事件。
也就是说&#xff0c;在 Spring Context 的整个生命周期中&#xff0c;会发布相应的 ApplicationContextEvent 事件。
SpringApplicationEvent 是 Spring Boot Application &#xff08;应用&#xff09;相关的事件基类&#xff0c;如下图所示&#xff1a;
ApplicationStartingEvent&#xff1a;Application 启动开始 事件。
ApplicationEnvironmentPreparedEvent&#xff1a;Spring Environment 准备完成的事件。
ApplicationContextInitializedEvent&#xff1a;Spring Context 准备完成&#xff0c;但是 Bean Definition 未加载时的事件
ApplicationPreparedEvent&#xff1a;Spring Context 准备完成&#xff0c;但是未刷新时的事件。
ApplicationReadyEvent&#xff1a;Application 启动成功 事件。
ApplicationFailedEvent&#xff1a;Application 启动失败 事件。
也就是说&#xff0c;在 Application 的整个生命周期中&#xff0c;会发布相应的 SpringApplicationEvent 事件。
通过 ApplicationContextEvent 和 SpringApplicationEvent 事件&#xff0c;我们在《芋道 Spring Boot 持续交付 Jenkins 入门》文章的「3. 优雅上下线」小节中&#xff0c;实现了 Spring Boot &#43; Nginx 的优雅上下线。
在《芋道 Spring Cloud 网关 Spring Cloud Gateway 入门》文章的「6. 基于配置中心 Nacos 实现动态路由」小节中&#xff0c;我们可以看到 Spring Cloud Gateway 通过监听 RefreshRoutesEvent 事件&#xff0c;结合 Nacos 作为配置中心&#xff0c;实现网关路由动态刷新 的功能。
友情提示&#xff1a;Spring Cloud Zuul 也是通过监听 RoutesRefreshedEvent 事件&#xff0c;实现网关路由动态刷新 的功能。
在《芋道 Spring Cloud 配置中心 Spring Cloud Config 入门》文章的「5. 自动配置刷新&#xff08;第二弹&#xff09;」小节中&#xff0c;我们可以看到 Spring Cloud Config Client 通过监听 RefreshRemoteApplicationEvent 事件&#xff0c;结合 RabbitMQ 作为 Spring Cloud Bus 消息总线&#xff0c;实现本地配置刷新 的功能。
至此&#xff0c;我们已经完成了对 Spring 事件机制的学习。当然&#xff0c;还有一些功能&#xff0c;胖友可以自己在倒腾倒腾。
① 如果胖友想要多个监听器按照指定顺序 执行&#xff0c;可以通过实现 Ordered 接口&#xff0c;指定其顺序。
② 如果胖友想要监听多种 ApplicationContext 事件&#xff0c;可以实现 SmartApplicationListener 接口&#xff0c;具体示例可以看看 SourceFilteringListener 类。
③ &#64;TransactionalEventListener
注解&#xff0c;可以声明在当前事务“结束”时&#xff0c;执行相应的监听逻辑。
④ 可以通过实现 ApplicationEventMulticaster 接口&#xff0c;定义自定义的事件广播器&#xff0c;可以往里面添加和移除监听器&#xff0c;并发布事件给注册在其中的监听器。使用比较少&#xff0c;基本可以忽略。
欢迎加入我的知识星球&#xff0c;一起探讨架构&#xff0c;交流源码。加入方式&#xff0c;长按下方二维码噢&#xff1a;
已在知识星球更新源码解析如下&#xff1a;
最近更新《芋道 SpringBoot 2.X 入门》系列&#xff0c;已经 101 余篇&#xff0c;覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。
提供近 3W 行代码的 SpringBoot 示例&#xff0c;以及超 4W 行代码的电商微服务项目。
获取方式&#xff1a;点“在看”&#xff0c;关注公众号并回复 666 领取&#xff0c;更多内容陆续奉上。
文章有帮助的话&#xff0c;在看&#xff0c;转发吧。
谢谢支持哟 (*^__^*&#xff09;