Spring 事件机制与观察者模式的深度解析

news2025/4/16 4:44:51

一、引言

在软件设计中,观察者模式(Observer Pattern)是一种非常经典且实用的设计模式。它允许一个对象(Subject)在状态发生改变时通知所有依赖它的对象(Observers),从而实现对象之间的解耦。本文将深入探讨观察者模式的基本概念,并结合 Spring 事件机制 进行实战分析,帮助大家更好地理解和应用这一模式。

二、观察者模式基础

2.1 观察者模式定义

观察者模式是一种行为型设计模式,其核心思想是:一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常通过调用各观察者所提供的方法来实现。

2.2 观察者模式应用场景

观察者模式广泛应用于需要实时事件处理的系统中。例如,在用户注册场景中,当用户完成注册后,系统需要执行一系列操作,如发送邮件、发放优惠券等。此时,可以利用观察者模式来解耦这些操作,使得系统更加灵活和可扩展。

2.3 观察者模式 vs 发布订阅模式

观察者模式和发布订阅模式常常被一起对比。简单来说,发布订阅模式属于广义上的观察者模式,在观察者模式的 Subject 和 Observer 的基础上,引入了 Event Channel 这个中介,进一步解耦。
在这里插入图片描述

  • 观察者模式:Subject 直接管理 Observers,状态变化时直接通知。
  • 发布订阅模式:Publisher 将事件发布到 Event Channel,Subscriber 订阅该通道,由通道负责通知。

三、Spring 事件机制详解

Spring 框架基于观察者模式实现了自身的事件机制,主要包括三个部分:

  1. 事件(ApplicationEvent):通过继承 ApplicationEvent 实现自定义事件。
  2. 事件发布者(ApplicationEventPublisher):用于发布事件。
  3. 事件监听器(ApplicationListener):用于监听并处理特定类型的事件。

3.1 事件(ApplicationEvent)

ApplicationEvent 是 Spring 事件的基类,可以通过继承它来定义自定义事件。例如,我们可以定义一个 UserRegisterEvent 来表示用户注册事件:

public class UserRegisterEvent extends ApplicationEvent {
    private String username;

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

3.2 事件发布者(ApplicationEventPublisher)

ApplicationEventPublisher 用于发布事件。我们可以通过实现 ApplicationEventPublisherAware 接口来获取 ApplicationEventPublisher 实例,并在适当的时候发布事件。

@Service
public class UserService implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;

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

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

        // 发布事件
        publisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

3.3 事件监听器(ApplicationListener)

ApplicationListener 用于监听并处理特定类型的事件。我们可以通过实现 ApplicationListener 接口或使用 @EventListener 注解来定义监听器。

实现 ApplicationListener 接口

@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {
    @Async
    public void onApplicationEvent(UserRegisterEvent event) {
        logger.info("给用户 {} 发送欢迎邮件", event.getUsername());
    }
}

使用 @EventListener 注解

@Service
public class CouponService {
    @EventListener
    public void addCoupon(UserRegisterEvent event) {
        logger.info("给用户 {} 发放优惠券", event.getUsername());
    }
}

四、实战示例:用户注册场景

4.1 传统方式 vs 观察者模式

在用户注册场景中,传统方式通常会将所有相关操作(如发送邮件、发放优惠券)直接写在注册逻辑中,导致代码耦合度高,不易维护。
在这里插入图片描述
而使用观察者模式后,UserService 在完成用户注册逻辑后,只需发布一个 UserRegisterEvent 事件,其他服务(如 EmailService、CouponService)可以自行订阅该事件,实现自定义的拓展逻辑。

4.2 完整示例代码

4.2.1 引入依赖

在 pom.xml 中引入 spring-boot-starter-web 依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

4.2.2 定义事件

创建 UserRegisterEvent 事件类:

public class UserRegisterEvent extends ApplicationEvent {
    private String username;

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

4.2.3 用户服务(发布事件)

创建 UserService 类:

@Service
public class UserService implements ApplicationEventPublisherAware {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private ApplicationEventPublisher publisher;

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

