沉梦听雨的编程指南 沉梦听雨的编程指南
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • RPC
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 多媒体框架
  • 虚拟机
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Axios入门
  • Vue Router入门
  • Pinia入门
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)

沉梦听雨

时间是最好的浸渍剂,而沉淀是最好的提纯器🚀
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • RPC
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 多媒体框架
  • 虚拟机
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Axios入门
  • Vue Router入门
  • Pinia入门
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)
  • API网关

  • RPC

  • 消息队列

    • 内存

      • SpringBoot事件机制
        • 使用背景
        • 简单介绍
        • 观察者模式和发布订阅模式有什么不同?
          • 观察者模式(Observer Pattern)
          • 发布订阅模式(Publish-Subscribe Pattern)
          • 关键区别
        • 代码示例
          • 引入依赖
          • 开启异步
          • 1、事件类
          • 2、事件发布者类
          • 3、事件监听器类
          • 客户端
        • 设置监听顺序
        • 学习参考
    • Redis

    • RocketMQ

    • Kafka

  • Spring Cloud

  • 分布式事务

  • 云存储

  • 搜索引擎

  • 多媒体框架

  • 虚拟机

  • 微服务
  • 消息队列
  • 内存
沉梦听雨
2024-12-31
目录

SpringBoot事件机制

# SpringBoot 事件机制

# 使用背景

考虑到部分项目对消息队列的要求不高,又不想引入额外部署的消息队列,这时候就可以使用 Spring Event 实现【内存】级别的消息队列。

# 简单介绍

Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:

  1. 事件 ApplicationEvent (opens new window):通过继承它,实现自定义事件。另外,通过它的 source 属性可以获取事件源,timestamp 属性可以获得发生时间。
  2. 事件发布者 ApplicationEventPublisher (opens new window):通过它,可以进行事件的发布。
  3. 事件监听器 ApplicationListener (opens new window):通过实现它,进行指定类型的事件的监听。

友情提示:JDK 也内置了事件机制的实现,考虑到通用性,Spring 的事件机制是基于它之上进行拓展。因此,

  • ApplicationEvent 继承自 java.util.EventObject (opens new window),
  • ApplicationListener 继承自 java.util.EventListener (opens new window)。

# 观察者模式和发布订阅模式有什么不同?

# 观察者模式(Observer Pattern)

在我们日常业务开发中,观察者模式对我们很大的一个作用,在于实现业务的解耦。例如用户注册的场景。

定义

观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。

组成部分

  • Subject(主题): 被观察的对象,维护一个观察者列表,并在自身状态改变时通知所有观察者。
  • Observer(观察者): 接收主题通知并更新自身状态的对象。

实现

  • 主题对象直接持有观察者的引用。
  • 主题对象负责通知所有观察者。

# 发布订阅模式(Publish-Subscribe Pattern)

定义

发布订阅模式是一种消息传递模式,发布者将消息发布到特定的通道,不直接与订阅者进行通信。订阅者订阅一个或多个通道,并接收来自这些通道的消息。

组成部分

  • Publisher(发布者): 生产消息的对象,将消息发布到某个通道。
  • Subscriber(订阅者): 消费消息的对象,订阅某个通道以接收消息。
  • Message Broker(消息代理): 管理发布者和订阅者之间的通信,确保消息从发布者传递到正确的订阅者。

实现

  • 发布者和订阅者通过消息代理进行通信,解耦了两者之间的直接依赖。
  • 消息代理负责管理和分发消息。

# 关键区别

  1. 耦合性:
    • 观察者模式: 观察者和主题之间存在直接依赖,观察者必须知道主题的存在。
    • 发布订阅模式: 发布者和订阅者之间没有直接依赖关系,消息代理负责两者的通信。
  2. 通信方向:
    • 观察者模式: 通知是从主题到观察者的单向通知。
    • 发布订阅模式: 通信可以是多向的,多个发布者和订阅者通过消息代理进行交互。
  3. 应用场景:
    • 观察者模式: 适用于对象之间存在明确依赖关系的场景,如 GUI 事件处理。
    • 发布订阅模式: 适用于需要解耦发布者和订阅者的场景,如消息队列系统。

