当前位置: 首页 > news >正文

观察者模式原理详解以及Spring源码如何使用观察者模式?

前言

  首先说明观察者模式,观察者模式,也叫发布-订阅模式,应该是用来处理对象间的一对多依赖关系的。当被观察者的状态变化时,所有依赖它的观察者都会得到通知并自动更新。

  观察者模式的结构,通常包括Subject(主题)和Observer(观察者)两个主要角色。Subject维护一个观察者列表,提供注册和移除的方法,还有通知观察者的方法。Observer则有一个更新方法,当Subject状态变化时被调用。可能还有具体的ConcreteSubject和ConcreteObserver,实现具体的业务逻辑。

  Spring框架中有很多地方用到了观察者模式,特别是事件驱动模型。比如ApplicationEvent和ApplicationListener,用户自定义事件,然后通过ApplicationContext发布事件,监听器会接收到这些事件进行处理。这是典型的观察者模式的应用。

  需要详细说明Spring中的事件机制。ApplicationEvent是所有事件的父类,自定义事件需要继承它。ApplicationListener是观察者接口,实现onApplicationEvent方法来处理事件。ApplicationContext作为事件发布者,有publishEvent方法。当事件发布时,所有注册的监听该事件类型的监听器都会被触发。

  另外,Spring的事件传播机制可能涉及到同步和异步处理。默认是同步的,但可以通过配置实现异步监听,比如使用@Async注解或者配置TaskExecutor。

  还涉及Spring内置的一些事件,比如ContextRefreshedEvent、ContextStartedEvent等,这些事件在容器生命周期不同阶段发布,监听器可以响应这些事件执行相应逻辑。

  在本文中会结合源码分析,ApplicationContext的publishEvent方法内部如何获取监听器,如何调用他们的onApplicationEvent方法。可能涉及到ApplicationEventMulticaster这个类,它负责管理监听器和事件广播。具体实现类SimpleApplicationEventMulticaster,使用线程池支持异步事件处理。

  其中,Spring中的观察者模式与传统观察者模式的区别,比如Spring使用ApplicationContext作为事件发布者,而传统模式可能由Subject直接维护观察者列表。此外,Spring支持基于注解的事件监听,比如@EventListener,简化了监听器的注册和使用。

  学完本篇可以在项目中使用观察者模式,比如通过自定义事件和监听器实现解耦的业务逻辑。比如订单创建后发送邮件通知,可以定义一个OrderCreatedEvent,监听器处理发送邮件的逻辑,这样订单服务和邮件服务就解耦了。

  还需要注意可能的问题,比如监听器的执行顺序,异常处理,异步监听时的线程池配置等。这些都是实际使用中需要考虑的。

  总结来说,回答需要分两部分:观察者模式的原理,包括结构、角色和流程;然后是Spring中的具体实现,结合事件机制和源码分析,说明如何利用ApplicationEvent、ApplicationListener以及相关类来实现观察者模式,并给出实际应用示例。


观察者模式原理详解及 Spring 源码应用分析

一、观察者模式(Observer Pattern)原理

核心思想:定义对象间的一对多依赖关系,当一个对象(被观察者/主题)状态发生改变时,所有依赖它的对象(观察者)会自动收到通知并更新。
别名:发布-订阅模式(Pub-Sub)。

1. 模式结构
角色职责
Subject(主题)维护观察者列表,提供注册、移除和通知观察者的方法。
Observer(观察者)定义更新接口,用于接收主题状态变化的通知。
ConcreteSubject具体主题,实现状态变更逻辑,并在状态变化时通知所有观察者。
ConcreteObserver具体观察者,实现更新逻辑以响应主题状态变化。
2. 类图
+----------------+          +----------------+
|   Subject      |          |   Observer     |
+----------------+          +----------------+
| +attach(Observer)| <------| +update():void |
| +detach(Observer)|        +----------------+
| +notify()       |                  ^
+----------------+                   |^                            ||                            |
+----------------+          +------------------+
| ConcreteSubject |          | ConcreteObserver |
+----------------+          +------------------+
| +getState()    |          | +update()        |
| +setState()    |          +------------------+
+----------------+
3. 执行流程
  1. 观察者注册ConcreteObserver 调用 Subject.attach() 注册到主题。
  2. 状态变更触发通知ConcreteSubject 调用 setState() 修改状态后,触发 notify()
  3. 广播通知Subject 遍历观察者列表,调用每个观察者的 update() 方法。

