设计模式-观察者模式

文章目录

  • 一、概述
    • [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 废弃原因分析

ObservableObserver 在 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 事件机制基于 ApplicationEventApplicationListenerApplicationEventPublisher 三个核心组件,提供了类型安全、异步支持、条件过滤等高级特性。

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

相关推荐
hssfscv1 小时前
软件设计师下午题训练1-3题+2019上上午题错题解析 练习真题训练13
笔记·设计模式·uml
拾-光3 小时前
【Git】命令大全:从入门到高手,100 个最常用命令速查(2026 版)
java·大数据·人工智能·git·python·elasticsearch·设计模式
多加点辣也没关系5 小时前
设计模式-模板方法模式
设计模式·模板方法模式
Autumn_ing9 小时前
2026实测:这5款AI生成UI工具支持Shadcn UI/Ant Design组件库
人工智能·ui·设计模式·aigc·设计规范
woniu_buhui_fei14 小时前
常用设计模式
设计模式·架构
likerhood14 小时前
设计模式 · 组合模式(Composite Pattern)
设计模式·组合模式
多加点辣也没关系14 小时前
设计模式-迭代器模式
设计模式·迭代器模式
江米小枣tonylua1 天前
从红绿灯到方向盘:TDD 在 AI 时代的新角色
前端·设计模式·ai编程
nnsix1 天前
设计模式 - 工厂模式 笔记
笔记·设计模式