简单来说:

  • 发布订阅模式 中间多了一个 事件通道,以此避免了发布者和订阅者之间产生的依赖关系。

更多参考:

  • 观察者模式和发布订阅模式有什么不同? - 知乎 (opens new window)

# 代码示例

# 引入依赖

        <!-- springboot web 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
1
2
3
4
5

# 开启异步

启动类上添加 @EnableAsync 注解,开启 Spring 异步的功能。

# 1、事件类

/**
 * 用户注册事件
 *
 * @author chenmeng
 */
@Getter
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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2、事件发布者类

/**
 * 事件发布者类
 * 
 * @author chenmeng
 */
@Service
public class UserService implements ApplicationEventPublisherAware {

    private final 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);

        // ... 发布
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 3、事件监听器类

/**
 * 事件监听器类(不需实现接口版)
 *
 * @author chenmeng
 */
@Service
public class CouponService {

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

    @Order(1)
    @EventListener
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[事件监听-addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }
}

/**
 * 事件监听器类(实现接口版)
 *
 * @author chenmeng
 */
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {

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

    @Override
    @Async // 可以不加,锦上添花,设置 @Async 注解,声明异步执行。毕竟实际场景下,发送邮件可能比较慢,又是非关键逻辑。
    public void onApplicationEvent(UserRegisterEvent event) {
        logger.info("[事件监听-onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
    }
}

/**
 * 事件监听器类(不需实现接口版)
 *
 * @author chenmeng
 */
@Service
public class InfoService {

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

    @Order(2)
    @EventListener
    public void sendInfo(UserRegisterEvent event) {
        logger.info("[事件监听-sendInfo][给用户({}) 发送信息]", event.getUsername());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 客户端

/**
 * @author chenmeng
 */
@RestController
@RequestMapping("/demo")
@RequiredArgsConstructor
public class DemoController {

    private final UserService userService;

    @GetMapping("/register")
    public String register(String username) {
        userService.register(username);
        return "success";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

测试结果:

2024-12-31 14:29:15.712  INFO 25272 --- [nio-9111-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2024-12-31 14:29:15.741  INFO 25272 --- [nio-9111-exec-2] c.chenmeng.project.service.UserService   : [事件发布-register][执行用户(张三) 的注册逻辑]
2024-12-31 14:29:15.742  INFO 25272 --- [nio-9111-exec-2] c.c.project.service.CouponService        : [事件监听-addCoupon][给用户(张三) 发放优惠劵]
2024-12-31 14:29:15.742  INFO 25272 --- [nio-9111-exec-2] c.chenmeng.project.service.InfoService   : [事件监听-sendInfo][给用户(张三) 发送信息]
2024-12-31 14:29:15.744  INFO 25272 --- [         task-1] c.chenmeng.project.service.EmailService  : [事件监听-onApplicationEvent][给用户(张三) 发送邮件]
1
2
3
4
5

# 设置监听顺序

如果想要多个监听器按照指定顺序执行,可在监听方法上加上 @Order() 注解。

注意事项:

  • 标上异步注解 @Async 的方法,使用会无效(即异步方法无法排序)
  • 若需排序,必须都使用添加 @EventListener 注解的形式类创建监听器类(另一种写法创建的监听器,笔者测试 @Order() 发现无效)

# 学习参考

  • 芋道 Spring Boot 事件机制 Event 入门 | 芋道源码 —— 纯源码解析博客 (opens new window)
上次更新: 2024/12/31 17:34:27
讲讲五种通信方式的区别
基于Redis实现消息队列

← 讲讲五种通信方式的区别 基于Redis实现消息队列→

Theme by Vdoing | Copyright © 2023-2025 沉梦听雨 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式