二、Spring 源码中的观察者模式实现

Spring 通过 事件驱动模型 实现观察者模式,核心涉及以下组件:

  • ApplicationEvent:所有事件的基类(如 ContextRefreshedEvent)。
  • ApplicationListener:观察者接口,监听特定事件。
  • ApplicationEventPublisher:主题接口,用于发布事件(由 ApplicationContext 实现)。
  • ApplicationEventMulticaster:事件广播器,管理监听器并分发事件。
1. Spring 事件机制核心流程
1. 定义事件 → 2. 发布事件 → 3. 监听器处理事件
2. 源码解析
(1)事件发布:ApplicationContext.publishEvent()

入口AbstractApplicationContext#publishEvent
流程

public void publishEvent(ApplicationEvent event) {getApplicationEventMulticaster().multicastEvent(event); // 广播事件// 若父容器存在,递归向上传播事件if (this.parent != null) {this.parent.publishEvent(event);}
}
(2)事件广播:ApplicationEventMulticaster

实现类SimpleApplicationEventMulticaster
关键方法multicastEvent()

public void multicastEvent(ApplicationEvent event) {for (ApplicationListener<?> listener : getApplicationListeners(event)) {// 判断是否支持异步执行Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));} else {invokeListener(listener, event); // 同步调用监听器}}
}private void invokeListener(ApplicationListener listener, ApplicationEvent event) {listener.onApplicationEvent(event); // 触发监听器逻辑
}
(3)监听器注册与匹配
  • 注册:Spring 容器启动时,自动扫描所有实现 ApplicationListener 的 Bean。
  • 匹配规则:通过 supportsEventType() 检查监听器是否支持当前事件类型。

示例GenericApplicationListener 判断事件类型:

public boolean supportsEventType(ResolvableType eventType) {return (this.delegate == this || this.delegate.supportsEventType(eventType));
}
3. Spring 内置事件示例
事件类型触发时机
ContextRefreshedEvent容器初始化或刷新完成时(所有 Bean 已加载)。
ContextStartedEvent调用 ConfigurableApplicationContext.start() 时。
ContextStoppedEvent调用 ConfigurableApplicationContext.stop() 时。
RequestHandledEventHTTP 请求处理完成后。

三、Spring 观察者模式的实际应用
1. 自定义事件与监听器

步骤

  1. 定义事件:继承 ApplicationEvent
  2. 定义监听器:实现 ApplicationListener 或使用 @EventListener 注解。
  3. 发布事件:通过 ApplicationContext.publishEvent() 发布。

代码示例

// 1. 自定义事件
public class OrderCreatedEvent extends ApplicationEvent {private String orderId;public OrderCreatedEvent(Object source, String orderId) {super(source);this.orderId = orderId;}public String getOrderId() { return orderId; }
}// 2. 监听器实现(注解方式)
@Component
public class EmailNotificationListener {@EventListenerpublic void handleOrderCreatedEvent(OrderCreatedEvent event) {System.out.println("发送邮件通知,订单ID: " + event.getOrderId());}
}// 3. 发布事件
@Service
public class OrderService {@Autowiredprivate ApplicationContext applicationContext;public void createOrder(String orderId) {// 业务逻辑...applicationContext.publishEvent(new OrderCreatedEvent(this, orderId));}
}
2. 异步事件处理

配置异步监听器

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {return Executors.newFixedThreadPool(5);}
}// 使用 @Async 注解异步处理
@Component
public class LoggingListener {@Async@EventListenerpublic void asyncLogOrderEvent(OrderCreatedEvent event) {System.out.println("异步记录日志,订单ID: " + event.getOrderId());}
}
3. 监听器执行顺序

通过 @Order 注解控制监听器执行顺序:

@EventListener
@Order(1)
public void firstListener(OrderCreatedEvent event) {System.out.println("第一个执行");
}@EventListener
@Order(2)
public void secondListener(OrderCreatedEvent event) {System.out.println("第二个执行");
}

四、Spring 观察者模式的设计优势
  1. 解耦业务逻辑
    事件发布者与监听者无需直接交互,例如订单服务不依赖邮件服务。
  2. 扩展性强
    新增监听器只需实现接口或添加注解,无需修改发布者代码。
  3. 支持异步处理
    通过 TaskExecutor 实现异步事件处理,提升系统吞吐量。
  4. 事件传播机制
    支持事件在容器层次结构中向上传播(如父子容器)。

五、总结
  • 观察者模式核心:解耦主题与观察者,通过事件通知机制实现松耦合通信。
  • Spring 实现:基于 ApplicationEventApplicationListenerApplicationEventMulticaster 构建事件驱动模型。
  • 应用场景
    • 业务流程解耦(如订单创建后发送通知、记录日志)。
    • 系统生命周期管理(如容器启动后加载缓存)。
    • 异步任务触发(如消息队列事件处理)。

源码设计亮点

  • 事件类型匹配:通过 ResolvableType 动态解析事件类型,支持泛型事件。
  • 灵活广播策略SimpleApplicationEventMulticaster 支持同步/异步事件分发。
  • 注解驱动简化@EventListener@Async 简化监听器定义与异步配置。

相关文章:

观察者模式原理详解以及Spring源码如何使用观察者模式?

前言 首先说明观察者模式&#xff0c;观察者模式&#xff0c;也叫发布-订阅模式&#xff0c;应该是用来处理对象间的一对多依赖关系的。当被观察者的状态变化时&#xff0c;所有依赖它的观察者都会得到通知并自动更新。 观察者模式的结构&#xff0c;通常包括Subject&#xff0…...

【Spring】Spring配置文件

目录 ​什么是配置文件&#xff1f; 配置文件的作用 SpringBoot配置文件 配置文件格式 配置文件的优先级 properties配置文件说明 properties基本语法 读取配置文件 properties缺点 yml配置文件说明 yml基本语法 使用yml连接数据库 yml配置不同数据类型及null 注意…...

MSI微星电脑冲锋坦克Pro Vector GP76 12UGS(MS-17K4)原厂Win11系统恢复镜像,含还原功能,预装OEM系统下载

适用机型&#xff1a;【MS-17K4】 链接&#xff1a;https://pan.baidu.com/s/1P8ZgXc6S_J9DI8RToRd0dQ?pwdqrf1 提取码&#xff1a;qrf1 微星笔记本原装出厂WINDOWS11系统自带所有驱动、出厂主题壁纸、系统属性专属联机支持标志、Office办公软件、MSI Center控制中心等预装…...

Unity合批处理优化内存序列帧播放动画

Unity合批处理序列帧优化内存 介绍图片导入到Unity中的处理Unity中图片设置处理Unity中图片裁剪 创建序列帧动画总结 介绍 这里是针对Unity序列帧动画的优化内容&#xff0c;将多个图片合批处理然后为了降低Unity的内存占用&#xff0c;但是相对的质量也会稍微降低。可自行进行…...

【Java】逻辑运算符详解:、|| 与、 | 的区别及应用

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: Java 文章目录 &#x1f4af;前言&#x1f4af;一、基本概念与运算符介绍&#x1f4af;二、短路与与非短路与&#xff1a;&& 与 & 的区别1. &&&#xff1a;短路与&#xff08;AND&#xff09;2. …...

深入解析 Flutter GetX

深入解析 Flutter GetX&#xff1a;从原理到实战 GetX 是 Flutter 中一个轻量级且功能强大的状态管理、路由管理和依赖注入框架。它以简单、快速、高效著称&#xff0c;适合从小型到大型项目的开发需求。GetX 的设计理念是一体化解决方案&#xff0c;通过一个框架解决状态管理…...

Java 大视界 -- 人才需求与培养:Java 大数据领域的职业发展路径(92)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

顺序表常用操作和笔试题

1、顺序表的常用操作 1.1 顺序表的创建 如下代码所示&#xff1a;创建了一个默认空间为10的整型顺序表&#xff0c;如果空间不足则会以1.5倍扩容。 List<Integer> list new ArrayList<>(); 创建一个空间为15的整型顺序表 List<Integer> list2 new ArrayL…...

List<Map<String, Object>> 如何对某个字段求和

在Java中&#xff0c;如果你有一个List<Map<String, Object>>的结构&#xff0c;并且你想要对某个特定字段进行求和&#xff0c;你可以使用Java 8的Stream API来简化这个过程。下面是一个示例代码&#xff0c;演示如何对某个字段进行求和。 假设你有一个List<M…...

2024亚马逊数据分析!

整体财务数据23 净销售额&#xff1a;全年净销售额达 6380 亿美元&#xff0c;同比增长 11%。 净利润&#xff1a;全年净利润为 592 亿美元&#xff0c;较上年同期的 304 亿美元增长 95%。 经营活动现金流&#xff1a;经营活动现金流达 1159 亿美元&#xff0c;同比增加了 36…...

foobar2000设置DSP使用教程及软件推荐

foobar2000安卓中文版&#xff1a;一款高品质手机音频播放器 foobar2000安卓中文版是一款备受好评的高品质手机音频播放器。 几乎支持所有的音频格式&#xff0c;包括 MP3、MP4、AAC、CD 音频等。不论是经典老歌还是最新的流行音乐&#xff0c;foobar2000都能完美播放。除此之…...

Apache Logic4j 库反序列化漏洞复现与深度剖析

前言 在渗透测试领域&#xff0c;反序列化漏洞一直是安全研究人员和攻击者关注的焦点。今天&#xff0c;我们将深入探讨 Apache Logic4j 库中的反序列化漏洞&#xff0c;详细了解其原理&#xff0c;并进行完整的复现演示。 一、漏洞原理 Apache Logic4j 库在处理对象的反序列…...

FPGA VIVADO:axi-lite 从机和主机

FPGA VIVADO:axi-lite 从机和主机 TOC在这里插入代码片 前言 协议就不详细讲解了&#xff0c;直接看手册即可。下面主要如何写代码和关键的时序。 此外下面的代码可以直接用于实际工程 一、AXI-LITE 主机 数据转axi lite接口&#xff1a; 读/写数据FIFO缓存 仲裁&#xff1a…...

LabVIEW 中的 3dgraph.llb库

3dgraph.llb 库位于C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform目录下&#xff0c;是 LabVIEW 系统里用于 3D 图形相关操作的关键库。它为 LabVIEW 用户提供众多功能&#xff0c;可在应用程序内创建、显示和交互各类 3D 图形&#xff0c;极大增…...

【Linux】文件系统:文件fd

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;linux笔记仓 目录 01.回顾C文件接口02.系统文件I/O02.1 openflags 参数&#xff08;文件打开模式&#xff09;标记位传参1. 访问模式&#xff08;必须指定一个&#xff09;2. 额外控制标志&#xf…...

Vue学习记录19

TransitonGroup <TransitionGroup> 是一个内置组件&#xff0c;用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。 和 <Transtion> 的区别 <TranstionGroup> 支持和 <Transtion> 基本相同的 props、CSS过渡 class 和 JavaScript…...

MATLAB更改图论的布局:设置layout

在图论那一章&#xff0c;我们讲过最小生成树和单源最短路径&#xff08;见&#xff1a;从零开始学数学建模&#xff09;&#xff1a; 以最短路径那节课为例&#xff0c;把绘图pplot那部分代码写为&#xff1a; % plot绘图有很多参数可以设置&#xff0c;使图尽量美观 P plot…...

【分果果——DP(困难)】

题目 分析 分果果题解参考&#xff0c;下面是补充https://blog.csdn.net/AC__dream/article/details/129431299 关于状态 设f[i][j][k]表示第i个人取到的最后一个糖果编号是j&#xff0c;第i-1个人取到的最后一个糖果编号小于等于k时的最大重量的最小值 关于转移方程 关于 j …...

禁止WPS强制打开PDF文件

原文网址&#xff1a;禁止WPS强制打开PDF文件_IT利刃出鞘的博客-CSDN博客 简介 本文介绍如何避免WPS强制打开PDF文件。 方法 1.删除注册表里.pdf的WPS绑定 WinR&#xff0c;输入&#xff1a;regedit&#xff0c;回车。找到&#xff1a;HKEY_CLASSES_ROOT\.pdf删除KWPS.PDF…...

罗技鼠标接收器丢了,怎么用另一个logi接收器重新配对?

1.首先接收器得是logi的&#xff0c;其次看这个接收器是什么类型的&#xff0c;一共有以下3种。&#xff08;这几种接收器都可以给其他logi鼠标用&#xff09; 下图左侧带红标的这个&#xff08;标可能带颜色或者是透明&#xff0c;都一样&#xff09;&#xff0c;叫多设备接收…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...

raid存储技术

1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划&#xff0c;涵盖存储系统的布局、数据存储策略等&#xff0c;它明确数据如何存储、管理与访问&#xff0c;为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...

codeforces C. Cool Partition

目录 题目简述&#xff1a; 思路&#xff1a; 总代码&#xff1a; https://codeforces.com/contest/2117/problem/C 题目简述&#xff1a; 给定一个整数数组&#xff0c;现要求你对数组进行分割&#xff0c;但需满足条件&#xff1a;前一个子数组中的值必须在后一个子数组中…...