    public void register(String username) {
        logger.info("用户 {} 注册成功", username);
        publisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

4.2.4 邮件服务(监听事件)

创建 EmailService 类:

@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Async
    public void onApplicationEvent(UserRegisterEvent event) {
        logger.info("给用户 {} 发送欢迎邮件", event.getUsername());
    }
}

4.2.5 优惠券服务(监听事件)

创建 CouponService 类:

@Service
public class CouponService {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener
    public void addCoupon(UserRegisterEvent event) {
        logger.info("给用户 {} 发放优惠券", event.getUsername());
    }
}

4.2.6 控制器

创建 DemoController 类:

@RestController
@RequestMapping("/demo")
public class DemoController {
    @Autowired
    private UserService userService;

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

4.2.7 启动类

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

4.3 测试验证

启动项目后,访问 http://127.0.0.1:8080/demo/register?username=yudaoyuanma,控制台输出如下:

2020-04-06 13:09:39.145  INFO 18615 --- [nio-8080-exec-1] c.i.s.l.eventdemo.service.UserService    : [register][执行用户(yudaoyuanma) 的注册逻辑]
2020-04-06 13:09:39.147  INFO 18615 --- [nio-8080-exec-1] c.i.s.l.eventdemo.service.CouponService  : [addCoupon][给用户(yudaoyuanma) 发放优惠劵]
2020-04-06 13:09:39.154  INFO 18615 --- [         task-1] c.i.s.l.eventdemo.service.EmailService   : [onApplicationEvent][给用户(yudaoyuanma) 发送邮件]

五、总结与展望

通过本文的介绍,相信大家对观察者模式和 Spring 事件机制有了更深入的理解。观察者模式不仅能够实现业务解耦,还能提高系统的灵活性和可扩展性。在实际开发中,合理运用这一模式,可以使我们的代码更加优雅和高效。

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

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

相关文章

【软考系统架构设计师】信息安全技术基础知识点

1、 信息安全包括5个基本要素&#xff1a;机密性、完整性、可用性、可控性与可审查性。 机密性&#xff1a;确保信息不暴露给未授权的实体或进程。&#xff08;采取加密措施&#xff09; 完整性&#xff1a;只有得到允许的人才能修改数据&#xff0c;并且能够判断出数据是否已…

2025年第十六届蓝桥杯省赛真题解析 Java B组(简单经验分享)

之前一年拿了国二后&#xff0c;基本就没刷过题了&#xff0c;实力掉了好多&#xff0c;这次参赛只是为了学校的加分水水而已&#xff0c;希望能拿个省三吧 >_< 目录 1. 逃离高塔思路代码 2. 消失的蓝宝思路代码 3. 电池分组思路代码 4. 魔法科考试思路代码 5. 爆破思路…

01-算法打卡-数组-二分查找-leetcode(704)-第一天

1 数组基础理论 数组是存放在连续内存空间上的相同数据结构的集合。数组可以通过下标索引快速获取数据&#xff0c;因为数组的存储空间是连续的所以在删除、更新数据的时候需要移动其他元素的地址。 下图是一个数组的案例图形&#xff1a;【内存连续、索引小标从0开始可…

怎么看英文论文 pdf沉浸式翻译

https://arxiv.org/pdf/2105.09492 Immersive Translate Xournal打开

RabbitMQ 深度解析:从基础到高级应用的全面指南

&#x1f430; RabbitMQ 深度解析&#xff1a;从基础到高级应用的全面指南 前言&#x1f4d8; 一、RabbitMQ 简介⚙️ 二、核心特性可靠性 &#x1f512;灵活路由 &#x1f504;高可用性 &#x1f310;多协议支持 &#x1f30d;多语言客户端 &#x1f4bb;插件机制 &#x1f50…

【图灵Python爬虫逆向】题七:千山鸟飞绝

题目背景 题目地址&#xff1a;https://stu.tulingpyton.cn/problem-detail/7/ 这一题为中等难度 打开控制台时会发现进入无限debug&#xff0c;可以通过右键点击"一律不在此处暂停"来绕过这个障碍。 一、请求与响应分析 1. 请求参数分析 首先观察网络请求&…

ubuntu 2404 安装 vcs 2018

参考ubuntu 2204 安装 vcs 2018 系统信息 Ubuntu 24.04.2 LTS ubuntu和 安装后的 vcs 花费了 22G , 其中 "安装后的 vcs" 占13G预先配置 过程 和 2204 安装 vcs 2018 不同, 其他相同 // vm-tools 的安装, 不是虚拟机不需要 sudo apt-get update sudo apt-get inst…

潇洒浪: Dify 上传自定义文件去除内容校验 File validation failed for file: re.json

Dify上传文件 添加其他文件类型如 my.myselfsuffix 上传成功 执行报错 File validation failed for file: re.json 解决办法 Notepad++ 搜索dify源码

python-66-前后端分离之图书管理系统的Vue前端项目逐行分析

文章目录 1 App.vue的数据表格1.1 template部分1.1.1 div标签1.1.2 h1标签1.1.3 el-button标签1.1.4 el-table标签1.1.5 el-table-column标签1.1.6 表格中放置按钮1.2 script部分1.2.1 加载库和函数1.2.2 创建响应式数组1.2.3 创建getBooks函数1.2.4 onMounted函数1.2.5 创建ha…

【实战手册】8000w数据迁移实践:MySQL到MongoDB的完整解决方案

🔥 本文将带你深入解析大规模数据迁移的实践方案,从架构设计到代码实现,手把手教你解决数据迁移过程中的各种挑战。 📚博主其他匠心之作,强推专栏: 小游戏开发【博主强推 匠心之作 拿来即用无门槛】文章目录 一、场景引入1. 问题背景2. 场景分析为什么需要消息队列?为…

OpenAI为抢跑AI,安全底线成牺牲品?

几年前&#xff0c;如果你问任何一个AI从业者&#xff0c;安全测试需要多长时间&#xff0c;他们可能会淡定地告诉你&#xff1a;“至少几个月吧&#xff0c;毕竟这玩意儿可能改变世界&#xff0c;也可能毁了它。”而现在&#xff0c;OpenAI用实际行动给出了一个新答案——几天…

OpenCV 图形API(25)图像滤波-----均值滤波(模糊处理)函数blur()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用归一化的方框滤波器模糊图像。 该函数使用以下核来平滑图像&#xff1a; K 1 k s i z e . w i d t h k s i z e . h e i g h t [ 1 1 ⋯ …

轻量级开源文件共享系统PicoShare本地部署并实现公网环境文件共享

&#xfeff;## 前言 本篇文章介绍&#xff0c;如何在 Linux 系统本地部署轻量级文件共享系统 PicoShare&#xff0c;并结合 Cpolar 内网穿透实现公网环境远程传输文件至本地局域网内文件共享系统。 PicoShare 是一个由 Go 开发的轻量级开源共享文件系统&#xff0c;它没有文…

UE5蓝图之间的通信------接口

一、创建蓝图接口 二、双击创建的蓝图接口&#xff0c;添加函数&#xff0c;并重命名新函数。 三、在一个蓝图&#xff08;如玩家角色蓝图&#xff09;中实现接口&#xff0c;如下图&#xff1a; 步骤一&#xff1a;点击类设置 步骤二&#xff1a;在细节面板已经实现的接口中…

银河麒麟服务器操作系统V10安装Nvidia显卡驱动和CUDA(L40)并安装ollama运行DeepSeek【开荒存档版】

前期说明 麒麟官方适配列表查找没有L40,只有海光和兆芯适配麒麟V10,不适配Intel芯片 但是我在英伟达驱动列表查到是适配的! 反正都算是X86,我就直接开始干了,按照上面安装系统版本为:银河麒麟kylinos V10 sp3 2403 输入nkvers可以查看麒麟系统具体版本: 安装Nvid…

学习八股的随机思考

随时有八股思考都更新一下&#xff0c;理解的学一下八股。谢谢大家的阅读&#xff0c;有错请大家指出。 bean的生命周期 实际上只有四步 实例化 ----> 属性赋值 ---> 初始化 ---> 销毁 但是在实例化前后 初始化前后 会存在一些前置后置的处理&#xff0c;目的是增…

山东大学软件学院创新项目实训开发日志(10)之测试前后端连接

在正式开始前后端功能开发前&#xff0c;在队友的帮助下&#xff0c;成功完成了前后端测试连接&#xff1a; 首先在后端编写一个测试相应程序&#xff1a; 然后在前端创建vue 并且在index.js中添加一下元素&#xff1a; 然后进行测试&#xff0c;测试成功&#xff1a; 后续可…

AUTO-RAG: AUTONOMOUS RETRIEVAL-AUGMENTED GENERATION FOR LARGE LANGUAGE MODELS

Auto-RAG&#xff1a;用于大型语言模型的自主检索增强生成 单位&#xff1a;中科院计算所 代码&#xff1a; https://github.com/ictnlp/Auto-RAG 拟解决问题&#xff1a;通过手动构建规则或者few-shot prompting产生的额外推理开销。 贡献&#xff1a;提出一种以LLM决策为中…

基础贪心算法集合2(10题)

目录 1.单调递增的数字 2.坏了的计算器 3.合并区间 4.无重叠区间 5. 用最少数量的箭引爆气球 6.整数替换 解法1&#xff1a;模拟记忆化搜索 解法2位运算贪心 7.俄罗斯套娃信封问题 补充.堆箱子 8.可被3整除的最大和 9.距离相等的条形码 10.重构字符串 1.单调递增的数字…