文章目录
- 一、概述
-
- [1.1 结构与角色](#1.1 结构与角色)
- [1.2 适用场景](#1.2 适用场景)
- 二、实现方式
-
- [2.1 自定义实现](#2.1 自定义实现)
-
- [2.1.1 推模型与拉模型](#2.1.1 推模型与拉模型)
- [2.2 JDK 内置 Observer](#2.2 JDK 内置 Observer)
-
- [2.2.1 基本使用](#2.2.1 基本使用)
- [2.2.2 废弃原因分析](#2.2.2 废弃原因分析)
- [2.3 自定义实现 vs JDK 内置 Observer](#2.3 自定义实现 vs JDK 内置 Observer)
- [三、Spring 事件机制](#三、Spring 事件机制)
-
- [3.1 核心组件](#3.1 核心组件)
- [3.2 实战示例------订单事件系统](#3.2 实战示例——订单事件系统)
- [3.3 @EventListener 注解方式](#3.3 @EventListener 注解方式)
- [3.4 ApplicationListener 接口 vs @EventListener 注解](#3.4 ApplicationListener 接口 vs @EventListener 注解)
- [3.5 Spring 内置事件](#3.5 Spring 内置事件)
- 四、总结
一、概述
在软件开发中,经常会遇到这样的场景:一个对象的状态发生改变时,需要通知其他依赖它的对象自动更新。例如,天气预报系统中,气象站数据更新后需要通知多个显示终端;股票行情系统中,股价波动后需要通知多个投资者;消息推送系统中,有新消息时需要通知所有订阅者。如果让被观察对象直接调用每个依赖对象的方法,就会产生强耦合 ------每新增一个依赖对象,就要修改被观察对象的代码,违反了开闭原则:
直接调用
直接调用
直接调用
新增调用???
被观察对象
显示终端1
显示终端2
显示终端3
显示终端N
每新增一个依赖对象就要修改被观察对象代码
观察者模式(Observer Pattern)正是为了解决这个问题而诞生的------它定义对象之间的一种一对多的依赖关系,使得每当一个对象的状态发生变化时,其相关依赖的对象皆得到通知并自动更新。观察者模式又称为"发布-订阅模式"(Publish-Subscribe Pattern),是使用频率最高的设计模式之一。
生活中的观察者模式例子:
- 天气预报系统:气象站(被观察者)发布天气数据,多个显示终端(观察者)接收并展示,新增显示终端无需修改气象站代码
- 股票行情系统:股价(被观察者)波动时通知所有订阅了该股票的投资者(观察者)
- 消息推送系统:公众号(被观察者)发布新文章,所有关注者(观察者)收到推送通知
- GUI 事件监听:按钮(被观察者)被点击时通知所有注册的监听器(观察者)
核心:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新
1.1 结构与角色
观察者模式包含以下角色:
持有
继承
实现
通知
持有引用
Client 客户端
Subject 抽象主题
Observer 抽象观察者
ConcreteSubject 具体主题
ConcreteObserver 具体观察者
- Subject(抽象主题):持有观察者对象的集合,提供添加和删除观察者对象的接口,同时提供通知所有观察者的接口
- ConcreteSubject(具体主题):实现抽象主题接口,内部状态发生改变时,给所有注册过的观察者发出通知
- Observer(抽象观察者):定义一个更新接口,在得到主题的更改通知时更新自身
- ConcreteObserver(具体观察者):实现抽象观察者定义的更新接口,在得到主题更改通知时更新自身的状态
- Client(客户端):创建具体主题和具体观察者对象,并将观察者注册到主题中
1.2 适用场景
- 一个对象的改变将导致一个或多个其他对象也发生改变,且不知道具体有多少对象需要改变
- 一个对象必须通知其他对象,但不希望与被通知的对象形成紧耦合
- 需要在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为又将影响 C 对象
- 事件驱动系统的底层机制,如 GUI 事件处理、消息中间件等
二、实现方式
观察者模式有两种常见的实现方式:自定义实现 和JDK 内置 Observer。前者灵活可控,后者开箱即用但已废弃。下面分别进行详细讲解。
2.1 自定义实现
以"天气预报系统"为例,气象站发布天气数据(温度、湿度、气压),多个显示终端(当前天气、天气统计、天气预报)订阅并展示:
持有
实现
实现
实现
通知
客户端
WeatherData 具体主题
Observer 抽象观察者
CurrentCondition 当前天气
StatisticsDisplay 天气统计
ForecastDisplay 天气预报
(1)抽象观察者------观察者接口
java
/**
* 抽象观察者:定义更新接口
* 当收到主题通知时,调用此方法更新自身状态
*/
public interface Observer {
/**
* 更新观察者状态
*
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
void update(float temperature, float humidity, float pressure);
}
(2)抽象主题------被观察者接口
java
/**
* 抽象主题:定义注册、移除、通知观察者的接口
*/
public interface Subject {
/**
* 注册观察者
*
* @param observer 观察者
*/
void registerObserver(Observer observer);
/**
* 移除观察者
*
* @param observer 观察者
*/
void removeObserver(Observer observer);
/**
* 通知所有观察者
*/
void notifyObservers();
}
(3)具体主题------气象站数据
java
import java.util.ArrayList;
import java.util.List;
/**
* 具体主题:气象站数据
* 维护观察者列表,当天气数据变化时通知所有观察者
*/
public class WeatherData implements Subject {
private final List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
@Override
public void registerObserver(Observer observer) {
if (!observers.contains(observer)) {
observers.add(observer);
}
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
/**
* 设置天气数据(模拟气象站数据更新)
*
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
// 数据变化后通知所有观察者
notifyObservers();
}
}
(4)具体观察者------当前天气显示
java
/**
* 具体观察者:当前天气显示
* 显示当前温度、湿度
*/
public class CurrentConditionDisplay implements Observer {
private float temperature;
private float humidity;
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
/**
* 显示当前天气
*/
public void display() {
System.out.println("当前天气:温度 " + temperature + "°C,湿度 " + humidity + "%");
}
}
(5)具体观察者------天气统计显示
java
import java.util.ArrayList;
import java.util.List;
/**
* 具体观察者:天气统计显示
* 统计平均温度、最大温度、最小温度
*/
public class StatisticsDisplay implements Observer {
private final List<Float> temperatures = new ArrayList<>();
@Override
public void update(float temperature, float humidity, float pressure) {
temperatures.add(temperature);
display();
}
/**
* 显示天气统计
*/
public void display() {
float avg = 0, max = Float.MIN_VALUE, min = Float.MAX_VALUE;
for (float temp : temperatures) {
avg += temp;
if (temp > max) max = temp;
if (temp < min) min = temp;
}
avg /= temperatures.size();
System.out.println("天气统计:平均 " + String.format("%.1f", avg)
+ "°C,最高 " + max + "°C,最低 " + min + "°C");
}
}
(6)具体观察者------天气预报显示
java
/**
* 具体观察者:天气预报显示
* 根据气压变化做简单预测
*/
public class ForecastDisplay implements Observer {
private float currentPressure = 29.92f;
private float lastPressure;
@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
/**
* 显示天气预报
*/
public void display() {
System.out.print("天气预报:");
if (currentPressure > lastPressure) {
System.out.println("天气转好!");
} else if (currentPressure == lastPressure) {
System.out.println("天气持平。");
} else {
System.out.println("注意降温降雨!");
}
}
}
(7)客户端调用
java
public class WeatherStationDemo {
public static void main(String[] args) {
// 创建主题------气象站
WeatherData weatherData = new WeatherData();
// 创建观察者------三个显示终端
CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay();
StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
ForecastDisplay forecastDisplay = new ForecastDisplay();
// 注册观察者
weatherData.registerObserver(currentDisplay);
weatherData.registerObserver(statisticsDisplay);
weatherData.registerObserver(forecastDisplay);
// 模拟天气数据更新
System.out.println("=== 第一次数据更新 ===");
weatherData.setMeasurements(26.5f, 65.0f, 1013.1f);
// 当前天气:温度 26.5°C,湿度 65.0%
// 天气统计:平均 26.5°C,最高 26.5°C,最低 26.5°C
// 天气预报:注意降温降雨!
System.out.println("\n=== 第二次数据更新 ===");
weatherData.setMeasurements(28.0f, 70.0f, 1015.0f);
// 当前天气:温度 28.0°C,湿度 70.0%
// 天气统计:平均 27.3°C,最高 28.0°C,最低 26.5°C
// 天气预报:天气转好!
// 移除某个观察者
System.out.println("\n=== 移除天气预报显示后 ===");
weatherData.removeObserver(forecastDisplay);
weatherData.setMeasurements(24.0f, 60.0f, 1012.0f);
// 当前天气:温度 24.0°C,湿度 60.0%
// 天气统计:平均 26.2°C,最高 28.0°C,最低 24.0°C
// (天气预报显示不再收到通知)
}
}
关键点 :气象站
WeatherData不需要知道有哪些具体的显示终端,只依赖Observer接口。新增显示终端只需实现Observer接口并注册到气象站即可,无需修改气象站代码,符合开闭原则。
2.1.1 推模型与拉模型
在自定义实现中,观察者模式有两种数据传递方式:
| 对比维度 | 推模型 | 拉模型 |
|---|---|---|
| 数据传递方向 | 主题主动将数据推送给观察者 | 观察者主动从主题拉取数据 |
| 更新方法签名 | update(temperature, humidity, pressure) |
update(Subject subject) |
| 观察者是否依赖主题 | 否(数据已包含在参数中) | 是(需要从主题获取数据) |
| 数据粒度 | 主题决定推送哪些数据 | 观察者按需获取数据 |
| 典型应用 | 上面的天气预报示例 | JDK 的 Observer 模式 |
推模型 (Push)的实现方式如上面天气预报示例------主题将变化的数据作为参数传给观察者的 update 方法。这种方式观察者不需要持有主题的引用,耦合度更低,但主题需要知道观察者需要哪些数据。
拉模型 (Pull)的实现方式------观察者的 update 方法接收主题对象作为参数,观察者自行从主题中获取所需数据:
java
/**
* 拉模型的观察者接口
*/
public interface PullObserver {
/**
* 更新观察者状态(拉模型)
*
* @param subject 主题对象,观察者从中拉取所需数据
*/
void update(Subject subject);
}
/**
* 拉模型的具体观察者:当前天气显示
*/
public class CurrentConditionPullDisplay implements PullObserver {
@Override
public void update(Subject subject) {
if (subject instanceof WeatherData) {
WeatherData weatherData = (WeatherData) subject;
// 观察者自行从主题中拉取所需数据
float temperature = weatherData.getTemperature();
float humidity = weatherData.getHumidity();
System.out.println("当前天气:温度 " + temperature + "°C,湿度 " + humidity + "%");
}
}
}
选型建议 :当主题数据较少且观察者所需数据一致时,使用推模型 更简洁;当不同观察者需要不同数据或主题数据复杂时,使用拉模型 更灵活。实际开发中,推模型更为常用。
2.2 JDK 内置 Observer
JDK 早期版本提供了内置的观察者模式支持:java.util.Observable(可观察者)和 java.util.Observer(观察者接口)。但这两个类在 JDK 9 中被标记为 @Deprecated,并在后续版本中逐步移除。
2.2.1 基本使用
被观察者
观察者接口
notifyObservers
持有引用
java.util.Observable
WeatherObservable 具体主题
java.util.Observer
CurrentDisplay 具体观察者
(1)具体主题------继承 Observable
java
import java.util.Observable;
/**
* 具体主题:继承 java.util.Observable
* JDK 内置的 Observable 是一个类而非接口
*/
public class WeatherObservable extends Observable {
private float temperature;
private float humidity;
private float pressure;
/**
* 设置天气数据
* 必须先调用 setChanged() 标记状态已改变,notifyObservers() 才会生效
*
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
// 标记状态已改变------这是必须的!
setChanged();
// 通知所有观察者(拉模型:传入 null,观察者自行获取数据)
notifyObservers();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
(2)具体观察者------实现 Observer 接口
java
import java.util.Observable;
import java.util.Observer;
/**
* 具体观察者:实现 java.util.Observer 接口
*/
@SuppressWarnings("deprecation")
public class CurrentDisplayJDK implements Observer {
private float temperature;
private float humidity;
/**
* 更新方法------拉模型
* Observable 通知时,观察者从 Observable 中拉取数据
*
* @param o 主题对象
* @param arg 推模型时传入的数据(此处为 null)
*/
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherObservable) {
WeatherObservable weatherData = (WeatherObservable) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("当前天气:温度 " + temperature + "°C,湿度 " + humidity + "%");
}
}
(3)客户端调用
java
@SuppressWarnings("deprecation")
public class JDKObserverDemo {
public static void main(String[] args) {
// 创建主题
WeatherObservable weatherData = new WeatherObservable();
// 创建观察者并注册
CurrentDisplayJDK currentDisplay = new CurrentDisplayJDK();
weatherData.addObserver(currentDisplay);
// 模拟数据更新
weatherData.setMeasurements(26.5f, 65.0f, 1013.1f);
// 当前天气:温度 26.5°C,湿度 65.0%
weatherData.setMeasurements(28.0f, 70.0f, 1015.0f);
// 当前天气:温度 28.0°C,湿度 70.0%
// 注意:如果不调用 setChanged(),notifyObservers() 不会通知观察者
}
}
2.2.2 废弃原因分析
Observable 和 Observer 在 JDK 9 中被废弃,主要原因如下:
| 问题 | 说明 |
|---|---|
| Observable 是类而非接口 | Java 不支持多继承,子类无法同时继承 Observable 和其他类 |
| setChanged() 受保护 | setChanged() 是 protected 方法,只有子类能调用,组合模式无法使用 |
| 线程安全但效率低 | Observable 内部使用 synchronized 保证线程安全,但粒度太粗,性能差 |
| 通知顺序不确定 | notifyObservers() 的通知顺序依赖于实现,且无法保证顺序性 |
| 不支持泛型 | Observer.update() 的参数是 Object,缺乏类型安全 |
| 违反组合优先原则 | 继承 Observable 限制了灵活性,无法通过组合实现观察者功能 |
结论 :JDK 内置的 Observer 机制存在设计缺陷,实际开发中推荐使用自定义实现 或 Spring 事件机制 。如果需要 JDK 原生支持,可以使用
java.util.concurrent.Flow(JDK 9+ 引入的响应式流 API)替代。
2.3 自定义实现 vs JDK 内置 Observer
| 对比维度 | 自定义实现 | JDK 内置 Observer |
|---|---|---|
| 主题定义方式 | 实现接口(组合优先) | 继承 Observable 类 |
| 类型安全 | 支持泛型 | 参数为 Object,需强转 |
| 灵活性 | 高(可自定义通知策略) | 低(通知逻辑固定) |
| 线程安全 | 自行控制 | 内置 synchronized |
| 推/拉模型 | 均可支持 | 仅支持拉模型为主 |
| 是否废弃 | 否 | JDK 9 已废弃 |
| 代码量 | 略多 | 较少 |
选型建议:
- 优先选择自定义实现------灵活可控,类型安全,不依赖废弃 API
- 如果项目已使用 Spring 框架,直接选择Spring 事件机制------功能更强大,生态更完善
- 避免使用 JDK 内置的
Observable/Observer
三、Spring 事件机制
Spring 框架提供了强大的事件机制,是观察者模式在企业级开发中最典型的应用。Spring 事件机制基于 ApplicationEvent、ApplicationListener 和 ApplicationEventPublisher 三个核心组件,提供了类型安全、异步支持、条件过滤等高级特性。
3.1 核心组件
publishEvent
传递
onApplicationEvent
implements
implements
替代
ApplicationEventPublisher 发布者
ApplicationEvent 事件
ApplicationListener 监听者
业务处理
ApplicationContext
EventListener 注解
- ApplicationEvent(事件):所有事件的抽象基类,自定义事件需要继承此类
- ApplicationListener(监听者):事件监听接口,泛型参数指定关注的事件类型
- ApplicationEventPublisher(发布者) :事件发布接口,
ApplicationContext本身就是发布者 - @EventListener(注解监听) :Spring 4.2+ 提供的注解方式,无需实现
ApplicationListener接口
3.2 实战示例------订单事件系统
以"订单系统"为例,当用户下单后需要触发多种后续操作:发送短信通知、记录操作日志、赠送积分等。
(1)自定义事件------订单创建事件
java
import org.springframework.context.ApplicationEvent;
/**
* 自定义事件:订单创建事件
* 继承 ApplicationEvent,携带订单相关信息
*/
public class OrderCreatedEvent extends ApplicationEvent {
private final String orderId;
private final String userId;
private final double amount;
/**
* 构造事件
*
* @param source 事件源(通常为发布事件的对象)
* @param orderId 订单ID
* @param userId 用户ID
* @param amount 订单金额
*/
public OrderCreatedEvent(Object source, String orderId, String userId, double amount) {
super(source);
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
}
public String getOrderId() {
return orderId;
}
public String getUserId() {
return userId;
}
public double getAmount() {
return amount;
}
}
(2)事件发布------订单服务
java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
/**
* 订单服务:发布订单创建事件
* 注入 ApplicationEventPublisher 发布事件
*/
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public OrderService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
/**
* 创建订单
*
* @param orderId 订单ID
* @param userId 用户ID
* @param amount 订单金额
*/
public void createOrder(String orderId, String userId, double amount) {
// 1. 业务逻辑:保存订单到数据库
System.out.println("订单已创建:" + orderId + ",用户:" + userId + ",金额:" + amount);
// 2. 发布订单创建事件------所有监听器自动收到通知
OrderCreatedEvent event = new OrderCreatedEvent(this, orderId, userId, amount);
eventPublisher.publishEvent(event);
}
}
(3)事件监听------实现 ApplicationListener 接口
java
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 监听者:短信通知监听器
* 实现 ApplicationListener<OrderCreatedEvent>,只接收订单创建事件
*/
@Component
public class SmsNotificationListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
System.out.println("【短信通知】用户 " + event.getUserId()
+ " 的订单 " + event.getOrderId() + " 已创建,金额:"
+ event.getAmount() + " 元");
}
}
(4)事件监听------日志记录监听器
java
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 监听者:操作日志监听器
* 记录订单创建的操作日志
*/
@Component
public class OrderLogListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
System.out.println("【操作日志】订单创建事件------订单号:" + event.getOrderId()
+ ",用户:" + event.getUserId()
+ ",金额:" + event.getAmount());
}
}
关键点 :
OrderService只负责发布事件,不需要知道有哪些监听器。新增监听器只需实现ApplicationListener接口并加@Component注解,Spring 自动发现并注册,无需修改任何已有代码。
3.3 @EventListener 注解方式
Spring 4.2+ 提供了 @EventListener 注解,无需实现 ApplicationListener 接口,在任意 Bean 的方法上标注即可成为事件监听器,大大简化了开发。
(1)积分赠送监听器------使用 @EventListener
java
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 监听者:积分赠送监听器
* 使用 @EventListener 注解,无需实现 ApplicationListener 接口
*/
@Component
public class PointsEventListener {
/**
* 监听订单创建事件,赠送积分
* 方法参数类型即关注的事件类型
*
* @param event 订单创建事件
*/
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
int points = (int) (event.getAmount() * 10);
System.out.println("【积分赠送】用户 " + event.getUserId()
+ " 获得积分:" + points);
}
}
(2)条件过滤------只监听大额订单
java
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 监听者:VIP 专属福利
* condition 属性支持 SpEL 表达式过滤事件
*/
@Component
public class VipBonusEventListener {
/**
* 只监听金额大于 1000 的订单
*
* @param event 订单创建事件
*/
@EventListener(condition = "#event.amount > 1000")
public void onBigOrderCreated(OrderCreatedEvent event) {
System.out.println("【VIP福利】用户 " + event.getUserId()
+ " 的大额订单 " + event.getOrderId()
+ ",金额:" + event.getAmount() + " 元,赠送专属优惠券!");
}
}
(3)异步监听------@Async + @EventListener
java
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 监听者:数据分析监听器(异步执行)
* 使用 @Async 注解使监听器在独立线程中异步执行,不阻塞主流程
*/
@Component
public class DataAnalysisEventListener {
/**
* 异步监听订单创建事件
*
* @param event 订单创建事件
*/
@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 模拟耗时数据分析
System.out.println("【数据分析】开始分析订单 " + event.getOrderId() + "...");
System.out.println("【数据分析】订单 " + event.getOrderId() + " 分析完成");
}
}
注意 :使用
@Async需要 Spring 配置类上添加@EnableAsync注解开启异步支持。
3.4 ApplicationListener 接口 vs @EventListener 注解
| 对比维度 | ApplicationListener 接口 | @EventListener 注解 |
|---|---|---|
| 定义方式 | 实现接口 + @Component | 在方法上标注注解 |
| 事件类型指定 | 泛型参数 ApplicationListener<E> |
方法参数类型即事件类型 |
| 条件过滤 | 不支持 | condition 属性支持 SpEL |
| 异步支持 | 需自行实现 | 搭配 @Async 注解 |
| 一个类监听多个事件 | 不支持(一个类只能监听一种事件) | 支持(多个方法分别标注) |
| 代码简洁度 | 略冗长 | 简洁 |
| Spring 版本要求 | 1.0+ | 4.2+ |
选型建议:
- 优先使用 @EventListener 注解------更简洁、更灵活,支持条件过滤和异步
- 如果需要在类级别做统一管理或需要早期初始化,可以使用
ApplicationListener接口
3.5 Spring 内置事件
Spring 框架自身也定义了一些内置事件,了解它们有助于更好地掌握 Spring 的生命周期:
| 事件 | 说明 | 触发时机 |
|---|---|---|
ContextRefreshedEvent |
上下文刷新事件 | ApplicationContext 初始化或刷新完成时 |
ContextStartedEvent |
上下文启动事件 | 调用 ConfigurableApplicationContext.start() 时 |
ContextStoppedEvent |
上下文停止事件 | 调用 ConfigurableApplicationContext.stop() 时 |
ContextClosedEvent |
上下文关闭事件 | 调用 ConfigurableApplicationContext.close() 时 |
RequestHandledEvent |
请求处理事件 | Spring MVC 处理完 HTTP 请求后(Web 应用) |
java
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 监听 Spring 上下文刷新事件
*/
@Component
public class StartupEventListener {
@EventListener
public void onContextRefreshed(ContextRefreshedEvent event) {
System.out.println("Spring 上下文已刷新,应用启动完成!");
}
}
四、总结
观察者模式的核心思想是定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
优点:
- 解耦主题与观察者:主题不需要知道观察者的具体实现,只依赖观察者接口,降低对象间的耦合度
- 符合开闭原则:新增观察者只需实现接口并注册,无需修改主题代码
- 支持广播通信:主题一次通知,所有观察者自动更新,实现一对多的广播效果
- 运行时动态关系:可以在运行时动态添加和移除观察者,灵活性高
缺点:
- 通知顺序不确定:观察者的通知顺序依赖于实现,多个观察者时可能产生不可预期的结果
- 性能问题:观察者过多或更新逻辑过重时,通知所有观察者可能造成性能瓶颈
- 循环依赖风险:观察者与主题之间可能形成循环引用,导致无限递归调用
- 内存泄漏风险:观察者注册后未及时移除,导致主题持有观察者的引用无法被 GC 回收
适用场景:
- 一个对象的改变需要同时改变其他对象,且不知道具体有多少对象需要改变
- 一个对象必须通知其他对象,但不希望与被通知的对象形成紧耦合
- 事件驱动系统的基础机制,如 GUI 事件处理、消息中间件、微服务事件驱动架构
- 需要实现发布-订阅模型的场景
三种实现方式对比:
| 实现方式 | 灵活性 | 类型安全 | 适用场景 |
|---|---|---|---|
| 自定义实现 | 高 | 支持 | 非 Spring 项目、需要精细控制的场景 |
| JDK 内置 Observer | 低 | 不支持 | 已废弃,不推荐使用 |
| Spring 事件机制 | 高 | 支持 | Spring 项目、企业级应用 |
观察者模式 vs 发布-订阅模式 :严格来说,两者有细微区别。观察者模式中,主题和观察者直接交互 (主题持有观察者引用并直接调用);发布-订阅模式中,发布者和订阅者通过**事件通道(Event Bus)**间接交互,完全解耦。Spring 事件机制中的
ApplicationEventPublisher扮演了事件通道的角色,更接近发布-订阅模式。
参考博客:
观察者模式 | 菜鸟教程:https://www.runoob.com/design-pattern/observer-pattern.html