热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringEvent业务解耦神器,刷爆了

点击上方“芋道源码”,选择“设为星标”管她前浪,还是后浪?能浪的浪,才是好浪!每天10:33更新文章ÿ

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 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 嘿,一起冲鸭!

1. 概述

在设计模式中,观察者模式 是一个比较常用的设计模式。维基百科解释如下:

FROM https://zh.wikipedia.org/wiki/观察者模式

观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。

此种模式通常被用来实时事件处理系统。

在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦 。以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:

847f0803b422b2493926a42693f8bf79.png
观察者模式
  • UserService 在完成自身的用户注册逻辑之后,仅仅只需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。

  • 其它 Service 可以自己 订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。

友情提示:很多时候,我们会把观察者模式发布订阅模式 放在一起对比。

简单来说,发布订阅模式属于广义上 的观察者模式,在观察者模式的 Subject 和 Observer 的基础上,引入 Event Channel 这个中介 ,进一步解耦。如下图所示:

f89fb38205c0557c4d7f9dbd4f70ccd1.jpeg
对比

进一步的讨论,胖友可以瞅瞅《观察者模式和发布订阅模式有什么不同?》的讨论。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

2. Spring 事件机制

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/

3. 入门示例

示例代码对应仓库:lab-54-demo

看完一些基础的概念,我们来撸个 Spring 事件机制的入门示例,具体的场景还是以用户注册 为例子。新建 lab-54-demo 项目,最终项目如下图:9f7e4751b09139b6ae117fd8969011ce.png

3.1 引入依赖

pom.xml 文件中,引入相关依赖。


org.springframework.bootspring-boot-starter-parent2.2.2.RELEASE 4.0.0lab-54-demoorg.springframework.bootspring-boot-starter-web

引入 spring-boot-starter-web 依赖的原因,是稍后会提供示例 API 接口,方便测试。

3.2 UserRegisterEvent

创建 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,将用户名附带上。

3.3 UserService

创建 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」事件。

3.4 EmailService

创建 EmailService 类&#xff0c;邮箱 Service。代码如下&#xff1a;

&#64;Service
public class EmailService implements ApplicationListener { // <1>private Logger logger &#61; LoggerFactory.getLogger(getClass());&#64;Override&#64;Async // <3>public void onApplicationEvent(UserRegisterEvent event) { // <2>logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());}}

<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 异步任务入门》文章。

3.5 CouponService

创建 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;

3.6 DemoController

创建 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";}}

3.7 DemoApplication

创建 DemoApplication 类&#xff0c;应用启动类。代码如下&#xff1a;

&#64;SpringBootApplication
&#64;EnableAsync // 开启 Spring 异步的功能
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}

3.8 简单测试

① 执行 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) 发送邮件]

4. Spring 内置事件

在 Spring 框架中&#xff0c;自定义了非常多的自定义事件&#xff0c;让我们更容易的进行拓展。下面&#xff0c;我们来简单举一些例子。

4.1 ApplicationContextEvent

ApplicationContextEvent 是 Spring Context 相关的事件基类&#xff0c;如下图所示&#xff1a;

友情提示&#xff1a;Spring Context 可以简单理解成 IoC 容器。

83574b3322d53320009a2df83e24c4eb.png
ApplicationContextEvent 类图
  • 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;

417a05653442b138f7d6b72442c68830.png
SpringApplicationEvent 类图
  • 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 的优雅上下线。

4.2 RouteRefreshListener

在《芋道 Spring Cloud 网关 Spring Cloud Gateway 入门》文章的「6. 基于配置中心 Nacos 实现动态路由」小节中&#xff0c;我们可以看到 Spring Cloud Gateway 通过监听 RefreshRoutesEvent 事件&#xff0c;结合 Nacos 作为配置中心&#xff0c;实现网关路由动态刷新 的功能。

8ff91823ac89562180d83866e5906fc1.png
网关路由动态刷新

友情提示&#xff1a;Spring Cloud Zuul 也是通过监听 RoutesRefreshedEvent 事件&#xff0c;实现网关路由动态刷新 的功能。

4.3 RefreshRemoteApplicationEvent

在《芋道 Spring Cloud 配置中心 Spring Cloud Config 入门》文章的「5. 自动配置刷新&#xff08;第二弹&#xff09;」小节中&#xff0c;我们可以看到 Spring Cloud Config Client 通过监听 RefreshRemoteApplicationEvent 事件&#xff0c;结合 RabbitMQ 作为 Spring Cloud Bus 消息总线&#xff0c;实现本地配置刷新 的功能。

3761641fa28bb5926238ab1a726427a1.png
Spring Cloud Config

666. 彩蛋

至此&#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;

4323f63707ee4eb2bfdbca3c38a94326.png

已在知识星球更新源码解析如下&#xff1a;

e0e1e27718e268fa481cfe3e7ec89057.jpeg

cd4e3dda9e5801f32a9da919c4c9c902.jpeg

cddfaca8ec5109eb919463524f351d29.jpeg

3c39003a7394ccd8f3563ce052384ca0.jpeg

最近更新《芋道 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;


推荐阅读
  • SpringBoot使用Netty实现远程调用的示例
    这篇文章主要介绍了SpringBoot使用Netty实现远程调用的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 2018深入java目标计划及学习内容
    本文介绍了作者在2018年的深入java目标计划,包括学习计划和工作中要用到的内容。作者计划学习的内容包括kafka、zookeeper、hbase、hdoop、spark、elasticsearch、solr、spring cloud、mysql、mybatis等。其中,作者对jvm的学习有一定了解,并计划通读《jvm》一书。此外,作者还提到了《HotSpot实战》和《高性能MySQL》等书籍。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 网址:https:vue.docschina.orgv2guideforms.html表单input绑定基础用法可以通过使用v-model指令,在 ... [详细]
  • 随着前端技术的发展,越来越多的开发者开始使用react、vue等web框架,但很少有人深入理解这些框架的源码。然而,这些框架底层都是由原生的javascript构建而成。对于初学前端的人来说,可能会认为javascript很容易上手,但实际上只是因为它被高度封装了。与能够使用封装类的人相比,能够理解框架原理的人则处于另一个层面。本文将深入剖析jquery源码,探寻框架底层的原理,帮助读者更好地理解web框架的运行机制。 ... [详细]
  • java开发公众号,java自学网公众号
    本文目录一览:1、JAVA微信公众号开发回复消息能回复多条吗?具体怎么代码实现? ... [详细]
  • Java中线程池,你真的了解会用吗
    2019独角兽企业重金招聘Python工程师标准在《深入源码分析Java线程池的实现原理》这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理。在文中 ... [详细]
  • 报错现象:从mysql5.5数据库导出的数据结构放到mysql5.7.10报错create_timetimestampNOTNULLDEFAULT‘0000-00-0 ... [详细]
  • SpringMVC 12 使用请求参数和返回值的Object类型
    对返回Objec ... [详细]
  • Hbase 进阶
    一、RegionServer架构1)StoreFile保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS上。每个Store会有一 ... [详细]
  • 写在前面对于JAVA这门语言,或者JAVA这个生态圈,从JAVASE到Spring,再到Netty、Mina。再到整体架构, ... [详细]
  • Netty(三)
    开发十年,就只剩下这套架构体系了!&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;  熟悉TCP编程的读者可能都会知道,无论是服务端 ... [详细]
author-avatar
用最优秀的你去面对爱你的人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有