内部应用解耦神器-Spring事件

news2024/11/16 3:40:29

大家好,我是程序员牛牛,《AI超级个体: ChatGPT与AIGC实战指南》的参与人,10年Java编程程序员。

1. 概述

在做业务开发过程中,有些复杂点的逻辑,可能代码逻辑会很冗长,举一个很简单的例子,如:用户购买产品下单支付,当支付完成后,可能有以下操作:

1710225092784.jpg

如果这些都在一个流程中同步执行下来,不仅代码冗长,耦合度高,而且也不方便维护,此时我们需要做的就是把这三个步骤进行异步解耦,我们第一个想到解决方案的可能是使用消息队列,MQ确实可以解决这个问题,但MQ是比较复杂的,非必要不提升架构复杂度。

如果是微服务架构,涉及到多个服务之间协作,那MQ无疑是最佳选择,但如果是单体架构,完全可以使用更加轻量级的解决方案:Spring Event

2. Spring Event简介

事件是框架中最容易被忽视的功能之一,但也是最有用的功能之一。与 Spring 中的许多其他功能一样,事件发布是ApplicationContext提供的功能之一,它类似发布订阅机制,发布一个事件之后,可以在其他地方监听这个事件,做一些异步处理(当然也支持同步),其实它就是一个观察者模式设计。

3. 使用Spring Event

spring event使用其实很简单:

image.png

下面我们已发送邮件为例,来做一个简单的演示

3.1 其中事件监听的方式有两种

  1. 通过监听器的方式

  2. 通过注解的方式

3.2 通过监听器方式监听事件

3.2.1 定义事件

定义邮件发送事件,需要继承ApplicationEvent, 这里我们列举了两个简单的属性,邮箱地址和邮件内容。

@Getter
@Setter
public class EmailSendEvent extends ApplicationEvent {

    private String address;

    private String content;

    public EmailSendEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }
}

3.2.2 定义事件发布者

事件发布类,需要实现ApplicationEventPublisherAware接口, 并且需要把该对象注入到spring容器中

@Component
public class EmailEventPublisher implements ApplicationEventPublisherAware {

    @Resource
    private ApplicationEventPublisher publisher;

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

    /**
     * 发送邮件
     *
     * @param address 邮件地址
     * @param content 邮件内容
     */
    public void sendEmail(String address, String content) {

        // 发送邮件的逻辑
        System.out.println("发送邮件:" + address + "," + content);

        // 发布邮件发送事件
        publisher.publishEvent(new EmailSendEvent(this, address, content));

        System.out.println("邮件发送完毕!");
    }

}

3.2.3 创建事件监听类

这里要实现ApplicationListener接口,且同样要把对象注入到Spring容器内

@Component
public class EmailEventListener implements ApplicationListener<EmailSendEvent> {

    @Override
    public void onApplicationEvent(EmailSendEvent event) {
        
        // 因为此处是同步执行,可以发现,这里收到邮件之后,前面的邮件发送才算完成
        // 如果需要异步,可以使用注解方式
        System.out.println("监听器方式,收到邮件:" + event.getAddress() + "," + event.getContent());
    }

}

3.2.4 测试效果

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestEmailDemo {

    @Autowired
    private EmailEventPublisher emailEventPublisher;

    @Test
    public void testSendEmail() {
        String address = "test@example.com";
        String content = "Hello, World!";
        emailEventPublisher.sendEmail(address, content);
    }
}

此时我们运行该测试类,正常情况下,EmailEventListener类中,将输出收到邮件的信息,我们看看效果

image.png

结果也和我们预想的一致,实际业务中,我们就可以在监听器中,做一些具体的逻辑处理,如把邮件内容发送给具体的用户等等…

3.3 通过注解方式监听事件

在spring4.2版本之后,可以直接使用注解的方式监听事件

事件定义和事件发布,和上面一致,我们增加一个注解监听的方式。

@Component
public class EmailService {

