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

大家好,我是程序员牛牛,《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 {@Resourceprivate ApplicationEventPublisher publisher;@Overridepublic 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> {@Overridepublic void onApplicationEvent(EmailSendEvent event) {// 因为此处是同步执行,可以发现,这里收到邮件之后,前面的邮件发送才算完成// 如果需要异步,可以使用注解方式System.out.println("监听器方式,收到邮件:" + event.getAddress() + "," + event.getContent());}}

3.2.4 测试效果

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestEmailDemo {@Autowiredprivate EmailEventPublisher emailEventPublisher;@Testpublic 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.mzph.cn/news/739067.shtml

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

相关文章

【vue.js】文档解读【day 3】 | 条件渲染

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 文章目录 条件渲染前言&#xff1a;v-ifv-elsev-else-iftemplate中的v-ifv-showv-if vs v-show 条件渲染 前言&#xff1a; 在JavaScript中&#xff0c;我们知道条件控制语句可以控制程序的走向&#…

Failed to fetch dynamically imported module错误解决方案

工作需要&#xff0c;需要搬移某个功能代码到去年的分支&#xff0c;结果报了这个错 花了2个多小时排查&#xff0c;最后发现&#xff0c;是某个ts文件没有搬过来 吐血&#xff0c;怎么不直接提示这个文件不存在呢&#xff0c;让我研究了半天

【JAVA】HashMap扩容性能影响及优化策略

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在软件开发中&#xff0c;HashMap是一种常用的数据结构&#xff0c;但在处理大量数据时&#xff0c;其扩容…

SpringBoot配置达梦数据库依赖(达梦8)

maven配置 <!-- 达梦数据库 --><dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.1.193</version></dependency><dependency><groupId>com.alibaba&l…

【力扣 - 最大子数组和】

题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 输出&…

自动化测试过程中的手机验证码处理!

手机验证码登录很普遍了&#xff0c;那么在自动化测试的时候需要登录&#xff0c;登录不了就意味着很多自动化就没法执行下去了。 到底该怎么处理呢&#xff1f;其实并不难&#xff0c;我们先看下验证码的业务逻辑&#xff0c;在我们“点击获取验证码”按钮的时候&#xff0c;…

一学就懂:安装OLED透明屏拼接屏需要注意什么?

安装OLED透明屏拼接屏时&#xff0c;需要注意以下几个方面&#xff1a; 一、前期准备 测量和规划&#xff1a;对安装区域进行详细测量&#xff0c;确保安装区域的尺寸和结构符合OLED透明屏的要求。同时&#xff0c;规划好拼接屏的数量、位置以及布线和固定方案。 环境评估&am…

Weblogic 常规渗透测试环境

测试环境 本环境模拟了一个真实的weblogic环境&#xff0c;其后台存在一个弱口令&#xff0c;并且前台存在任意文件读取漏洞。分别通过这两种漏洞&#xff0c;模拟对weblogic场景的渗透。 Weblogic版本&#xff1a;10.3.6(11g) Java版本&#xff1a;1.6 弱口令 环境启动后…

(golang)切片何时会创建新切片或影响原切片

什么时候切片操作会影响原切片 // 1.切片后没有触发slice的扩容机制时 什么时候对切片操作会创建新切片不影响原切片 // 2.对切片头元素进行截取的时候 // 3.当使用append时&#xff0c;len > cap则会触发扩容机制 前置&#xff1a; //slice结构体 type SliceHeader struct…

指针篇章-(4)+qsort函数的模拟

学习目录 ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————…

​知识图谱:基于嵌入的模型(TransE 、TransH、TransR和TransD)

(一)TransE: Translating Embeddings for Modeling Multi-relational Data. Antoine Bordes, Nicolas Usunier, Alberto Garcia-Duran, Jason Weston, Oksana Yakhnenko. NIPS 2013. 论文地址:http://papers.nips.cc/paper/5071-translating-embeddings-for-modeling-multi-…

[论文笔记]跨语言摘要最新综述:典型挑战及解决方案

https://arxiv.org/abs/2203.12515 跨语言摘要是指为给定的一种语言(例如中文)的文档生成另一种语言(例如英文)的摘要。 图1:四个端到端框架的概述。XLS:跨语言摘要;MT:机器翻译;MS:单语摘要。虚线箭头表示监督信号。无框彩色方块表示相应任务的输入或输出…

Deep Learning for Detecting Robotic Grasps

链接&#xff1a;1301.3592.pdf (arxiv.org) 这个用于从单一RGB-D视图进行机器人抓取检测的算法包括以下步骤&#xff1a; 图像获取&#xff1a; 机器人获取包含待抓取对象的场景的RGB-D图像。 抓取评分&#xff1a; 使用小型深度网络对RGB-D图像中的潜在抓取进行评分。抓取以在…

如何才能做一名渗透测试人员?

学习实践&#xff0c;目前只有这路子&#xff0c;自学9月&#xff0c;成功入圈。下面说一下自己的学习路径&#xff0c;都是摸爬滚打&#xff0c;交了N份钱才学会的。 切记一定要先了解整个渗透测试的流程&#xff0c;记住整个流程口诀&#xff1a;信息收集&打点&#xff…

Linux:进程

进程 知识铺垫冯诺依曼体系结构操作系统&#xff08;OS&#xff09; 进程概念进程的查看ps 命令获取进程 pid文件内查看进程终止进程的方式kill命令快捷键 进程的创建 forkfork 返回值问题 进程状态运行状态 &#xff1a;R休眠状态&#xff1a;S &#xff08;可中断&#xff09…

Python实用工具:三维坐标点的键值对数组的值替换功能

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 代码: # 定义数据列表 data [{x: 9000.00, y: 0.00, z: 28209.83},{x: 8950.70, y: 940.76, z: 28209.83},{x: 8803.33, y: 1871.21, z: 28209.83},{x: 8559.51, y: 2781.15, z: 28209.83},{x: 8221.91, y: …

Vue源码系列讲解——内置组件篇【一】(keep-alive)

目录 1. 前言 2 用法回顾 3. 实现原理 props created destroyed mounted render 4. 生命周期钩子 5. 总结 1. 前言 <keep-alive> 是 Vue 实现的一个内置组件&#xff0c;也就是说 Vue 源码不仅实现了一套组件化的机制&#xff0c;也实现了一些内置组件&#xf…

数据集生成 YOLOV5 可训练的数据目录、并且可视化

1、前言 YOLOV5 训练数据的目录结构如下&#xff1a; 如果有测试集的话&#xff0c;也按照下面目录摆放即可 注意&#xff1a;这里的图片和标签文件名要严格对应&#xff01;&#xff01;后缀除外 关于YOLOv5介绍或者yolo格式的介绍参考之前专栏&#xff0c; 2、划分数据生成…

SpringMVC04、Controller 及 RestFul

4、Controller 及 RestFul 4.1、控制器Controller 控制器复杂提供访问应用程序的行为&#xff0c;通常通过接口定义或注解定义两种方法实现。控制器负责解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器类可以包含多个方法在Spring MVC中&#xff0c;对于Contr…

http协议中的强缓存与协商缓存,带图详解

此篇抽自本人之前的文章&#xff1a;http面试题整理 。 别急着跳转&#xff0c;先把缓存知识学会了~ http中的缓存分为两种&#xff1a;强缓存、协商缓存。 强缓存 响应头中的 status 是 200&#xff0c;相关字段有expires&#xff08;http1.0&#xff09;,cache-control&…