Redian新闻
>
Spring Event 业务解耦神器,刷爆了

Spring Event 业务解耦神器,刷爆了

科技

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

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 



本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-54 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

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

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/

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 项目,最终项目如下图:

3.1 引入依赖

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-54-demo</artifactId>

    <dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

引入 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 = LoggerFactory.getLogger(getClass());

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String username) {
        // ... 执行注册逻辑
        logger.info("[register][执行用户({}) 的注册逻辑]", username);

        // <2> ... 发布
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }

}

<1> 处,实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入到其中。

<2> 处,在执行完注册逻辑后,调用 ApplicationEventPublisher 的 #publishEvent(ApplicationEvent event) 方法,发布「3.2 UserRegisterEvent」事件。

3.4 EmailService

创建 EmailService 类,邮箱 Service。代码如下:

@Service
public class EmailService implements ApplicationListener<UserRegisterEvent// <1>

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    @Async // <3>
    public void onApplicationEvent(UserRegisterEvent event) // <2>
        logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
    }

}

<1> 处,实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。

<2> 处,实现 #onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。

【可以不加】<3> 处,锦上添花,设置 @Async 注解,声明异步执行。毕竟实际场景下,发送邮件可能比较慢,又是非关键逻辑。

友情提示:对 @Async 注解感兴趣的胖友,可以阅读《芋道 Spring Boot 异步任务入门》文章。

3.5 CouponService

创建 CouponService 类,优惠劵 Service。代码如下:

@Service
public class CouponService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener // <1>
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }

}

<1> 处,在方法上,添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent。这是另一种使用方式!

3.6 DemoController

创建 DemoController 类,提供 /demo/register 注册接口。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String register(String username) {
        userService.register(username);
        return "success";
    }

}

3.7 DemoApplication

创建 DemoApplication 类,应用启动类。代码如下:

@SpringBootApplication
@EnableAsync // 开启 Spring 异步的功能
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.classargs);
    }

}

3.8 简单测试

① 执行 DemoApplication 类,启动项目。

② 调用 http://127.0.0.1:8080/demo/register?username=yudaoyuanma 接口,进行注册。IDEA 控制台打印日志如下:

# 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 框架中,自定义了非常多的自定义事件,让我们更容易的进行拓展。下面,我们来简单举一些例子。

4.1 ApplicationContextEvent

ApplicationContextEvent 是 Spring Context 相关的事件基类,如下图所示:

友情提示:Spring Context 可以简单理解成 IoC 容器。

ApplicationContextEvent 类图
  • ContextStartedEvent:Spring Context 启动完成 事件。
  • ContextStoppedEvent:Spring Context 停止完成 事件。
  • ContextClosedEvent:Spring Context 停止开始 事件。
  • ContextRefreshedEvent:Spring Context 初始化或刷新完成 事件。

也就是说,在 Spring Context 的整个生命周期中,会发布相应的 ApplicationContextEvent 事件。

SpringApplicationEvent 是 Spring Boot Application (应用)相关的事件基类,如下图所示:

SpringApplicationEvent 类图
  • ApplicationStartingEvent:Application 启动开始 事件。
  • ApplicationEnvironmentPreparedEvent:Spring Environment 准备完成的事件。
  • ApplicationContextInitializedEvent:Spring Context 准备完成,但是 Bean Definition 未加载时的事件
  • ApplicationPreparedEvent:Spring Context 准备完成,但是未刷新时的事件。
  • ApplicationReadyEvent:Application 启动成功 事件。
  • ApplicationFailedEvent:Application 启动失败 事件。

也就是说,在 Application 的整个生命周期中,会发布相应的 SpringApplicationEvent 事件。

通过 ApplicationContextEvent 和 SpringApplicationEvent 事件,我们在《芋道 Spring Boot 持续交付 Jenkins 入门》文章的「3. 优雅上下线」小节中,实现了 Spring Boot + Nginx 的优雅上下线。

4.2 RouteRefreshListener

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

网关路由动态刷新

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

4.3 RefreshRemoteApplicationEvent

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

Spring Cloud Config

666. 彩蛋

至此,我们已经完成了对 Spring 事件机制的学习。当然,还有一些功能,胖友可以自己在倒腾倒腾。

① 如果胖友想要多个监听器按照指定顺序 执行,可以通过实现 Ordered 接口,指定其顺序。

② 如果胖友想要监听多种 ApplicationContext 事件,可以实现 SmartApplicationListener 接口,具体示例可以看看 SourceFilteringListener 类。

@TransactionalEventListener 注解,可以声明在当前事务“结束”时,执行相应的监听逻辑。

④ 可以通过实现 ApplicationEventMulticaster 接口,定义自定义的事件广播器,可以往里面添加和移除监听器,并发布事件给注册在其中的监听器。使用比较少,基本可以忽略。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
In China, Even Movie Stars Just Want a Stable Gig今日无内容,预告下已经购买的机器,你们更关心哪个机器,也告诉我一下Bunker Hunters: Mapping Shanghai’s Remaining FortificationsChina Bans Schools From Forcing E-Devices on Students【小红书精选】向日葵花田合集、Seventeen演唱会、在飞机上的民宿……Shanghai Spring/Summer 2022: The Season That Never Was咨询公司BCG已开放咨询类2022 Events,仅限Final year学生参加!退出印度运动 信仰的颠覆(四十)豆瓣8.6,刷爆朋友圈的一本书Hiring | Real Estate Senior Accountant / Accounting Manager车载品牌音响被「软硬解耦」,扬声器赛道进入全新市场周期JNBY Removes ‘Spooky’ Children’s Clothing After Outcry, AgainWith Bittersweet Memories, Students Leave Shanghai in DrovesWomen Want Pepper Spray To Keep Safe, But Buying It Isn’t EasyLearning Device Accused of Offering ‘Inappropriate’ Content【5.25今日折扣】英国SPA下午茶旅行折扣大放送!雅诗兰黛眼霜/EVE LOM卸妆膏大降价!【前瞻Prospect·EP03】线性资本王淮: From Scaling FB to Leading VC Investing早报|爱马仕在武汉开设新店;传Dior将撤回对Valentino的索赔;西装品牌Gieves & Hawkes挂牌出售Bain(US)已开放2022 Virtual Events小红书刷爆了!伊能静在洛杉矶给通缉犯庆祝生日?The Key to Reviving Urban Rivers? Recreation, Not RegulationBeijing Axes COVID-19 Testing Lab Over Procedural ViolationsHenan Plans to ‘Recognize’ Locals Preventing Domestic ViolenceSpring Week,专属低年级学生的“留英神器”德国禁止崇拜和骂人“纳粹”Summer (and Spring, and Fall) Heat Waves Are the New Normal美股SPAC|中国投行深度参与SPAC,由瑞士信贷前总裁领导的SPAC将与曾强的鑫根资本合作教育随笔(88)课堂教学的兴奋点和切入点J.P. Morgan已开放科技类2022 Events,仅限女生参加!Train to Chengdu: Two Students Reflect on Leaving Shanghai‘Devil Doctor’ Probed for Allegedly Extorting Sick Patients晚点独家丨快手国际化业务组织调整;商汤收缩安防业务团队、汽车业务仍扩张USC滑板女孩的留美第一年:上课、健身、刷Events在南法的日子(2)----美丽的雨伞松在牛郎织女分手之后
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。