    /**
    * 使用注解方式,监听时间
    * @param sendEvent: email事件
    */
    @EventListener(EmailSendEvent.class)
    public void receiveEmail(EmailSendEvent sendEvent) {
        System.out.println("注解监听方式,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());
    }

}

此时我们再运行上面的测试类,得到的结果:

image.png

可以看到两种监听方式都生效了(ps: 正常使用时,我们只需要选择一种监听方式即可

3.4 同步事件和异步事件

3.4.1 同步事件

默认的spring事件,是同步的,也就是说,事件发送者,需要等到事件被监听完成,才算是一个事件发送完成。

按我们这个例子,是邮件监听完成后,才算邮件事件发送完成,我们来测试一下。

在监听器方式中,加入以下代码:

public void onApplicationEvent(EmailSendEvent event) {
        // 此处睡眠10秒
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // 因为此处是同步执行,可以发现,这里收到邮件之后,前面的邮件发送才算完成
        // 如果需要异步,可以使用注解方式
        System.out.println("监听器方式,收到邮件:" + event.getAddress() + "," + event.getContent());
    }

此时执行测试类,应该是10s后,才算是邮件事件发布完成,看看实际效果:

image.png

对于一些同步场景,我们可以直接使用监听器方式,然后我们更多场景,应该是使用异步事件

3.4.2 异步事件

先使用@EnableAsync注解,开启异步事件支持

@SpringBootApplication
@EnableAsync
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

然后修改EmailService代码,加入@Async注解,来开启异步模式

 @EventListener(EmailSendEvent.class)
    @Async
    @Order(1)
    public void receiveEmail(EmailSendEvent sendEvent) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("注解监听方式,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());
    }

同时我们把监听器方式类,注释掉(只保留异步监听方式)。

此时再执行测试类的时候,应该是邮件事件发送后,不用等待事件监听完成执行,就算是该事件已经发送完成了。

image.png

3.4.3 事件监听顺序

当一个事件,我们有多个监听器时,可以可以使用@Order注解,来指定监听器的顺序,这里@Order(1)来指定这里的顺序为1,可以看看这个注解的源码,默认值为Integer.MAX_VALUE

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
    int value() default Integer.MAX_VALUE;
}

我们在EmailService中,增加一个监听器,并指定receiveEmail的顺序为100,receiveEmail2的顺序为1,此时应该先执行receiveEmail2

/**
    * 使用注解方式,监听时间
    * @param sendEvent: email事件
    */
    @EventListener(EmailSendEvent.class)
    @Async
    @Order(100)
    public void receiveEmail(EmailSendEvent sendEvent) {
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        System.out.println("注解监听方式1,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());
    }

    /**
     * 使用注解方式,监听时间
     * @param sendEvent: email事件
     */
    @EventListener(EmailSendEvent.class)
    @Async
    @Order(1)
    public void receiveEmail2(EmailSendEvent sendEvent) {
        System.out.println("注解监听方式2,收到邮件:" + sendEvent.getAddress() + "," + sendEvent.getContent());
    }

看看测试结果:

image.png

跟预期一致!

最后我把测试源码都放到码云上了,欢迎关注公众号获取源码!发送消息“Spring Event”即可。

d465119f927be0d100059305e0aa759.jpg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1510530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows解决nodejs版本冲突:安装版本管理器nvm,可根据不同项目一键切换适配版本

windows解决nodejs版本冲突&#xff1a;安装版本管理器nvm&#xff0c;可根据不同项目一键切换适配版本 参考来源&#xff1a;在本机 Windows 上设置 NodeJS | Microsoft Learn 建议安装版本管理器 nvm-windows&#xff0c;再用它来安装 Node.js 和 npm&#xff0c;这样可以根据…

C++学习笔记:红黑树

红黑树 什么是红黑树红黑树的规则红黑树节点的定义红黑树的插入空树插入非空插入条件判断新插入的节点 cur 不为 root 且 parent->_col 为红就需要调整父节点为左 grandf->left parent当uncle节点为红色时,只需要进行颜色调整,即可当uncle为空 或 者存在但是为黑parent …

Midjourney能让角色保持一致了

Midjourney发布新功能&#xff0c;网友直呼“不可思议”&#xff01; 现在你可以让生成的图像几乎保持角色一致&#xff0c;belike&#xff1a; 所有超级英雄长一个模样盯着你。 甚至动漫风、写实风等跨风格生成也同样适用&#xff1a; 保持同一风格&#xff0c;感jio配上文字…

【FPGA】DDR3学习笔记(一)丨SDRAM原理详解

本篇文章包含的内容 一、DDR3简介1.1 DDR3 SDRAM概述1.2 SDRAM的基础结构 二、 SDRAM操作时序2.1 SDRAM操作指令2.2 模式寄存器&#xff08;LOAD MODE REGISTER&#xff09;2.3 SDRAM操作时序示例2.3.1 SDRAM初始化时序2.3.2 突发读时序2.3.3 随机读时序2.3.4 突发写时序2.3.5 …

Java基础-接口

文章目录 1.快速入门代码&#xff1a;结果&#xff1a; 2.接口基本介绍1.语法注意&#xff1a;在jdk1.8以后接口才可以有静态方法&#xff0c;默认方法 2.代码实例 3.接口的应用场景1.场景介绍2.代码实例4.接口使用细节 5.接口课堂练习题目&#xff1a;答案&#xff1a;注意&am…

深入理解,java标识符?类型转换?

1、标识符 下面这张图是中国的一些姓氏 中国人起名字的规则都是以姓开头&#xff0c;名结尾。通过这个规则可以起&#xff1a;刘爱国、张三等&#xff0c;都是以汉字起的。但是不会起李ad、王123等名字&#xff0c;因为不符合规则。 所以&#xff0c;java在给变量、方法、类等…

【C++进阶】C++继承概念详解

C继承详解 一&#xff0c;继承的概念和定义1.1 继承的概念1.2 继承的定义1.3 继承关系和访问限定符 二&#xff0c;基类和派生类的对象赋值转移三&#xff0c;继承的作用域四&#xff0c;派生类的默认成员函数五&#xff0c;继承和友元&静态成员和继承六&#xff0c;菱形继…

Ansys Lumerical | 激光雷达天线仿真

附件下载 联系工作人员获取附件 在本文中&#xff0c;我们将了解如何根据激光雷达应用需求设计和优化相控阵光栅天线。 概述 激光雷达&#xff08;LIDAR&#xff09;是“light detection and ranging”的简称&#xff0c;近年来由于在机器人、自动驾驶汽车、高精度测绘等领域…

【AcWing】蓝桥杯集训每日一题Day2|前缀和|562.壁画(C++)

562. 壁画 562. 壁画 - AcWing题库难度&#xff1a;中等时/空限制&#xff1a;1s / 64MB总通过数&#xff1a;4154总尝试数&#xff1a;10197来源&#xff1a;Google Kickstart2018 Round H Problem B算法标签 思维题枚举前缀和 题目内容 Thanh 想在一面被均分为 N 段的墙上画…

[java——基础] 双亲委派机制

目录 核心思想&#xff1a; 双亲委派机制的好处&#xff1a; 三种类加载器 解析源代码 双亲委派思想面试总结&#xff1a; 核心思想&#xff1a; 向上搜索&#xff0c;向下加载。 双亲委派机制的好处&#xff1a; 防止Java核心类被篡改&#xff0c;防止类的重复加载。 三…

哈希表|15.三数之和

力扣题目链接 int cmp(const void *a,const void *b) {return *(int*)a - *(int*)b;} int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {*returnSize 0;if(numsSize < 3)return NULL;qsort(nums, numsSize, sizeof(int),cmp);int **…

C++ Qt开发:QNetworkAccessManager网络接口组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QNetworkAccessManager组件实现Web网…

Java日志框架Log4j 2详解

目录 一、什么是日志&#xff1f; 二、日志的主要用途 三、常用日志框架 1、Apache Log4j 2、Commons Logging 3、SLF4J 4、Logback 5、JUL(Java Util Logging) 6、Log4j 2 四、log4j 2 的优点 五、Log4j 2下载和配置 1、访问Log4j – 下载 Apache Log4j™ 2官网&a…

RHEL9 DNF/YUM仓库管理软件包

DNF/YUM仓库管理软件包 一个基于RPM包的软件包管理器能够从指定的服务器自动下载RPM包并且安装&#xff0c;自动处理依赖性关系&#xff0c;并且一次性安装所有依赖的软件包C/S模式 Server服务端提供RPM软件包与数据库文件repodataClient客户端使用dnf仓库 常用组合 组合参…

你还可以通过“nrm”工具,来自由管理“npm”的镜像

你还可以通过“nrm”工具&#xff0c;来自由管理“npm”的镜像 nrm&#xff08;npm registry manager&#xff09;是npm的镜像管理工具&#xff0c;有时候国外的资源太慢&#xff0c;使用这个就可以快速地在npm源间切换。 1.安装nrm 在命令行执行命令&#xff0c;npm install…

Java 容器启动执行指定任务

1、实现CommandLineRunner接口 实现CommandLineRunner接口&#xff0c;注意做初始化任务的类需要放在扫描路径下&#xff0c;使用Component注入到spring容器中。 import com.zw.service.StudentService; import org.springframework.beans.factory.annotation.Autowired; impo…

网络安全AI智能体公司「云起无垠」获数千万元天使+轮融资,致力于大模型与网络安全深度融合的技术研究

「云起无垠」致力于打造最懂安全的AI智能体&#xff0c;通过持续运营的工具、知识以及记忆引擎&#xff0c;不断提升智能体对用户安全场景的理解&#xff0c;以达到易于使用、自我学习、自主行动的特性&#xff0c;助力企业自动化执行各类安全任务&#xff0c;让软件更安全&…

在WSL2中安装多个Ubuntu教程

文章目录 前言一、前期准备1、WSL安装2、Docker安装 二、安装第二个Ubuntu系统1.切换为WSL22.获取Ubuntu16.04的tar文件从容器中导出tar 3. 将tar文件导入WSL4. 设置默认用户 总结 前言 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能&#xff0c;可用于在 Wind…

H12-811_19

19.(多选题)如下图所示的网络&#xff0c;下列哪些命令可以使RouterA可以转发目的IP地址为10.0.3.3的效据包? A.ip route-static 10.0.3.3 255.255.255.255 10.0.12.2 B.ip route-static 10.0.2.2 255.255.255.255 10.0.12.2 ip route-static 10.0.3.3 255.255.255.255 10.0…

7、设计模式之桥接模式(Bridge)

一、什么是桥接模式 桥接模式是一种结构型设计模式。它将抽象部分和实现部分分离&#xff0c;使它们可以独立地变化。 二、角色组成 抽象部分&#xff08;Abstraction&#xff09;&#xff1a;定义了抽象部分的接口&#xff0c;并包含对实现部分的引用。 实现部分&#xff08;…