概述
在软件系统的复杂协作网络中,对象之间的状态同步与通信始终是一个核心挑战。观察者模式(Observer Pattern),作为GoF 23种经典设计模式中的行为型模式之一,定义了一种对象间的一对多依赖关系——当一个主题对象(Subject)的状态发生改变时,所有依赖于它的观察者对象(Observer)都会自动收到通知并完成状态更新。这一模式的核心意图在于解耦:让状态变化的发起者无需知晓谁会响应变化,让响应变化的观察者无需时刻关注发起者的状态。
传统编程实践中,开发者常通过轮询(Polling)机制周期性检查目标对象的状态变化,这不仅消耗计算资源,更造成了代码的紧耦合——调用方必须明确知道被调用方的存在。观察者模式通过引入发布-订阅的思想,将主动查询转变为被动通知,从根本上改变了对象间的通信范式。从JDK内置的Observable到Spring的事件广播机制,从Guava EventBus到RxJava的响应式流,观察者模式的演进贯穿了Java技术栈的各个层面,甚至在分布式系统中以消息队列、配置中心等形式焕发新生。
本文将带领读者从最原始的轮询代码出发,逐步重构为经典的观察者模式实现,深入剖析推模型与拉模型的本质差异,继而探索异步通知、弱引用管理等进阶特性。随后,我们将潜入JDK、Spring、Guava、RxJava、ZooKeeper等主流框架的源码深处,揭示观察者模式在工业级应用中的精妙设计。在分布式章节中,您将看到观察者模式如何演化为消息队列的发布-订阅、配置中心的动态推送。配合股票行情、订单系统、UI绑定、文件监控、社交推送五大实战场景的完整Demo,以及十余道专家级面试题的深度解答,本文旨在为Java开发者呈现一幅观察者模式的全景技术图谱。
一、模式定义与结构
1.1 GoF标准定义
Observer Pattern: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
1.2 UML类图
classDiagram
class Subject {
<<interface>>
+registerObserver(Observer o)
+removeObserver(Observer o)
+notifyObservers()
}
class ConcreteSubject {
-state: State
-observers: List~Observer~
+getState(): State
+setState(State s)
+registerObserver(Observer o)
+removeObserver(Observer o)
+notifyObservers()
}
class Observer {
<<interface>>
+update()
}
class ConcreteObserverA {
-subject: ConcreteSubject
-observerState: State
+update()
}
class ConcreteObserverB {
-subject: ConcreteSubject
-observerState: State
+update()
}
Subject <|.. ConcreteSubject : implements
Observer <|.. ConcreteObserverA : implements
Observer <|.. ConcreteObserverB : implements
ConcreteSubject --> Observer : notifies
ConcreteObserverA --> ConcreteSubject : observes
ConcreteObserverB --> ConcreteSubject : observes
1.3 推模型与拉模型的深度对比
观察者模式在数据传递策略上存在两种经典变体:推模型(Push Model)和拉模型(Pull Model)。这两种模型的根本分歧在于谁掌握数据传递的主导权。
推模型(Push Model) 的核心特征是主题在通知观察者时,主动将状态变化的详细信息作为参数推送给观察者。典型实现是在update(StateData data)方法中携带具体的数据载荷。这种模型的优势在于观察者无需主动获取数据,响应路径最短,适用于数据量小、时效性要求高的场景。然而,其致命缺陷在于主题必须预判观察者的数据需求——如果推送的数据粒度过粗,会造成网络和内存的浪费;如果过细,又可能导致观察者因缺少必要信息而需要回查主题,丧失了推送的意义。
拉模型(Pull Model) 则采取了截然相反的策略:主题在通知时仅告知观察者"我发生了变化",而具体需要哪些数据、何时获取数据,完全由观察者主动从主题中拉取。实现上,update()方法通常不携带参数,观察者内部持有主题引用,通过主题暴露的getter方法按需获取状态。拉模型赋予了观察者最大的灵活性,按需获取、延迟加载的特性使其特别适用于数据量大、观察者关注维度各异的复杂场景。但代价是增加了观察者与主题之间的耦合度——观察者必须知晓主题的具体类型和接口。
从Java生态的实践来看,java.util.Observable采用了拉模型设计(观察者需自行从Observable中拉取数据),而java.beans.PropertyChangeEvent则更贴近推模型(新旧值随事件传递)。选择推还是拉,本质上是在"最小知识原则"与"最少传输原则"之间权衡:推模型更符合迪米特法则,拉模型更符合带宽优化原则。在实际系统设计中,常见的折衷方案是轻量通知+按需拉取:通知时携带变化类型和关键ID,观察者据此决定是否深入拉取详细数据。
1.4 角色职责详解
对照上述UML类图,观察者模式的四个核心角色各司其职:
| 角色 | 类型 | 核心职责 |
|---|---|---|
| Subject(抽象主题) | 接口/抽象类 | 定义观察者管理契约:registerObserver()用于建立订阅关系,removeObserver()用于解除订阅,notifyObservers()触发广播通知。这是主题对外的统一管理接口,保证所有具体主题行为的一致性。 |
| ConcreteSubject(具体主题) | 具体类 | 维护observers集合(通常是ArrayList或CopyOnWriteArrayList),持有业务状态state。当setState()被调用且状态确实发生变化时,主动调用notifyObservers()遍历观察者列表,触发每个观察者的更新操作。在拉模型中,还需提供getState()供观察者拉取数据。 |
| Observer(抽象观察者) | 接口 | 声明update()方法作为统一的回调入口。推模型下该方法携带状态参数,拉模型下则无参。这个接口的存在使得主题可以面向抽象编程,无需关心具体观察者的实现细节,是实现解耦的关键。 |
| ConcreteObserver(具体观察者) | 具体类 | 实现update()逻辑,完成自身状态与主题状态的同步。通常持有对具体主题的引用(拉模型必需,推模型可选),在构造时完成订阅,也可在运行时动态注册/注销。内部维护observerState以保持与主题状态的最终一致性。 |
二、代码演进与实现
2.1 不使用模式的原始代码:轮询的泥潭
在未引入观察者模式之前,客户端通常采用主动轮询或硬编码调用的方式实现状态同步。以下是一个天气监测系统的原始实现:
/**
* 天气数据采集器(被依赖方)
* 每次数据更新后,需要手动通知所有依赖方
*/
class WeatherStation {
private float temperature;
private float humidity;
private float pressure;
// 硬编码依赖:采集器必须知道所有需要通知的对象
private CurrentConditionsDisplay currentDisplay;
private StatisticsDisplay statisticsDisplay;
private ForecastDisplay forecastDisplay;
public WeatherStation() {
// 紧耦合的初始化
this.currentDisplay = new CurrentConditionsDisplay();
this.statisticsDisplay = new StatisticsDisplay();
this.forecastDisplay = new ForecastDisplay();
}
public void measurementsChanged() {
// 每次数据变化都需要手动调用各显示器的更新方法
// 问题1:新增显示器需要修改WeatherStation源码
// 问题2:遗漏通知的风险(人为失误)
// 问题3:显示器无法动态注册/注销
currentDisplay.update(temperature, humidity, pressure);
statisticsDisplay.update(temperature, humidity, pressure);
forecastDisplay.update(temperature, humidity, pressure);
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged(); // 触发硬编码通知
}
}
/**
* 轮询方式的客户端
* 通过无限循环不断检查数据源状态
*/
class PollingClient {
private WeatherStation station;
private float lastTemperature;
public void startPolling() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
// 每2秒轮询一次,检查温度是否变化
executor.scheduleAtFixedRate(() -> {
float currentTemp = station.getTemperature();
if (currentTemp != lastTemperature) {
System.out.println("温度变化:" + lastTemperature + " -> " + currentTemp);
lastTemperature = currentTemp;
// 执行响应逻辑...
}
// 问题4:大量无效的轮询检查消耗CPU资源
// 问题5:实时性受轮询间隔限制
}, 0, 2, TimeUnit.SECONDS);
}
}
问题分析:原始代码暴露了五个核心缺陷:
- 紧耦合:
WeatherStation必须明确知道所有显示器的存在,违反开闭原则。 - 遗漏通知风险:手动维护通知列表,新增显示器容易忘记添加调用。
- 动态性缺失:无法在运行时灵活增加或移除显示器。
- 资源浪费:轮询机制产生大量无效检查,尤其当状态变化频率远低于轮询频率时。
- 实时性受限:轮询间隔决定了状态变化的感知延迟上限。
2.2 经典观察者模式重构(推模型)
// ==================== 抽象层定义 ====================
/**
* 观察者接口(推模型版本)
* update方法接收主题推送的完整状态数据
*/
interface WeatherObserver {
/**
* 当主题状态变化时被调用
* @param temperature 温度(摄氏度)
* @param humidity 湿度(百分比)
* @param pressure 气压(hPa)
*/
void update(float temperature, float humidity, float pressure);
}
/**
* 主题接口
* 定义了观察者的管理契约
*/
interface Subject {
/**
* 注册观察者
* @param observer 要注册的观察者对象
*/
void registerObserver(WeatherObserver observer);
/**
* 移除观察者
* @param observer 要移除的观察者对象
*/
void removeObserver(WeatherObserver observer);
/**
* 通知所有已注册的观察者
*/
void notifyObservers();
}
// ==================== 具体主题实现 ====================
/**
* 天气数据主题(具体主题)
* 维护观察者集合并广播天气变化
*/
class WeatherDataSubject implements Subject {
// 使用ArrayList维护观察者列表,注意非线程安全
private List<WeatherObserver> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherDataSubject() {
this.observers = new ArrayList<>();
}
@Override
public void registerObserver(WeatherObserver observer) {
if (observer != null && !observers.contains(observer)) {
observers.add(observer);
System.out.println("[Subject] 观察者注册成功: " + observer.getClass().getSimpleName());
}
}
@Override
public void removeObserver(WeatherObserver observer) {
observers.remove(observer);
System.out.println("[Subject] 观察者已移除: " + observer.getClass().getSimpleName());
}
@Override
public void notifyObservers() {
// 遍历副本以防止在通知过程中观察者列表被修改
List<WeatherObserver> snapshot = new ArrayList<>(observers);
for (WeatherObserver observer : snapshot) {
// 推模型:主动传递所有状态数据
observer.update(temperature, humidity, pressure);
}
}
/**
* 业务方法:当气象站采集到新数据时调用
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
// 状态变更检测(避免无效通知)
boolean changed = this.temperature != temperature
|| this.humidity != humidity
|| this.pressure != pressure;
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
if (changed) {
System.out.println("\n[Subject] 气象数据更新: " + temperature + "°C, "
+ humidity + "%, " + pressure + "hPa");
notifyObservers(); // 触发通知
}
}
}
// ==================== 具体观察者实现 ====================
/**
* 当前天气状况显示器
*/
class CurrentConditionsDisplay implements WeatherObserver {
private float temperature;
private float humidity;
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
private void display() {
System.out.printf("[当前状况] 温度: %.1f°C, 湿度: %.1f%%%n", temperature, humidity);
}
}
/**
* 气象统计数据显示器
*/
class StatisticsDisplay implements WeatherObserver {
private List<Float> temperatureHistory = new ArrayList<>();
private static final int MAX_HISTORY = 10;
@Override
public void update(float temperature, float humidity, float pressure) {
temperatureHistory.add(temperature);
if (temperatureHistory.size() > MAX_HISTORY) {
temperatureHistory.remove(0);
}
display();
}
private void display() {
double avg = temperatureHistory.stream()
.mapToDouble(Float::doubleValue).average().orElse(0.0);
double max = temperatureHistory.stream()
.mapToDouble(Float::doubleValue).max().orElse(0.0);
double min = temperatureHistory.stream()
.mapToDouble(Float::doubleValue).min().orElse(0.0);
System.out.printf("[统计数据] 平均: %.1f°C, 最高: %.1f°C, 最低: %.1f°C%n", avg, max, min);
}
}
/**
* 天气预报显示器
*/
class ForecastDisplay implements WeatherObserver {
private float lastPressure;
private float currentPressure = 1013.0f; // 标准大气压
@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
private void display() {
System.out.print("[天气预报] ");
if (currentPressure > lastPressure) {
System.out.println("气压上升,天气转好");
} else if (currentPressure < lastPressure) {
System.out.println("气压下降,可能有降雨");
} else {
System.out.println("气压稳定,天气持续");
}
}
}
// ==================== 客户端演示 ====================
public class WeatherStationDemo {
public static void main(String[] args) {
// 1. 创建主题
WeatherDataSubject weatherData = new WeatherDataSubject();
// 2. 创建观察者
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
ForecastDisplay forecastDisplay = new ForecastDisplay();
// 3. 建立订阅关系(观察者主动注册)
weatherData.registerObserver(currentDisplay);
weatherData.registerObserver(statisticsDisplay);
weatherData.registerObserver(forecastDisplay);
// 4. 模拟气象数据变化
System.out.println("=== 第一组数据 ===");
weatherData.setMeasurements(25.5f, 65.0f, 1013.2f);
System.out.println("\n=== 第二组数据(模拟移除观察者)===");
weatherData.removeObserver(statisticsDisplay);
weatherData.setMeasurements(26.8f, 70.0f, 1012.5f);
System.out.println("\n=== 第三组数据(模拟动态添加新观察者)===");
// 动态添加新的观察者——无需修改WeatherDataSubject源码
WeatherObserver heatIndexDisplay = new WeatherObserver() {
@Override
public void update(float t, float h, float p) {
double heatIndex = computeHeatIndex(t, h);
System.out.printf("[体感温度] %.1f°C%n", heatIndex);
}
private double computeHeatIndex(double t, double rh) {
// 简化版体感温度计算公式
return t + 0.1 * (rh - 50);
}
};
weatherData.registerObserver(heatIndexDisplay);
weatherData.setMeasurements(28.0f, 75.0f, 1011.8f);
}
}
运行结果:
[Subject] 观察者注册成功: CurrentConditionsDisplay
[Subject] 观察者注册成功: StatisticsDisplay
[Subject] 观察者注册成功: ForecastDisplay
=== 第一组数据 ===
[Subject] 气象数据更新: 25.5°C, 65.0%, 1013.2hPa
[当前状况] 温度: 25.5°C, 湿度: 65.0%
[统计数据] 平均: 25.5°C, 最高: 25.5°C, 最低: 25.5°C
[天气预报] 气压稳定,天气持续
=== 第二组数据(模拟移除观察者)===
[Subject] 观察者已移除: StatisticsDisplay
[Subject] 气象数据更新: 26.8°C, 70.0%, 1012.5hPa
[当前状况] 温度: 26.8°C, 湿度: 70.0%
[天气预报] 气压下降,可能有降雨
=== 第三组数据(模拟动态添加新观察者)===
[Subject] 观察者注册成功: WeatherStationDemo$1
[Subject] 气象数据更新: 28.0°C, 75.0%, 1011.8hPa
[当前状况] 温度: 28.0°C, 湿度: 75.0%
[天气预报] 气压下降,可能有降雨
[体感温度] 30.5°C
2.3 拉模型实现对比
// ==================== 拉模型实现 ====================
/**
* 拉模型观察者接口
* update方法无参数,观察者主动拉取数据
*/
interface PullObserver {
/**
* 主题状态变化通知
* 观察者需要自行从主题拉取所需数据
*/
void update();
}
/**
* 拉模型主题接口
* 增加了状态访问方法供观察者拉取
*/
interface PullSubject {
void registerObserver(PullObserver observer);
void removeObserver(PullObserver observer);
void notifyObservers();
// 拉模型必需:暴露状态访问接口
float getTemperature();
float getHumidity();
float getPressure();
}
/**
* 拉模型具体主题
*/
class PullWeatherDataSubject implements PullSubject {
private List<PullObserver> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
@Override
public void registerObserver(PullObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(PullObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
// 遍历副本,通知时不携带数据
new ArrayList<>(observers).forEach(PullObserver::update);
}
// 状态访问器(供观察者拉取)
@Override public float getTemperature() { return temperature; }
@Override public float getHumidity() { return humidity; }
@Override public float getPressure() { return pressure; }
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
/**
* 拉模型观察者示例
* 持有主题引用,按需拉取数据
*/
class SmartDisplay implements PullObserver {
private PullWeatherDataSubject subject; // 持有主题引用以便拉取
private String displayMode;
public SmartDisplay(PullWeatherDataSubject subject, String mode) {
this.subject = subject;
this.displayMode = mode;
subject.registerObserver(this);
}
@Override
public void update() {
// 拉模型:按需从主题拉取数据
switch (displayMode) {
case "TEMP_ONLY":
System.out.printf("[智能显示-仅温度] %.1f°C%n", subject.getTemperature());
break;
case "FULL":
System.out.printf("[智能显示-完整] 温度:%.1f°C, 湿度:%.1f%%, 气压:%.1fhPa%n",
subject.getTemperature(), subject.getHumidity(), subject.getPressure());
break;
case "SUMMARY":
// 只关心部分数据,按需拉取
float temp = subject.getTemperature();
String comfort = temp < 18 ? "偏冷" : (temp > 28 ? "偏热" : "舒适");
System.out.printf("[智能显示-摘要] 当前%s (%.1f°C)%n", comfort, temp);
break;
}
}
}
// 拉模型演示
public class PullModelDemo {
public static void main(String[] args) {
PullWeatherDataSubject subject = new PullWeatherDataSubject();
// 不同观察者根据自身需求拉取不同粒度的数据
new SmartDisplay(subject, "TEMP_ONLY");
new SmartDisplay(subject, "FULL");
new SmartDisplay(subject, "SUMMARY");
System.out.println("=== 拉模型演示 ===");
subject.setMeasurements(22.5f, 55.0f, 1015.0f);
}
}
推模型 vs 拉模型对比总结:
| 维度 | 推模型(Push) | 拉模型(Pull) |
|---|---|---|
| 数据传递 | 主题主动推送完整数据 | 观察者主动拉取所需数据 |
| 耦合度 | 观察者依赖推送数据格式 | 观察者依赖主题具体类型 |
| 灵活性 | 低,所有观察者接收相同数据 | 高,观察者按需获取 |
| 网络开销 | 可能传输冗余数据 | 按需传输,可能多次请求 |
| 实时性 | 高,一次性完成通知+数据传递 | 可能需二次请求 |
| 典型应用 | 轻量级事件、配置推送 | 大数据量、选择性订阅 |
| Java示例 | PropertyChangeEvent | java.util.Observable |
2.4 观察者模式的进阶特性
2.4.1 支持事件类型的多播
实际系统中,主题可能产生多种类型的事件,不同观察者只关心特定事件。以下是支持事件分类的多播实现:
/**
* 事件类型枚举
*/
enum WeatherEventType {
TEMPERATURE_CHANGED, // 温度变化
HUMIDITY_CHANGED, // 湿度变化
PRESSURE_CHANGED, // 气压变化
SEVERE_WEATHER_ALERT // 恶劣天气预警
}
/**
* 事件对象(携带事件元数据)
*/
class WeatherEvent {
private final WeatherEventType type;
private final float oldValue;
private final float newValue;
private final long timestamp;
public WeatherEvent(WeatherEventType type, float oldValue, float newValue) {
this.type = type;
this.oldValue = oldValue;
this.newValue = newValue;
this.timestamp = System.currentTimeMillis();
}
public WeatherEventType getType() { return type; }
public float getOldValue() { return oldValue; }
public float getNewValue() { return newValue; }
public long getTimestamp() { return timestamp; }
}
/**
* 支持事件分类的主题
*/
class EventDrivenWeatherSubject {
// 按事件类型维护不同的观察者集合
private final Map<WeatherEventType, List<Consumer<WeatherEvent>>> listeners = new EnumMap<>(WeatherEventType.class);
private float temperature;
private float humidity;
private float pressure;
/**
* 订阅特定类型的事件
*/
public void subscribe(WeatherEventType eventType, Consumer<WeatherEvent> listener) {
listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
}
/**
* 发布事件(只通知订阅该类型的观察者)
*/
private void fireEvent(WeatherEvent event) {
List<Consumer<WeatherEvent>> eventListeners = listeners.get(event.getType());
if (eventListeners != null) {
eventListeners.forEach(listener -> listener.accept(event));
}
}
public void setTemperature(float newTemp) {
if (this.temperature != newTemp) {
float oldTemp = this.temperature;
this.temperature = newTemp;
fireEvent(new WeatherEvent(WeatherEventType.TEMPERATURE_CHANGED, oldTemp, newTemp));
// 温度过高时触发预警
if (newTemp > 35.0f) {
fireEvent(new WeatherEvent(WeatherEventType.SEVERE_WEATHER_ALERT, 0, newTemp));
}
}
}
public void setHumidity(float newHumidity) {
if (this.humidity != newHumidity) {
float oldHumidity = this.humidity;
this.humidity = newHumidity;
fireEvent(new WeatherEvent(WeatherEventType.HUMIDITY_CHANGED, oldHumidity, newHumidity));
}
}
}
// 事件多播演示
public class MulticastEventDemo {
public static void main(String[] args) {
EventDrivenWeatherSubject subject = new EventDrivenWeatherSubject();
// 观察者只订阅感兴趣的事件类型
subject.subscribe(WeatherEventType.TEMPERATURE_CHANGED,
e -> System.out.printf("[温度监听器] 温度从%.1f°C变为%.1f°C%n",
e.getOldValue(), e.getNewValue()));
subject.subscribe(WeatherEventType.SEVERE_WEATHER_ALERT,
e -> System.err.printf("[预警监听器] 高温预警!当前温度%.1f°C%n", e.getNewValue()));
subject.subscribe(WeatherEventType.HUMIDITY_CHANGED,
e -> System.out.printf("[湿度监听器] 湿度从%.1f%%变为%.1f%%%n",
e.getOldValue(), e.getNewValue()));
System.out.println("=== 事件多播演示 ===");
subject.setTemperature(30.0f); // 仅触发温度监听器
subject.setHumidity(65.0f); // 仅触发湿度监听器
subject.setTemperature(37.0f); // 触发温度监听器 + 预警监听器
}
}
2.4.2 观察者的异步通知
同步通知可能因观察者执行耗时操作而阻塞主题,异步通知是生产环境的必备特性:
/**
* 异步通知的主题实现
*/
class AsyncWeatherSubject {
private final List<WeatherObserver> observers = new CopyOnWriteArrayList<>();
// 使用线程池执行异步通知
private final ExecutorService executor = Executors.newFixedThreadPool(4);
private float temperature;
private float humidity;
private float pressure;
public void registerObserver(WeatherObserver observer) {
observers.add(observer);
}
/**
* 异步通知:每个观察者在独立线程中执行
*/
public void notifyObserversAsync() {
float temp = this.temperature;
float hum = this.humidity;
float pres = this.pressure;
List<CompletableFuture<Void>> futures = observers.stream()
.map(observer -> CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + " 开始处理通知");
observer.update(temp, hum, pres);
System.out.println(Thread.currentThread().getName() + " 完成通知处理");
}, executor))
.collect(Collectors.toList());
// 可选:等待所有通知完成
// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
/**
* 带超时控制的异步通知
*/
public void notifyObserversWithTimeout(long timeout, TimeUnit unit) {
float temp = this.temperature;
float hum = this.humidity;
float pres = this.pressure;
List<CompletableFuture<Void>> futures = observers.stream()
.map(observer -> CompletableFuture.runAsync(() ->
observer.update(temp, hum, pres), executor)
.orTimeout(timeout, unit)
.exceptionally(throwable -> {
System.err.println("观察者执行超时或异常: " + observer.getClass().getSimpleName());
return null;
}))
.collect(Collectors.toList());
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
System.out.println("[异步主题] 数据更新,触发异步通知...");
notifyObserversAsync();
}
public void shutdown() {
executor.shutdown();
}
}
// 耗时观察者模拟
class SlowObserver implements WeatherObserver {
private final String name;
public SlowObserver(String name) { this.name = name; }
@Override
public void update(float t, float h, float p) {
try {
Thread.sleep(2000); // 模拟耗时操作
System.out.printf("[%s] 处理完成: %.1f°C%n", name, t);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class AsyncNotificationDemo {
public static void main(String[] args) throws InterruptedException {
AsyncWeatherSubject subject = new AsyncWeatherSubject();
subject.registerObserver(new SlowObserver("观察者A"));
subject.registerObserver(new SlowObserver("观察者B"));
subject.registerObserver(new SlowObserver("观察者C"));
System.out.println("=== 异步通知演示(主题不会被阻塞)===");
long start = System.currentTimeMillis();
subject.setMeasurements(25.0f, 60.0f, 1013.0f);
System.out.println("主题通知方法立即返回,耗时: " + (System.currentTimeMillis() - start) + "ms");
Thread.sleep(3000); // 等待异步任务完成
subject.shutdown();
}
}
2.4.3 弱引用观察者:防止内存泄漏
/**
* 使用WeakReference管理观察者,防止因观察者未正确注销导致的内存泄漏
*/
class WeakReferenceSubject {
// 使用WeakReference包装观察者
private final List<WeakReference<WeatherObserver>> observers = new ArrayList<>();
private final ReferenceQueue<WeatherObserver> refQueue = new ReferenceQueue<>();
private float temperature;
/**
* 注册观察者(使用弱引用)
*/
public void registerObserver(WeatherObserver observer) {
// 清理已被GC回收的引用
cleanUpStaleReferences();
observers.add(new WeakReference<>(observer, refQueue));
}
/**
* 清理已被垃圾回收的观察者引用
*/
private void cleanUpStaleReferences() {
Reference<? extends WeatherObserver> ref;
while ((ref = refQueue.poll()) != null) {
observers.remove(ref);
System.out.println("[弱引用主题] 清理了一个已被GC的观察者");
}
}
/**
* 通知所有存活的观察者
*/
public void notifyObservers() {
cleanUpStaleReferences();
Iterator<WeakReference<WeatherObserver>> iterator = observers.iterator();
while (iterator.hasNext()) {
WeatherObserver observer = iterator.next().get();
if (observer == null) {
iterator.remove(); // 引用已被清除,移除空引用
} else {
observer.update(temperature, 0, 0);
}
}
}
public void setTemperature(float temp) {
this.temperature = temp;
notifyObservers();
}
}
/**
* 演示弱引用防止内存泄漏
*/
public class WeakReferenceDemo {
public static void main(String[] args) {
WeakReferenceSubject subject = new WeakReferenceSubject();
// 创建观察者但不持有强引用
subject.registerObserver(new WeatherObserver() {
@Override
public void update(float t, float h, float p) {
System.out.println("观察者1收到通知: " + t + "°C");
}
});
subject.setTemperature(25.0f);
// 建议垃圾回收
System.gc();
try { Thread.sleep(100); } catch (InterruptedException e) {}
// 此时观察者已被GC,主题中的弱引用自动失效
subject.setTemperature(26.0f); // 不会再有观察者收到通知
System.out.println("观察者已被GC,不再收到通知(避免了内存泄漏)");
}
}
2.5 观察者模式通知时序图
sequenceDiagram
participant Client as 客户端
participant Subject as ConcreteSubject
participant ObsList as 观察者列表
participant Obs1 as ConcreteObserver1
participant Obs2 as ConcreteObserver2
participant ObsN as ConcreteObserverN
Client->>Subject: setState(newState)
activate Subject
Subject->>Subject: 检查状态是否变更
alt 状态已变更
Subject->>Subject: notifyObservers()
activate Subject
Subject->>ObsList: 获取观察者列表快照
ObsList-->>Subject: List(Observer)
loop 遍历所有观察者
Subject->>Obs1: update(state)
activate Obs1
Obs1->>Obs1: 同步自身状态
Obs1-->>Subject:
deactivate Obs1
Subject->>Obs2: update(state)
activate Obs2
Obs2->>Obs2: 同步自身状态
Obs2-->>Subject:
deactivate Obs2
Subject->>ObsN: update(state)
activate ObsN
ObsN->>ObsN: 同步自身状态
ObsN-->>Subject:
deactivate ObsN
end
deactivate Subject
else 状态未变更
Subject->>Subject: 跳过通知
end
deactivate Subject
Subject-->>Client: 返回
时序图解读:该时序图完整展示了观察者模式中一次状态变更触发的通知流程。
-
触发阶段:客户端调用
ConcreteSubject的setState()方法修改主题状态。主题首先进行状态变更检测——对比新旧状态值,如果未发生实质性变化则直接返回,避免无效通知。这是观察者模式中的性能优化要点。 -
通知准备阶段:确认状态变更后,主题调用自身的
notifyObservers()方法。注意时序图中的关键操作:获取观察者列表快照。这通常通过new ArrayList<>(observers)实现,目的是防止在遍历过程中观察者列表被并发修改(如观察者在update()方法中注销自己)。这是生产级观察者模式实现的线程安全基线。 -
广播阶段:主题依次遍历快照中的每个观察者,调用其
update()方法。这里展示了观察者模式的核心特征:主题对所有观察者一视同仁——它不关心观察者的具体类型,仅依赖Observer接口。每个观察者在update()方法中完成与主题的状态同步,同步方式可以是推(参数携带数据)或拉(主动调用主题getter)。 -
返回阶段:所有观察者处理完毕后,
notifyObservers()方法返回,setState()方法也随之返回给客户端。在同步通知模式下,客户端的调用会阻塞直到所有观察者完成处理;异步模式下(如2.4.2节所示)则立即返回。
三、源码级应用分析
3.1 JDK深度剖析
3.1.1 java.util.Observable与Observer
JDK 1.0即内置了观察者模式实现,但其设计存在诸多缺陷,在现代Java开发中已基本被弃用。
package java.util;
public class Observable {
private boolean changed = false;
private Vector<Observer> obs; // 使用Vector保证线程安全
public synchronized void addObserver(Observer o) { ... }
public synchronized void deleteObserver(Observer o) { ... }
public void notifyObservers() {
notifyObservers(null); // 拉模型:不传递数据
}
public void notifyObservers(Object arg) { // 推模型:可携带数据
Object[] arrLocal;
synchronized (this) {
if (!changed) return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--) // 倒序遍历
((Observer)arrLocal[i]).update(this, arg);
}
protected synchronized void setChanged() { changed = true; }
protected synchronized void clearChanged() { changed = false; }
public synchronized boolean hasChanged() { return changed; }
}
public interface Observer {
void update(Observable o, Object arg);
}
JDK Observable的核心缺陷分析:
| 缺陷 | 描述 | 影响 |
|---|---|---|
| 类继承限制 | Observable是类而非接口,由于Java单继承,业务类若继承了其他父类则无法作为主题 | 严重限制了主题类的设计灵活性 |
| 非线程安全细节 | 虽使用Vector和synchronized,但notifyObservers在同步块外遍历快照,存在竞态条件 | 可能导致观察者收到过时状态或遗漏通知 |
setChanged()保护方法 | 必须显式调用setChanged()才能触发通知,API设计冗余易出错 | 开发者经常忘记调用,导致通知失效 |
| 不支持泛型 | update(Observable o, Object arg)中arg为Object类型 | 类型不安全,需要强制转换 |
| 倒序遍历 | notifyObservers采用倒序遍历观察者 | 非直观行为,且非文档保证,依赖实现细节 |
| 不支持Lambda | 接口设计早于Java 8,观察者必须实现完整类 | 代码冗长,无法享受函数式编程便利 |
现代替代方案:PropertyChangeSupport(JavaBeans)、Flow API(Java 9+)、第三方事件总线。
3.1.2 java.util.EventObject与EventListener
AWT/Swing的事件模型是观察者模式的经典应用:
// 事件对象基类
public class EventObject implements Serializable {
protected transient Object source;
public Object getSource() { return source; }
}
// 事件监听器标记接口
public interface EventListener { }
// 具体事件:鼠标点击
public class MouseEvent extends InputEvent {
public static final int MOUSE_CLICKED = 500;
public int getClickCount() { ... }
public int getX() { ... }
public int getY() { ... }
}
// 监听器接口(观察者)
public interface MouseListener extends EventListener {
void mouseClicked(MouseEvent e);
void mousePressed(MouseEvent e);
void mouseReleased(MouseEvent e);
void mouseEntered(MouseEvent e);
void mouseExited(MouseEvent e);
}
// 组件(主题)
public class Component {
protected EventListenerList listenerList = new EventListenerList();
public void addMouseListener(MouseListener l) {
listenerList.add(MouseListener.class, l);
}
protected void processMouseEvent(MouseEvent e) {
for (MouseListener listener : listenerList.getListeners(MouseListener.class)) {
switch (e.getID()) {
case MouseEvent.MOUSE_CLICKED:
listener.mouseClicked(e); break;
// ...
}
}
}
}
设计亮点:
- 强类型事件对象:每种事件有明确的类型和携带数据
- 细粒度监听器接口:
MouseListener定义多个方法,适配不同事件 - 适配器模式辅助:
MouseAdapter提供空实现,观察者只需覆写关心的方法 - EventListenerList:高效的多类型监听器存储结构
3.1.3 java.beans.PropertyChangeSupport
这是JavaBeans规范中为属性变更通知提供的基础设施,修复了Observable的诸多问题:
public class PropertyChangeSupport {
private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
// 支持按属性名注册监听器
public void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
if (listener == null) return;
map.add(propertyName, listener);
}
public void firePropertyChange(String propertyName,
Object oldValue, Object newValue) {
if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
firePropertyChange(new PropertyChangeEvent(source, propertyName,
oldValue, newValue));
}
}
private void firePropertyChange(PropertyChangeEvent event) {
// 通知特定属性的监听器
for (PropertyChangeListener listener : map.get(event.getPropertyName())) {
listener.propertyChange(event);
}
// 通知通配符监听器(监听所有属性)
for (PropertyChangeListener listener : map.get(null)) {
listener.propertyChange(event);
}
}
}
// 事件对象携带变更详情(推模型)
public class PropertyChangeEvent extends EventObject {
private final String propertyName;
private final Object oldValue;
private final Object newValue;
// getter方法...
}
相比Observable的改进:
- 基于组合而非继承:
PropertyChangeSupport作为工具类嵌入业务对象 - 属性级精细订阅:可仅监听特定属性的变化
- 新旧值对比:事件携带变更前后值,避免观察者回查
- 线程安全委托:线程安全性由使用者控制,更灵活
3.1.4 Flow API(Java 9+)
Java 9引入的java.util.concurrent.Flow是Reactive Streams规范的JDK实现,定义了响应式流中的观察者模式:
public final class Flow {
@FunctionalInterface
public static interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}
public static interface Subscriber<T> {
void onSubscribe(Subscription subscription);
void onNext(T item);
void onError(Throwable throwable);
void onComplete();
}
public static interface Subscription {
void request(long n);
void cancel();
}
// 默认处理器:按需传递数据
public static interface Processor<T,R>
extends Subscriber<T>, Publisher<R> { }
}
Flow API对传统观察者模式的增强:
- 背压机制:
Subscription.request(n)允许观察者控制数据流速 - 完成与错误信号:
onComplete()和onError()提供终止语义 - 异步非阻塞:设计目标即支持异步流处理
- 资源管理:
Subscription.cancel()显式释放资源
3.2 Spring框架深度剖析
3.2.1 ApplicationEvent与ApplicationListener
Spring的事件机制是观察者模式的经典实现,贯穿整个框架生命周期:
// 事件抽象基类
public abstract class ApplicationEvent extends EventObject {
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
}
// 监听器接口(观察者)
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent>
extends EventListener {
void onApplicationEvent(E event);
}
// 事件发布器(主题)
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
// ApplicationContext继承发布器接口
public interface ApplicationContext extends ApplicationEventPublisher { ... }
// 抽象实现类中的事件广播核心
public abstract class AbstractApplicationContext {
private ApplicationEventMulticaster applicationEventMulticaster;
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
getApplicationEventMulticaster().multicastEvent(
(ApplicationEvent) event, eventType);
}
}
3.2.2 ApplicationEventMulticaster
事件广播器是Spring事件机制的核心执行引擎:
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void removeApplicationListener(ApplicationListener<?> listener);
void multicastEvent(ApplicationEvent event);
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
public class SimpleApplicationEventMulticaster
implements ApplicationEventMulticaster {
// 默认使用ListenerRetriever管理监听器
private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
// 线程池(若设置则异步广播)
@Nullable
private Executor taskExecutor;
// 错误处理器
@Nullable
private ErrorHandler errorHandler;
@Override
public void multicastEvent(ApplicationEvent event,
@Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType
: resolveDefaultEventType(event));
// 获取匹配的监听器(支持泛型事件类型匹配)
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
// 异步执行
executor.execute(() -> invokeListener(listener, event));
} else {
// 同步执行
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener<?> listener,
ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
} catch (Throwable err) {
errorHandler.handleError(err); // 错误隔离
}
} else {
doInvokeListener(listener, event); // 异常会中断后续监听器
}
}
private void doInvokeListener(ApplicationListener listener,
ApplicationEvent event) {
listener.onApplicationEvent(event);
}
}
设计精髓:
- 泛型事件匹配:通过
ResolvableType支持监听器声明泛型事件类型 - 同步/异步可切换:设置
taskExecutor即可从同步切换为异步广播 - 错误隔离:
ErrorHandler确保单个监听器异常不影响其他监听器 - 早期事件缓存:容器启动早期发布的事件会被缓存,待广播器初始化后重放
3.2.3 @EventListener注解
Spring 4.2引入的注解驱动事件监听:
@Component
public class OrderEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 根据方法参数类型自动匹配事件
System.out.println("订单创建: " + event.getOrderId());
}
@EventListener(condition = "#event.amount > 1000")
public void handleLargeOrder(OrderCreatedEvent event) {
// SpEL条件表达式过滤
System.out.println("大额订单: " + event.getAmount());
}
@EventListener
@Async // 异步执行
public void sendNotification(OrderCreatedEvent event) {
// 异步发送通知
}
@EventListener
@Order(1) // 控制执行顺序
public void validateOrder(OrderCreatedEvent event) { ... }
}
实现原理:Spring通过EventListenerMethodProcessor在容器初始化时扫描@EventListener注解,为每个方法动态创建ApplicationListenerMethodAdapter适配器并注册到广播器。
3.2.4 TransactionalEventListener
事务绑定监听器是Spring事件机制的高级特性:
@Component
public class TransactionalOrderListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(OrderCreatedEvent event) {
// 事务提交后才执行,确保数据已持久化
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleAfterRollback(OrderCreatedEvent event) {
// 事务回滚时执行补偿逻辑
}
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMPLETION,
fallbackExecution = true // 无事务时也执行
)
public void handleAfterCompletion(OrderCreatedEvent event) { ... }
}
实现机制:TransactionalApplicationListener包装原始监听器,通过TransactionSynchronizationManager注册事务同步回调,在事务各阶段触发。
3.3 Google Guava EventBus
Guava的EventBus提供了轻量级的事件总线实现:
// 事件总线
public class EventBus {
private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
private final Dispatcher dispatcher;
public void register(Object object) {
subscribers.register(object); // 扫描@Subscribe注解方法
}
public void post(Object event) {
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
}
}
}
// 订阅者方法示例
public class OrderEventListener {
@Subscribe
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("收到订单事件: " + event);
}
@Subscribe
@AllowConcurrentEvents // 允许并发调用
public void handleConcurrent(OrderCreatedEvent event) { ... }
}
// 异步事件总线
AsyncEventBus asyncBus = new AsyncEventBus(Executors.newFixedThreadPool(4));
EventBus特色:
- 基于注解的声明式订阅:
@Subscribe标注订阅方法 - 事件继承匹配:发布子类事件也会通知监听父类的订阅者
- DeadEvent处理:无订阅者时发布
DeadEvent - 异常隔离:
SubscriberExceptionHandler统一处理订阅者异常
3.4 RxJava与Project Reactor
响应式编程框架将观察者模式推向了新的高度:
// RxJava 3
Observable<String> observable = Observable.create(emitter -> {
emitter.onNext("Hello");
emitter.onNext("World");
emitter.onComplete();
});
observable.subscribe(
item -> System.out.println("收到: " + item), // onNext
error -> error.printStackTrace(), // onError
() -> System.out.println("完成") // onComplete
);
// 背压支持:Flowable
Flowable.range(1, 1_000_000)
.onBackpressureBuffer() // 背压策略
.observeOn(Schedulers.computation())
.subscribe(new Subscriber<Integer>() {
Subscription s;
@Override
public void onSubscribe(Subscription s) {
this.s = s;
s.request(10); // 请求前10个元素
}
@Override
public void onNext(Integer i) {
if (i % 10 == 0) s.request(10); // 动态请求
}
});
RxJava Observable vs GoF观察者模式:
| 维度 | GoF观察者模式 | RxJava Observable |
|---|---|---|
| 数据流方向 | 单向推送 | 支持操作符转换、组合 |
| 完成语义 | 无 | onComplete()明确终止 |
| 错误处理 | 各自处理 | 统一错误通道onError() |
| 背压 | 不支持 | 通过Flowable/request()支持 |
| 线程模型 | 同步为主 | 声明式线程调度 |
| 操作符 | 无 | 丰富的转换/过滤/组合操作符 |
3.5 ZooKeeper Watcher机制
ZooKeeper的Watcher是一次性触发的观察者模式变体:
public class ZooKeeper {
// 注册Watcher
public byte[] getData(String path, boolean watch, Stat stat) { ... }
public List<String> getChildren(String path, boolean watch) { ... }
public Stat exists(String path, boolean watch) { ... }
}
public interface Watcher {
void process(WatchedEvent event);
interface Event {
enum KeeperState { ... }
enum EventType { None, NodeCreated, NodeDeleted,
NodeDataChanged, NodeChildrenChanged }
}
}
// 客户端使用示例
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, event -> {
if (event.getType() == EventType.NodeDataChanged) {
System.out.println("节点数据变化: " + event.getPath());
// 需要重新注册Watcher!
zk.getData(event.getPath(), true, null);
}
});
Stat stat = new Stat();
byte[] data = zk.getData("/config", event -> {
// Watcher注册
}, stat);
一次性触发设计考量:
- 防止通知风暴:集群状态变化频繁,避免客户端被海量通知淹没
- 减轻服务端压力:无需维护长连接或持久化订阅关系
- 确保通知可达:客户端处理完通知后主动重新注册,确认其仍在关注
- 简化状态同步:通知后客户端通常需要重新读取数据,自然保证了数据最新
缺点:客户端必须显式重新注册,增加代码复杂度;网络闪断可能导致事件丢失。
四、分布式环境下的观察者模式
4.1 消息队列中的发布-订阅模型
在分布式系统中,观察者模式演化为通过消息中间件解耦的发布-订阅模型。
RabbitMQ Topic Exchange:
// 发布者(主题)
ConnectionFactory factory = new ConnectionFactory();
try (Connection conn = factory.newConnection();
Channel channel = conn.createChannel()) {
channel.exchangeDeclare("order_events", BuiltinExchangeType.TOPIC);
String message = "Order 12345 created";
channel.basicPublish("order_events", "order.created", null,
message.getBytes());
}
// 订阅者(观察者)
channel.exchangeDeclare("order_events", BuiltinExchangeType.TOPIC);
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, "order_events", "order.*"); // 模式匹配
DeliverCallback callback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("收到: " + message);
};
channel.basicConsume(queueName, true, callback, consumerTag -> {});
Kafka消费者组:
// 发布者
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("orders", "order_123", "created"));
// 消费者组(同一组内负载均衡,不同组广播)
props.put("group.id", "order_processors");
props.put("key.deserializer", StringDeserializer.class.getName());
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("orders"));
4.2 配置中心的动态配置推送
Apollo/Nacos等配置中心实现了配置变更的实时推送:
// Apollo配置监听
@ApolloConfig
private Config config;
@ApolloConfigChangeListener
private void onChange(ConfigChangeEvent changeEvent) {
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
System.out.printf("配置变更: %s = %s -> %s%n",
key, change.getOldValue(), change.getNewValue());
// 刷新本地配置缓存
}
}
// Nacos配置监听
NacosConfigService configService = new NacosConfigService(properties);
configService.addListener("application.properties", "DEFAULT_GROUP",
new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("收到配置更新: " + configInfo);
}
@Override
public Executor getExecutor() {
return Executors.newSingleThreadExecutor();
}
});
长轮询实现原理:客户端发起HTTP请求,服务端持有连接直到有配置变更或超时(通常30-60秒),超时后客户端立即发起新请求。这种方式平衡了实时性与服务端压力。
4.3 服务注册中心的变更通知
Eureka/Consul等服务注册中心通过观察者模式实现服务上下线感知:
// Eureka客户端服务发现
@EnableDiscoveryClient
@Component
public class ServiceDiscoveryWatcher {
@EventListener
public void onHeartbeat(HeartbeatEvent event) {
// 服务列表更新时触发
List<ServiceInstance> instances = discoveryClient.getInstances("order-service");
updateLocalCache(instances);
}
}
// Spring Cloud负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// Ribbon动态更新服务列表
public class DynamicServerListLoadBalancer {
private final ServerListUpdater.UpdateAction updateAction =
() -> updateListOfServers();
// PollingServerListUpdater定时轮询或Eureka事件驱动更新
}
4.4 分布式事件总线
基于消息中间件实现跨服务的领域事件广播:
// 领域事件定义
public class OrderPaidEvent {
private String orderId;
private BigDecimal amount;
private LocalDateTime paidTime;
}
// 事件发布者(订单服务)
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional
public void payOrder(String orderId) {
// 业务处理...
OrderPaidEvent event = new OrderPaidEvent(orderId, amount, now());
eventPublisher.publishEvent(event); // 本地发布
rabbitTemplate.convertAndSend("order.exchange", "order.paid", event); // 远程发布
}
}
// 远程事件消费者(积分服务)
@Component
public class PointEventListener {
@RabbitListener(queues = "point.order.paid.queue")
public void handleOrderPaid(OrderPaidEvent event) {
// 增加用户积分
pointService.addPoints(event.getUserId(), event.getAmount());
}
}
4.5 微服务架构中的CQRS事件同步
命令查询职责分离(CQRS)架构中,观察者模式用于同步读写模型:
// 命令端(写模型)
@PostMapping("/orders")
public Order createOrder(@RequestBody OrderRequest request) {
Order order = orderService.create(request);
// 发布领域事件
eventBus.publish(new OrderCreatedEvent(order));
return order;
}
// 查询端(读模型)- 事件消费者
@Component
public class OrderViewUpdater {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 更新Elasticsearch中的订单视图
OrderView view = new OrderView();
view.setId(event.getOrderId());
view.setStatus(event.getStatus());
elasticsearchRepository.save(view);
}
}
4.6 Redis Pub/Sub分布式观察者示例
// Redis主题(发布者)
@Component
public class RedisEventPublisher {
@Autowired
private StringRedisTemplate redisTemplate;
public void publishConfigChange(String configKey, String newValue) {
ConfigChangeMessage msg = new ConfigChangeMessage(configKey, newValue);
redisTemplate.convertAndSend("config:changes",
objectMapper.writeValueAsString(msg));
}
}
// Redis观察者(订阅者)
@Component
public class RedisConfigSubscriber implements MessageListener {
@PostConstruct
public void init() {
// 在独立线程中订阅(Jedis客户端是阻塞的)
new Thread(() -> {
Jedis jedis = new Jedis("localhost", 6379);
jedis.subscribe(this, "config:changes");
}, "redis-subscriber").start();
}
@Override
public void onMessage(String channel, String message) {
ConfigChangeMessage msg = objectMapper.readValue(message,
ConfigChangeMessage.class);
System.out.printf("[%s] 配置变更: %s = %s%n",
getClass().getSimpleName(), msg.getKey(), msg.getValue());
refreshLocalConfig(msg.getKey(), msg.getValue());
}
}
// 多个服务节点订阅同一频道,实现配置同步
4.7 分布式观察者架构图
flowchart TB
subgraph ServiceA[服务A - 事件发布者]
Publisher[领域事件发布器]
Business[业务逻辑]
Business -->|状态变更| Publisher
end
subgraph MessageMiddleware[消息中间件]
Exchange[Exchange / Topic]
Queue1[队列: 服务B消费组]
Queue2[队列: 服务C消费组]
end
subgraph ServiceB[服务B - 观察者1]
Consumer1[事件消费者]
Handler1[业务处理器]
Consumer1 --> Handler1
end
subgraph ServiceC[服务C - 观察者2]
Consumer2[事件消费者]
Handler2[业务处理器]
Consumer2 --> Handler2
end
subgraph ConfigCenter[配置中心]
ConfigServer[配置服务]
LongPolling[长轮询处理器]
end
subgraph ClientNodes[客户端节点]
Client1[服务实例1]
Client2[服务实例2]
Client3[服务实例N]
end
Publisher -->|发布事件| Exchange
Exchange -->|路由| Queue1
Exchange -->|路由| Queue2
Queue1 -->|消费| Consumer1
Queue2 -->|消费| Consumer2
ConfigServer -->|配置变更推送| LongPolling
LongPolling -->|通知| Client1
LongPolling -->|通知| Client2
LongPolling -->|通知| Client3
ServiceA -.->|注册/发现| ServiceB
ServiceA -.->|注册/发现| ServiceC
架构图解读:该架构图展示了分布式环境下观察者模式的三种典型变体。
上半部分——消息队列的发布-订阅:服务A作为事件发布者(主题),在业务状态变更后将事件发布到消息中间件的Exchange/Topic。消息中间件根据路由规则将事件分发到不同消费者的队列。服务B和服务C作为观察者,各自从自己的队列消费事件。这种设计的精妙之处在于:
- 完全解耦:发布者无需知道订阅者的存在,甚至无需知道有多少订阅者
- 可靠传递:消息持久化确保事件不丢失
- 消费模式灵活:同一服务的多个实例可组成消费者组实现负载均衡,不同服务通过独立队列实现广播
下半部分——配置中心的长轮询推送:配置服务作为主题维护配置状态,多个客户端节点通过长轮询连接配置中心。当配置发生变更时,配置中心通过已建立的长轮询连接主动推送变更通知。这种机制在实时性(接近推送)和资源消耗(远低于短轮询)之间取得了最佳平衡。
虚线部分——服务注册中心:服务间的发现依赖通过注册中心实现。服务B/C上线时向注册中心注册,服务A通过观察注册中心的变化感知服务B/C的可用性。这本质上是观察者模式的又一应用:注册中心是主题,服务实例的上下线是状态变更,服务调用方是观察者。
五、对比辨析
5.1 观察者模式 vs 发布-订阅模式
| 维度 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 通信中介 | 主题直接维护观察者列表 | 独立的Event Channel/Broker |
| 耦合程度 | 主题与观察者互相知晓(接口依赖) | 发布者与订阅者完全解耦 |
| 适用场景 | 进程内、单应用 | 跨进程、分布式系统 |
| 实现复杂度 | 简单 | 复杂(需消息中间件) |
| 可靠性 | 依赖内存,进程崩溃则丢失 | 支持持久化、事务 |
| 扩展性 | 受限于单机 | 天然支持水平扩展 |
5.2 观察者模式 vs 中介者模式
// 观察者模式:一对多直接依赖
class Subject {
List<Observer> observers; // 主题直接管理观察者
void notifyObservers() { observers.forEach(Observer::update); }
}
// 中介者模式:多对多通过中介者协调
class ChatRoomMediator {
List<User> users;
void sendMessage(User from, String msg) {
users.stream()
.filter(u -> u != from)
.forEach(u -> u.receive(msg)); // 中介者统一分发
}
}
5.3 观察者模式 vs 责任链模式
// 观察者:广播给所有观察者
observers.forEach(o -> o.handle(event));
// 责任链:请求沿链传递直到被处理
for (Handler h : chain) {
if (h.handle(request)) break; // 处理后终止
}
5.4 推模型 vs 拉模型深度对比
| 对比维度 | 推模型 | 拉模型 |
|---|---|---|
| 数据传递时机 | 通知时一并传递 | 观察者主动获取 |
| 观察者复杂度 | 低(被动接收) | 高(需知道如何获取) |
| 网络调用次数 | 1次 | 1+N次(N为拉取次数) |
| 数据时效性 | 通知时刻的快照 | 拉取时刻的最新值 |
| 数据一致性 | 可能不一致(观察者处理延迟) | 拉取时保证最新 |
| Java典型实现 | PropertyChangeEvent | java.util.Observable |
六、适用场景分析
场景一:股票价格实时推送
完整可运行Demo:
// ==================== 领域模型 ====================
class StockQuote {
private final String symbol;
private final BigDecimal price;
private final long timestamp;
// 构造器、getter省略...
}
// ==================== 主题 ====================
interface StockSubject {
void registerObserver(String symbol, StockObserver observer);
void removeObserver(String symbol, StockObserver observer);
void notifyObservers(String symbol, StockQuote quote);
}
class StockMarket implements StockSubject {
// 按股票代码维护观察者集合
private final Map<String, Set<StockObserver>> observers = new ConcurrentHashMap<>();
private final ExecutorService executor = Executors.newCachedThreadPool();
@Override
public void registerObserver(String symbol, StockObserver observer) {
observers.computeIfAbsent(symbol, k -> ConcurrentHashMap.newKeySet()).add(observer);
System.out.printf("[%s] 投资者 %s 开始关注%n", symbol, observer.getName());
}
@Override
public void removeObserver(String symbol, StockObserver observer) {
Set<StockObserver> set = observers.get(symbol);
if (set != null) {
set.remove(observer);
System.out.printf("[%s] 投资者 %s 取消关注%n", symbol, observer.getName());
}
}
@Override
public void notifyObservers(String symbol, StockQuote quote) {
Set<StockObserver> symbolObservers = observers.get(symbol);
if (symbolObservers == null || symbolObservers.isEmpty()) return;
// 异步推送,避免阻塞价格更新线程
symbolObservers.forEach(observer ->
executor.submit(() -> observer.onPriceUpdate(quote)));
}
/**
* 模拟价格变动(由外部行情源触发)
*/
public void updatePrice(String symbol, BigDecimal price) {
StockQuote quote = new StockQuote(symbol, price, System.currentTimeMillis());
System.out.printf("\n[行情] %s 最新价: %.2f%n", symbol, price);
notifyObservers(symbol, quote);
}
}
// ==================== 观察者 ====================
interface StockObserver {
String getName();
void onPriceUpdate(StockQuote quote);
}
class Investor implements StockObserver {
private final String name;
private final BigDecimal alertThreshold; // 关注阈值
private final Map<String, BigDecimal> portfolio = new ConcurrentHashMap<>();
public Investor(String name, BigDecimal alertThreshold) {
this.name = name;
this.alertThreshold = alertThreshold;
}
@Override
public String getName() { return name; }
@Override
public void onPriceUpdate(StockQuote quote) {
BigDecimal oldPrice = portfolio.put(quote.getSymbol(), quote.getPrice());
// 价格变动超过阈值才详细处理(数据过滤)
if (oldPrice != null) {
BigDecimal change = quote.getPrice().subtract(oldPrice)
.divide(oldPrice, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
if (change.abs().compareTo(alertThreshold) > 0) {
System.out.printf(" [%s] %s 价格变动 %.2f%% (%.2f -> %.2f),触发预警%n",
name, quote.getSymbol(), change, oldPrice, quote.getPrice());
} else {
System.out.printf(" [%s] %s 价格更新: %.2f%n",
name, quote.getSymbol(), quote.getPrice());
}
} else {
System.out.printf(" [%s] 首次接收 %s 行情: %.2f%n",
name, quote.getSymbol(), quote.getPrice());
}
}
}
// ==================== 演示客户端 ====================
public class StockMarketDemo {
public static void main(String[] args) throws InterruptedException {
StockMarket market = new StockMarket();
Investor alice = new Investor("Alice", new BigDecimal("2.0"));
Investor bob = new Investor("Bob", new BigDecimal("0.5"));
Investor charlie = new Investor("Charlie", BigDecimal.ZERO);
// 订阅感兴趣股票
market.registerObserver("AAPL", alice);
market.registerObserver("AAPL", bob);
market.registerObserver("GOOGL", alice);
market.registerObserver("GOOGL", charlie);
market.registerObserver("TSLA", bob);
// 模拟价格变动
market.updatePrice("AAPL", new BigDecimal("175.50"));
Thread.sleep(100);
market.updatePrice("GOOGL", new BigDecimal("142.30"));
Thread.sleep(100);
market.updatePrice("AAPL", new BigDecimal("180.20")); // 涨幅约2.68%,触发Alice预警
Thread.sleep(100);
market.updatePrice("TSLA", new BigDecimal("245.80"));
// 模拟取消关注
market.removeObserver("AAPL", bob);
market.updatePrice("AAPL", new BigDecimal("182.00"));
System.exit(0);
}
}
Mermaid类图:
classDiagram
class StockSubject {
<<interface>>
+registerObserver(symbol, observer)
+removeObserver(symbol, observer)
+notifyObservers(symbol, quote)
}
class StockMarket {
-observers: Map
-executor: ExecutorService
+updatePrice(symbol, price)
+registerObserver(symbol, observer)
+removeObserver(symbol, observer)
+notifyObservers(symbol, quote)
}
class StockObserver {
<<interface>>
+getName() String
+onPriceUpdate(quote)
}
class Investor {
-name: String
-alertThreshold: BigDecimal
-portfolio: Map
+onPriceUpdate(quote)
}
class StockQuote {
-symbol: String
-price: BigDecimal
-timestamp: long
}
StockSubject <|.. StockMarket : implements
StockObserver <|.. Investor : implements
StockMarket --> StockObserver : notifies
Investor --> StockQuote : uses
场景分析与实现策略:
股票行情推送是观察者模式的经典应用场景,其核心挑战在于高频率数据推送和个性化订阅需求。本实现中采用了以下策略:
推送频率控制:通过alertThreshold阈值过滤,只有当价格变动幅度超过投资者设定的阈值时才触发详细处理逻辑。这有效减少了无意义通知对投资者的干扰,也降低了系统的处理负载。在实际生产环境中,还可引入采样推送机制——即使价格持续微幅波动,也以固定间隔(如每5秒)推送一次聚合数据。
数据过滤策略:主题维护了Map<String, Set<StockObserver>>结构,按股票代码维度组织观察者。当某只股票价格更新时,仅通知订阅该股票的投资者,避免了全量广播。这是主题分片思想的体现——将大主题拆分为多个子主题,每个子主题独立管理观察者。
网络传输压缩考量:在高频交易场景下,网络带宽是瓶颈。可采用的优化包括:①增量传输:只传输价格变动量而非完整报价;②批量推送:将同一投资者关注的多只股票价格合并为一个数据包;③二进制协议:使用Protobuf或Avro替代JSON,减少序列化开销;④压缩算法:对历史价格序列使用差值编码。
场景二:订单状态变更通知
完整可运行Demo:
// ==================== 事件定义 ====================
enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED, CANCELLED }
class OrderEvent {
private final String orderId;
private final OrderStatus oldStatus;
private final OrderStatus newStatus;
private final long userId;
private final BigDecimal amount;
private final LocalDateTime timestamp;
// 构造器、getter...
}
// ==================== 观察者接口 ====================
interface OrderStatusObserver {
void onOrderStatusChanged(OrderEvent event);
String getServiceName();
}
// ==================== 主题 ====================
class OrderService {
private final List<OrderStatusObserver> observers = new CopyOnWriteArrayList<>();
private final Map<String, OrderStatus> orderStatusMap = new ConcurrentHashMap<>();
// 支持按顺序执行的观察者注册
public void registerObserver(OrderStatusObserver observer, int order) {
// 简化的顺序控制:插入指定位置
observers.add(Math.min(order, observers.size()), observer);
System.out.println("[订单服务] 注册观察者: " + observer.getServiceName());
}
public void updateOrderStatus(String orderId, OrderStatus newStatus) {
OrderStatus oldStatus = orderStatusMap.getOrDefault(orderId, OrderStatus.PENDING);
if (oldStatus == newStatus) return;
orderStatusMap.put(orderId, newStatus);
OrderEvent event = new OrderEvent(orderId, oldStatus, newStatus,
1001L, new BigDecimal("299.99"), LocalDateTime.now());
System.out.printf("\n[订单服务] 订单 %s 状态变更: %s -> %s%n",
orderId, oldStatus, newStatus);
notifyObservers(event);
}
private void notifyObservers(OrderEvent event) {
List<CompletableFuture<Void>> futures = observers.stream()
.map(obs -> CompletableFuture.runAsync(() -> {
try {
obs.onOrderStatusChanged(event);
} catch (Exception e) {
handleObserverError(obs, event, e);
}
}))
.collect(Collectors.toList());
// 等待所有通知完成(可配置超时)
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(throwable -> {
System.err.println("[订单服务] 部分观察者执行超时");
return null;
})
.join();
}
private void handleObserverError(OrderStatusObserver observer,
OrderEvent event, Exception e) {
System.err.printf("[订单服务] 观察者 %s 执行失败: %s%n",
observer.getServiceName(), e.getMessage());
// 记录失败事件到重试队列
FailedNotificationQueue.add(new FailedNotification(observer, event));
}
}
// ==================== 具体观察者实现 ====================
class SmsNotificationObserver implements OrderStatusObserver {
@Override
public String getServiceName() { return "短信服务"; }
@Override
public void onOrderStatusChanged(OrderEvent event) {
System.out.printf(" [短信服务] 向用户 %d 发送: 您的订单 %s 状态已更新为 %s%n",
event.getUserId(), event.getOrderId(), event.getNewStatus());
}
}
class InventoryObserver implements OrderStatusObserver {
private final Map<String, Integer> inventory = new ConcurrentHashMap<>();
public InventoryObserver() {
inventory.put("SKU001", 100);
}
@Override
public String getServiceName() { return "库存服务"; }
@Override
public void onOrderStatusChanged(OrderEvent event) {
if (event.getNewStatus() == OrderStatus.PAID) {
// 支付成功后扣减库存
Integer current = inventory.getOrDefault("SKU001", 0);
if (current > 0) {
inventory.put("SKU001", current - 1);
System.out.printf(" [库存服务] 扣减库存成功,剩余: %d%n", current - 1);
} else {
throw new RuntimeException("库存不足,扣减失败");
}
} else if (event.getNewStatus() == OrderStatus.CANCELLED &&
event.getOldStatus() == OrderStatus.PAID) {
// 已支付订单取消,恢复库存
inventory.merge("SKU001", 1, Integer::sum);
System.out.printf(" [库存服务] 订单取消,恢复库存,当前: %d%n",
inventory.get("SKU001"));
}
}
}
class PointsObserver implements OrderStatusObserver {
@Override
public String getServiceName() { return "积分服务"; }
@Override
public void onOrderStatusChanged(OrderEvent event) {
if (event.getNewStatus() == OrderStatus.DELIVERED) {
// 确认收货后增加积分
int points = event.getAmount().intValue() / 10;
System.out.printf(" [积分服务] 订单 %s 已签收,为用户 %d 增加 %d 积分%n",
event.getOrderId(), event.getUserId(), points);
}
}
}
// 失败重试队列
class FailedNotificationQueue {
private static final BlockingQueue<FailedNotification> queue = new LinkedBlockingQueue<>();
static void add(FailedNotification notification) {
queue.offer(notification);
}
static class FailedNotification {
final OrderStatusObserver observer;
final OrderEvent event;
int retryCount = 0;
FailedNotification(OrderStatusObserver o, OrderEvent e) { observer = o; event = e; }
}
}
// ==================== 演示客户端 ====================
public class OrderDemo {
public static void main(String[] args) {
OrderService orderService = new OrderService();
// 按业务逻辑顺序注册观察者
orderService.registerObserver(new SmsNotificationObserver(), 0);
orderService.registerObserver(new InventoryObserver(), 1);
orderService.registerObserver(new PointsObserver(), 2);
String orderId = "ORD-2024-001";
// 模拟订单状态流转
orderService.updateOrderStatus(orderId, OrderStatus.PAID);
orderService.updateOrderStatus(orderId, OrderStatus.SHIPPED);
orderService.updateOrderStatus(orderId, OrderStatus.DELIVERED);
}
}
Mermaid时序图:
sequenceDiagram
participant Client as 客户端
participant OrderSvc as 订单服务
participant SMS as 短信观察者
participant Inventory as 库存观察者
participant Points as 积分观察者
participant RetryQueue as 重试队列
Client->>OrderSvc: updateOrderStatus(PAID)
activate OrderSvc
OrderSvc->>OrderSvc: 构建OrderEvent
par 并行通知
OrderSvc->>SMS: onOrderStatusChanged(event)
activate SMS
SMS->>SMS: 发送短信通知
SMS-->>OrderSvc: 成功
deactivate SMS
and
OrderSvc->>Inventory: onOrderStatusChanged(event)
activate Inventory
Inventory->>Inventory: 扣减库存
alt 库存充足
Inventory-->>OrderSvc: 成功
else 库存不足
Inventory-->>OrderSvc: 异常
OrderSvc->>RetryQueue: 加入重试队列
end
deactivate Inventory
and
OrderSvc->>Points: onOrderStatusChanged(event)
activate Points
Points->>Points: 检查状态(PAID不处理)
Points-->>OrderSvc: 成功
deactivate Points
end
OrderSvc-->>Client: 返回
deactivate OrderSvc
Note over RetryQueue: 后台线程处理重试
异常处理与顺序控制策略分析:
订单状态变更涉及多个下游服务的协同,其复杂度远超简单通知场景。
观察者执行失败处理:本实现采用异步并行通知+失败记录策略。每个观察者在独立线程中执行,互不阻塞。当某个观察者抛出异常时,handleObserverError方法捕获异常并将失败的(observer, event)记录到FailedNotificationQueue。后台可启动专门的补偿线程,按指数退避策略(1s、2s、4s...)重试失败的通知。对于关键业务(如库存扣减),还可引入事务消息或本地消息表确保最终一致性。
观察者执行顺序控制:通过registerObserver(observer, order)方法支持指定注册顺序,这在某些场景下至关重要。例如,必须先扣减库存成功后再通知物流服务创建运单。本实现将顺序控制委托给CopyOnWriteArrayList的插入位置,通知时按列表顺序创建CompletableFuture,但需要注意:并行执行时无法保证完成顺序。如需严格的串行执行,应使用同步通知方式:
// 严格顺序执行
for (OrderStatusObserver observer : observers) {
observer.onOrderStatusChanged(event); // 同步阻塞
}
补偿与回滚策略:当某个观察者失败时,可能需要对已成功执行的观察者进行补偿。例如库存扣减成功但积分增加失败,需要回滚库存。这需要引入Saga模式:每个观察者提供compensate()补偿方法,当后续步骤失败时逆序调用补偿。
场景三:UI组件数据绑定
完整可运行Demo:
// ==================== 可观察模型 ====================
class ObservableModel {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private String name = "";
private int age = 0;
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void setName(String newName) {
String oldName = this.name;
this.name = newName;
pcs.firePropertyChange("name", oldName, newName);
}
public void setAge(int newAge) {
int oldAge = this.age;
this.age = newAge;
pcs.firePropertyChange("age", oldAge, newAge);
}
public String getName() { return name; }
public int getAge() { return age; }
}
// ==================== 视图(观察者)====================
class UserFormView {
private final ObservableModel model;
private final JTextField nameField = new JTextField(20);
private final JTextField ageField = new JTextField(5);
private final JLabel displayLabel = new JLabel();
public UserFormView(ObservableModel model) {
this.model = model;
// View观察Model变化(Model -> View)
model.addPropertyChangeListener(evt -> {
switch (evt.getPropertyName()) {
case "name":
// 防止循环更新
if (!nameField.getText().equals(evt.getNewValue())) {
nameField.setText((String) evt.getNewValue());
}
break;
case "age":
String ageStr = String.valueOf(evt.getNewValue());
if (!ageField.getText().equals(ageStr)) {
ageField.setText(ageStr);
}
break;
}
updateDisplay();
});
// View更新Model(View -> Model)—— 双向绑定
nameField.addActionListener(e -> model.setName(nameField.getText()));
ageField.addActionListener(e -> {
try {
model.setAge(Integer.parseInt(ageField.getText()));
} catch (NumberFormatException ex) {
ageField.setText(String.valueOf(model.getAge()));
}
});
}
private void updateDisplay() {
displayLabel.setText(String.format("当前用户: %s (%d岁)",
model.getName(), model.getAge()));
}
public JPanel getPanel() {
JPanel panel = new JPanel();
panel.add(new JLabel("姓名:"));
panel.add(nameField);
panel.add(new JLabel("年龄:"));
panel.add(ageField);
panel.add(displayLabel);
return panel;
}
}
// ==================== 响应式数据绑定模拟(类似Vue)====================
class ReactiveModel {
private final Map<String, Object> data = new HashMap<>();
private final Map<String, List<Consumer<Object>>> watchers = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) data.get(key);
}
public void set(String key, Object value) {
Object oldValue = data.put(key, value);
if (!Objects.equals(oldValue, value)) {
// 触发依赖收集的观察者
List<Consumer<Object>> observers = watchers.getOrDefault(key, Collections.emptyList());
observers.forEach(observer -> observer.accept(value));
}
}
public void watch(String key, Consumer<Object> callback) {
watchers.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);
// 立即执行一次
if (data.containsKey(key)) {
callback.accept(data.get(key));
}
}
}
// 模拟Vue的computed计算属性
class ComputedView {
private final ReactiveModel model;
private String fullDisplay = "";
public ComputedView(ReactiveModel model) {
this.model = model;
// 依赖收集:自动追踪依赖的firstName和lastName
model.watch("firstName", v -> updateDisplay());
model.watch("lastName", v -> updateDisplay());
}
private void updateDisplay() {
String firstName = model.get("firstName");
String lastName = model.get("lastName");
fullDisplay = firstName + " " + lastName;
System.out.println("[Computed] 全名更新: " + fullDisplay);
}
}
// ==================== 演示 ====================
public class DataBindingDemo {
public static void main(String[] args) {
// JavaBeans风格双向绑定
ObservableModel model = new ObservableModel();
UserFormView view = new UserFormView(model);
// 响应式风格
ReactiveModel reactiveModel = new ReactiveModel();
reactiveModel.set("firstName", "张");
reactiveModel.set("lastName", "三");
new ComputedView(reactiveModel);
reactiveModel.set("firstName", "李");
reactiveModel.set("lastName", "四");
}
}
Mermaid流程图:
flowchart TD
subgraph UserAction[用户交互]
Input[用户在输入框键入]
end
subgraph ViewLayer[视图层 View]
TextField[文本框组件]
EventHandler[事件处理器]
end
subgraph ModelLayer[模型层 Model]
Setter[setter方法]
PCS[PropertyChangeSupport]
Data[状态数据]
end
subgraph UpdateCycle[更新循环]
Notify[触发通知]
Loop[遍历监听器]
Update[更新视图]
end
Input -->|键入内容| TextField
TextField -->|ActionEvent| EventHandler
EventHandler -->|调用| Setter
Setter -->|修改| Data
Setter -->|firePropertyChange| PCS
PCS -->|触发| Notify
Notify --> Loop
Loop -->|propertyChange| Update
Update -->|setText| TextField
TextField -.->|防循环检查| EventHandler
数据绑定机制对比分析:
AngularJS脏检查机制:AngularJS 1.x采用$digest循环实现数据绑定。在每次可能改变Model的操作后(如点击事件、digest循环,遍历所有digest`循环,直到模型稳定(最多10次迭代)。这种方式本质上是拉模型+批处理:View不直接观察Model变化,而是依赖框架统一检查。优点是对JavaScript引擎无特殊要求,缺点是性能随watch数量线性下降。
Vue/React观察者模式:Vue 2通过Object.defineProperty劫持数据属性的getter/setter,在getter中收集依赖(Watcher),在setter中触发通知。React则通过setState显式触发更新。这种推模型实现粒度更细、性能更高,因为只有真正变化的数据才会触发对应的视图更新。Vue 3进一步升级为Proxy,可以拦截更多操作(数组索引、属性增删等)。
双向绑定防循环:当View更新Model、Model又通知View更新时,可能形成死循环。解决方案是在View更新时检查新旧值:if (!textField.getText().equals(newValue)) { ... },只有值确实不同时才更新UI。
场景四:文件目录监控
完整可运行Demo:
// ==================== 文件事件定义 ====================
enum FileEventType { CREATED, MODIFIED, DELETED }
class FileChangeEvent {
private final Path path;
private final FileEventType type;
private final Instant timestamp;
// 构造器、getter...
}
// ==================== 观察者接口 ====================
interface FileChangeListener {
void onFileChanged(FileChangeEvent event);
}
// ==================== 目录监控主题 ====================
class DirectoryMonitor implements Runnable {
private final Path directory;
private final WatchService watchService;
private final List<FileChangeListener> listeners = new CopyOnWriteArrayList<>();
private volatile boolean running = true;
public DirectoryMonitor(Path directory) throws IOException {
this.directory = directory;
this.watchService = FileSystems.getDefault().newWatchService();
directory.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
}
public void addListener(FileChangeListener listener) {
listeners.add(listener);
}
@Override
public void run() {
System.out.println("[目录监控] 开始监控: " + directory);
while (running) {
try {
WatchKey key = watchService.poll(1, TimeUnit.SECONDS);
if (key == null) continue;
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) continue;
Path filename = (Path) event.context();
Path fullPath = directory.resolve(filename);
FileEventType type = mapEventType(kind);
FileChangeEvent fileEvent = new FileChangeEvent(fullPath, type, Instant.now());
notifyListeners(fileEvent);
}
if (!key.reset()) break; // 目录不可访问,退出
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println("[目录监控] 停止监控");
}
private FileEventType mapEventType(WatchEvent.Kind<?> kind) {
if (kind == StandardWatchEventKinds.ENTRY_CREATE) return FileEventType.CREATED;
if (kind == StandardWatchEventKinds.ENTRY_MODIFY) return FileEventType.MODIFIED;
if (kind == StandardWatchEventKinds.ENTRY_DELETE) return FileEventType.DELETED;
throw new IllegalArgumentException("Unknown kind: " + kind);
}
private void notifyListeners(FileChangeEvent event) {
listeners.forEach(listener -> {
try {
listener.onFileChanged(event);
} catch (Exception e) {
System.err.println("监听器处理异常: " + e.getMessage());
}
});
}
public void stop() {
running = false;
try { watchService.close(); } catch (IOException ignored) {}
}
}
// ==================== 具体监听器 ====================
class LoggingFileListener implements FileChangeListener {
@Override
public void onFileChanged(FileChangeEvent event) {
System.out.printf("[日志监听] %s - %s%n",
event.getType(), event.getPath().getFileName());
}
}
class AutoBackupListener implements FileChangeListener {
private final Path backupDir;
public AutoBackupListener(Path backupDir) {
this.backupDir = backupDir;
try { Files.createDirectories(backupDir); }
catch (IOException e) { throw new RuntimeException(e); }
}
@Override
public void onFileChanged(FileChangeEvent event) {
if (event.getType() == FileEventType.MODIFIED ||
event.getType() == FileEventType.CREATED) {
try {
Path source = event.getPath();
Path target = backupDir.resolve(source.getFileName().toString()
+ "." + System.currentTimeMillis() + ".bak");
Files.copy(source, target);
System.out.printf("[自动备份] %s -> %s%n",
source.getFileName(), target.getFileName());
} catch (IOException e) {
System.err.println("备份失败: " + e.getMessage());
}
}
}
}
// ==================== 演示 ====================
public class FileWatchDemo {
public static void main(String[] args) throws Exception {
Path watchDir = Paths.get("./watched");
Files.createDirectories(watchDir);
DirectoryMonitor monitor = new DirectoryMonitor(watchDir);
monitor.addListener(new LoggingFileListener());
monitor.addListener(new AutoBackupListener(Paths.get("./backup")));
Thread monitorThread = new Thread(monitor);
monitorThread.start();
// 模拟文件操作
Thread.sleep(500);
Path testFile = watchDir.resolve("test.txt");
Files.write(testFile, "Hello World".getBytes());
Thread.sleep(200);
Files.write(testFile, "Updated content".getBytes(), StandardOpenOption.APPEND);
Thread.sleep(200);
Files.delete(testFile);
Thread.sleep(500);
monitor.stop();
monitorThread.join();
}
}
Mermaid流程图:
flowchart TD
subgraph OS[操作系统层]
FS[文件系统事件]
end
subgraph JVM[Java WatchService]
WS[WatchService.poll]
WK[WatchKey.pollEvents]
end
subgraph Monitor[DirectoryMonitor 主题]
Loop[事件循环]
Parse[解析WatchEvent]
Map[映射为FileChangeEvent]
Notify[notifyListeners]
end
subgraph Listeners[观察者]
L1[LoggingFileListener]
L2[AutoBackupListener]
L3[其他监听器...]
end
FS -->|inotify/ReadDirectoryChangesW| WS
WS -->|获取| Loop
Loop --> WK
WK --> Parse
Parse --> Map
Map --> Notify
Notify --> L1
Notify --> L2
Notify --> L3
JDK WatchService底层实现分析:
WatchService是Java NIO.2提供的文件系统监控API,其底层实现因操作系统而异:
- Linux:基于
inotify子系统,内核在文件事件发生时直接通知用户态,效率极高。WatchKey对应inotify实例,pollEvents()从内核事件队列读取。 - Windows:基于
ReadDirectoryChangesWAPI,异步I/O方式监控目录变化。 - macOS:基于kqueue/ FSEvents。
观察者模式封装的价值:原始WatchService API使用较为繁琐(需要循环poll、处理OVERFLOW事件、reset key等)。通过观察者模式封装,将底层的轮询逻辑与业务处理逻辑分离。监听器只需关注FileChangeEvent,无需关心底层API细节。此外,封装层可以轻松添加事件去重(短时间内同一文件的多次修改合并)、防抖处理(等待文件写入完成再触发)、模式过滤(按文件扩展名分发)等高级特性。
场景五:社交媒体动态推送
完整可运行Demo:
// ==================== 领域模型 ====================
class Post {
private final String postId;
private final String authorId;
private final String content;
private final Instant timestamp;
// 构造器、getter...
}
class User {
private final String userId;
private final String name;
private final List<String> following = new ArrayList<>();
private final List<Post> timeline = new ArrayList<>();
// getter、业务方法...
}
// ==================== 主题 ====================
class SocialFeedSubject {
// 用户ID -> 粉丝的观察者列表
private final Map<String, Set<FeedObserver>> followerMap = new ConcurrentHashMap<>();
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
public void follow(String followerId, String followeeId, FeedObserver observer) {
followerMap.computeIfAbsent(followeeId, k -> ConcurrentHashMap.newKeySet())
.add(new ObserverWrapper(followerId, observer));
System.out.printf("[社交] %s 关注了 %s%n", followerId, followeeId);
}
public void unfollow(String followerId, String followeeId) {
Set<FeedObserver> followers = followerMap.get(followeeId);
if (followers != null) {
followers.removeIf(w -> ((ObserverWrapper) w).followerId.equals(followerId));
}
}
public void publishPost(Post post) {
System.out.printf("\n[动态] %s 发布了: %s%n", post.getAuthorId(), post.getContent());
notifyFollowers(post.getAuthorId(), post);
}
private void notifyFollowers(String authorId, Post post) {
Set<FeedObserver> followers = followerMap.get(authorId);
if (followers == null || followers.isEmpty()) return;
// 粉丝列表分片批量处理
List<List<FeedObserver>> batches = partition(new ArrayList<>(followers), 100);
CompletableFuture.allOf(batches.stream()
.map(batch -> CompletableFuture.runAsync(() ->
batch.forEach(observer -> {
try {
observer.onNewPost(post);
} catch (Exception e) {
System.err.println("推送失败: " + e.getMessage());
}
}), executor))
.toArray(CompletableFuture[]::new))
.join();
}
private <T> List<List<T>> partition(List<T> list, int size) {
List<List<T>> partitions = new ArrayList<>();
for (int i = 0; i < list.size(); i += size) {
partitions.add(list.subList(i, Math.min(i + size, list.size())));
}
return partitions;
}
// 包装器携带粉丝ID
private record ObserverWrapper(String followerId, FeedObserver delegate)
implements FeedObserver {
@Override
public void onNewPost(Post post) {
delegate.onNewPost(post);
}
}
}
// ==================== 观察者接口 ====================
interface FeedObserver {
void onNewPost(Post post);
}
class UserFeedView implements FeedObserver {
private final String userId;
private final List<Post> timeline = new ArrayList<>();
public UserFeedView(String userId) {
this.userId = userId;
}
@Override
public void onNewPost(Post post) {
timeline.add(0, post); // 最新动态在前
System.out.printf(" [%s的Feed] 收到 %s 的新动态: %s%n",
userId, post.getAuthorId(), post.getContent());
}
public List<Post> getTimeline() {
return Collections.unmodifiableList(timeline);
}
}
// ==================== 演示 ====================
public class SocialFeedDemo {
public static void main(String[] args) {
SocialFeedSubject feedSystem = new SocialFeedSubject();
UserFeedView aliceFeed = new UserFeedView("Alice");
UserFeedView bobFeed = new UserFeedView("Bob");
UserFeedView charlieFeed = new UserFeedView("Charlie");
// 建立关注关系
feedSystem.follow("Alice", "Bob", aliceFeed);
feedSystem.follow("Charlie", "Bob", charlieFeed);
feedSystem.follow("Bob", "Charlie", bobFeed);
// 发布动态
feedSystem.publishPost(new Post("p1", "Bob", "今天天气真好!", Instant.now()));
feedSystem.publishPost(new Post("p2", "Charlie", "刚看完一本好书", Instant.now()));
feedSystem.publishPost(new Post("p3", "Bob", "晚上吃什么?", Instant.now()));
// 取关
feedSystem.unfollow("Alice", "Bob");
feedSystem.publishPost(new Post("p4", "Bob", "Alice看不到了", Instant.now()));
}
}
Mermaid时序图:
sequenceDiagram
participant Author as 发布者
participant Subject as SocialFeedSubject
participant Batch1 as 粉丝批次1
participant Batch2 as 粉丝批次N
participant Alice as Alice Feed
participant Bob as Bob Feed
Author->>Subject: publishPost(post)
activate Subject
Subject->>Subject: 获取粉丝列表
par 并行批量推送
Subject->>Batch1: 异步推送批次1
activate Batch1
Batch1->>Alice: onNewPost(post)
activate Alice
Alice->>Alice: 更新Timeline
Alice-->>Batch1:
deactivate Alice
Batch1-->>Subject:
deactivate Batch1
and
Subject->>Batch2: 异步推送批次N
activate Batch2
Batch2->>Bob: onNewPost(post)
activate Bob
Bob->>Bob: 更新Timeline
Bob-->>Batch2:
deactivate Bob
Batch2-->>Subject:
deactivate Batch2
end
Subject-->>Author: 推送完成
deactivate Subject
大规模粉丝场景性能优化分析:
社交媒体动态推送面临极端的规模挑战:一个头部用户可能有千万级粉丝,每次发布动态都需要在短时间内完成海量推送。观察者模式在此场景下必须结合多种优化策略。
1. 消息队列异步解耦:动态发布后立即写入消息队列,推送服务异步消费。发布者无需等待推送完成,大幅提升响应速度。
// 发布动态时不直接推送,而是写入Kafka
kafkaProducer.send(new ProducerRecord<>("new-posts", post.getAuthorId(), post));
2. 粉丝列表分片与并行消费:推送服务读取粉丝列表后,按固定大小(如1000人)分片,每个分片由一个Worker处理。Worker内部再并发推送给具体用户。
// 分片推送
List<String> followers = getFollowers(authorId);
int shardSize = 1000;
IntStream.range(0, (followers.size() + shardSize - 1) / shardSize)
.parallel()
.forEach(shard -> pushToShard(followers, shard * shardSize, shardSize));
3. 推拉结合(Push-Pull Hybrid):对活跃粉丝采用推模型(直接写入其Timeline缓存),对非活跃粉丝采用拉模型(用户上线时从收件箱拉取)。判断活跃度的依据可以是最近登录时间、互动频率等。
4. 收件箱(Inbox)模式:每个用户维护一个Timeline收件箱(Redis List或Cassandra表)。发布动态时,不直接推送给所有粉丝,而是将Post ID写入粉丝的收件箱。用户查看Feed时从收件箱读取ID列表,再批量获取Post详情。
5. 名人用户特殊处理:对粉丝数超过阈值(如100万)的头部用户,不采用推模型,而是将他们的动态存入专门的"名人动态池",粉丝的Feed服务在生成时主动从该池拉取。这避免了每次名人发布动态时的写入风暴。
6. 地理位置分片与CDN加速:对于图片、视频等大文件内容,上传时同步到CDN边缘节点。推送通知中仅包含内容URL,实际内容由用户终端从最近的CDN节点拉取。
七、面试题精选与专家级解答
1. JDK内置的Observable和Observer有什么缺陷?为什么在现代Java开发中很少使用?
参考答案:
JDK内置观察者模式的缺陷主要集中在以下几个方面:
设计层面的根本缺陷:
Observable是类而非接口。由于Java单继承限制,任何希望成为主题的业务类如果已经继承了其他父类,就无法再继承Observable。这严重违背了"组合优于继承"的设计原则。setChanged()方法的反直觉设计。开发者必须显式调用protected setChanged()才能标记状态变更,notifyObservers()才会真正执行通知。这一设计初衷是支持批量变更优化,但实践中极易因遗忘调用而导致通知静默失败。
线程安全方面的瑕疵:
- 虽然
Observable使用Vector存储观察者和synchronized方法,但notifyObservers()的实现存在竞态条件:它在同步块内复制观察者快照,然后在同步块外遍历通知。这意味着通知期间新注册的观察者不会收到本次通知(合理),但被移除的观察者仍可能收到通知(不合理)。 - 倒序遍历观察者(
for (int i = arrLocal.length-1; i>=0; i--))属于未文档化的实现细节,依赖此行为的代码脆弱且难以维护。
类型安全缺失:
Observer.update(Observable o, Object arg)的arg参数为Object类型,观察者必须进行类型转换,丧失了编译期类型检查。在泛型普及的现代Java中,这是不可接受的。
功能简陋:
- 不支持按事件类型过滤:所有观察者收到相同的通知,无法精细订阅特定事件。
- 无内置异步支持:同步通知会阻塞主题线程。
- 无错误隔离机制:一个观察者抛出异常会中断整个通知链。
现代替代方案:Spring的ApplicationEvent、Guava的EventBus、RxJava的Observable,或自行基于PropertyChangeSupport/CopyOnWriteArrayList实现。
2. Spring事件机制是如何实现观察者模式的?ApplicationEventMulticaster的作用是什么?
参考答案:
Spring事件机制是观察者模式的教科书级实现,其架构可分为三层:
第一层:事件定义与发布
ApplicationEvent继承自EventObject,是所有事件的基类。ApplicationEventPublisher是主题接口,ApplicationContext实现了该接口,因此任何Spring Bean都可以通过注入ApplicationEventPublisher或直接发布事件。- Spring 4.2+支持发布任意对象作为事件(无需继承
ApplicationEvent),内部会包装为PayloadApplicationEvent。
第二层:监听器注册与发现
ApplicationListener<E>是观察者接口,通过泛型E声明感兴趣的事件类型。- 监听器可通过多种方式注册:
@Component注解的Bean自动检测、@EventListener注解方法、编程式addApplicationListener()。 ApplicationEventMulticaster在容器启动时收集所有监听器,并维护ListenerRetriever缓存,按事件类型建立索引以加速匹配。
第三层:事件广播——ApplicationEventMulticaster的核心作用
ApplicationEventMulticaster是Spring事件机制的执行引擎,承担以下关键职责:
-
监听器管理:维护
defaultRetriever和按事件类型分组的监听器缓存。当容器中有新的监听器Bean时,通过addApplicationListener()动态添加。 -
事件-监听器匹配:利用
ResolvableType进行泛型类型匹配。例如ApplicationListener<OrderCreatedEvent>只接收OrderCreatedEvent及其子类事件。这是通过GenericApplicationListener接口和ResolvableType实现的类型安全匹配。 -
同步/异步广播切换:
SimpleApplicationEventMulticaster维护一个Executor引用。若taskExecutor非空,则为每个监听器提交异步任务;否则在当前线程同步调用。这使得应用可以通过简单配置从同步切换为异步。 -
错误隔离:通过
ErrorHandler捕获监听器执行异常,防止单个监听器失败导致整个广播中断。默认实现是抛出异常中断后续监听器,生产环境通常配置自定义ErrorHandler记录日志并继续。 -
早期事件缓存:在
ApplicationEventMulticaster初始化完成前(如BeanFactoryPostProcessor阶段),AbstractApplicationContext会将发布的早期事件暂存在earlyApplicationEvents集合中,待广播器就绪后重放。
3. 推模型和拉模型在观察者模式中分别指什么?各有什么优缺点?
参考答案:
详见1.3节"推模型与拉模型的深度对比"。补充要点:
推模型的适用场景:
- 数据量小、变化频率低的配置信息推送
- 实时性要求极高的行情数据
- 观察者对数据需求高度一致
拉模型的适用场景:
- 数据量大、观察者只关心部分字段
- 观察者需要按需延迟加载(如详情页数据)
- 主题状态变更频繁,但观察者处理节奏自主控制
Java生态案例:
- 推模型:
PropertyChangeEvent携带新旧值,Servlet的ServletContextListener接收ServletContextEvent。 - 拉模型:
java.util.Observable,ZooKeeper Watcher(通知后客户端需主动拉取数据)。
4. 如何防止观察者模式中的内存泄漏?请列举几种解决方案。
参考答案:
观察者模式的内存泄漏根源在于:主题持有观察者的强引用,而观察者可能已不再被应用的其他部分使用。在长期运行的应用(如Web容器)中,这会导致观察者无法被GC回收,最终OOM。
解决方案一:显式注销(Explicit Deregistration)
最直接的方式——确保观察者在不再需要时主动调用removeObserver()。
@Component
public class MyObserver implements ApplicationListener<MyEvent> {
@Autowired private ApplicationContext context;
@PreDestroy
public void cleanup() {
// Spring会自动清理ApplicationListener,此为示例
}
}
解决方案二:弱引用(WeakReference)
主题使用WeakReference包装观察者,当观察者没有其他强引用时自动被GC。
public class WeakReferenceSubject {
private final List<WeakReference<Observer>> observers = new ArrayList<>();
private final ReferenceQueue<Observer> queue = new ReferenceQueue<>();
public void register(Observer o) {
cleanUp(); // 清理已被GC的引用
observers.add(new WeakReference<>(o, queue));
}
private void cleanUp() {
Reference<? extends Observer> ref;
while ((ref = queue.poll()) != null) {
observers.remove(ref);
}
}
public void notifyObservers() {
cleanUp();
Iterator<WeakReference<Observer>> it = observers.iterator();
while (it.hasNext()) {
Observer o = it.next().get();
if (o == null) it.remove();
else o.update();
}
}
}
注意:弱引用适用于观察者生命周期不由主题控制的场景,但需要额外的清理逻辑。
解决方案三:Spring的事件机制自动清理
Spring的ApplicationEventMulticaster在AbstractApplicationContext.doClose()时会清空所有监听器。对于@EventListener注解的方法,Spring通过ApplicationListenerMethodAdapter持有Bean的弱引用还是强引用?实际上Spring使用强引用,但依赖容器关闭时的清理。对于Web环境,建议将监听器定义为原型作用域或确保在Session/Request销毁时注销。
解决方案四:Guava EventBus的显式注销
EventBus bus = new EventBus();
Object listener = new Object() {
@Subscribe void handle(String event) {}
};
bus.register(listener);
// 不再需要时
bus.unregister(listener);
解决方案五:使用Cleaner或PhantomReference
Java 9+的Cleaner可以在对象被GC时执行清理动作,自动从主题注销。但这会引入一定的复杂性和性能开销,一般不建议作为首选方案。
5. ZooKeeper的Watcher为什么设计为一次性触发?这种设计有什么优缺点?
参考答案:
ZooKeeper将Watcher设计为一次性触发(One-Time Trigger)是基于分布式协调服务的独特场景考量。
设计原因:
-
防止通知风暴:在分布式环境中,ZooKeeper集群的状态变化可能非常频繁(如临时节点的会话过期、配置变更、Leader选举)。如果Watcher是持久订阅,客户端可能在短时间内收到海量通知,不仅可能压垮客户端,还会对ZooKeeper服务端造成巨大负担。
-
确保通知被处理:一次性触发强制客户端在处理完通知后主动重新注册Watcher。这个重新注册的动作向服务端传达了一个明确信号:客户端已成功处理上次变更,现在准备好接收下一次变更。这实质上实现了一种应用层的背压(Backpressure)。
-
简化服务端状态管理:ZooKeeper服务端无需维护每个客户端的长连接订阅关系,只需在触发Watcher后将其从Watch Manager中移除。这大幅简化了服务端设计,提升了集群扩展能力。
-
天然的状态同步:收到通知后,客户端通常需要重新从ZooKeeper读取最新数据(拉模型)。重新读取的过程自然保证了客户端获得的是最新状态,避免了推送数据可能存在的时序问题。
优点:
- 防止过时通知堆积:客户端处理慢时不会积压未处理的通知。
- 服务端内存占用小:Watch对象在触发后即被清理。
- 语义清晰:每次通知都代表"状态已变更,你需要重新读取"。
缺点:
- 编程模型复杂:开发者必须记得在每次处理通知后重新注册Watcher。
- 事件丢失风险:在重新注册的间隙(时间窗口)内发生的状态变更会丢失通知。虽然可以通过读取数据时的版本号(Stat)检测,但增加了开发负担。
- 代码冗余:业务逻辑与Watcher重注册逻辑耦合。
最佳实践:使用Curator等高级客户端的NodeCache、PathChildrenCache,它们在内部自动处理Watcher的重注册,对外提供持续监听的语义。
6. 观察者模式和发布-订阅模式常被混淆,它们的本质区别是什么?
参考答案:
详见第五章"对比辨析"表格。核心区别可概括为:
观察者模式:主题(Subject)直接知晓并维护观察者(Observer)列表,通知由主题直接发起。耦合存在于主题与观察者接口之间。
发布-订阅模式:发布者(Publisher)与订阅者(Subscriber)完全隔离,通过独立的事件通道/消息代理通信。发布者和订阅者彼此不知道对方的存在。
形象类比:
- 观察者模式 = 老师(主题)拿着花名册点名通知学生(观察者)。老师知道学生是谁。
- 发布-订阅模式 = 广播电台(消息代理)发射信号,收音机(订阅者)调频接收。电台不知道谁在收听。
技术选型:
- 进程内通信、单一应用 → 观察者模式
- 跨进程、跨服务、分布式系统 → 发布-订阅模式
7. 在分布式系统中,如何利用观察者模式实现配置中心的动态刷新?
参考答案:
配置中心的动态刷新是观察者模式在分布式环境下的典型应用,结合了长轮询和推送两种机制。
架构流程:
-
客户端注册监听:应用启动时,向配置中心服务端注册对特定配置项(如
application.properties)的监听。这可以通过HTTP长轮询或gRPC双向流实现。 -
配置变更触发:管理员在配置中心控制台修改配置,服务端将变更持久化到数据库,并更新内存缓存。
-
变更通知分发:服务端遍历所有注册了该配置项的客户端长轮询连接,向每个连接写入变更通知(或等待中的HTTP请求返回响应)。
-
客户端接收与刷新:客户端收到通知后,从配置中心拉取最新配置(拉模型),更新本地缓存,并通过Spring Cloud的
RefreshScope或自定义@RefreshScope注解刷新使用该配置的Bean。 -
环境变量/系统属性更新:对于
@Value注入的属性,需要借助ContextRefresher或EnvironmentChangeEvent触发重新绑定。
关键代码示例(Spring Cloud Config + Bus):
// 配置刷新端点
@RestController
@RequestMapping("/monitor")
public class RefreshController {
@Autowired private ContextRefresher refresher;
@PostMapping("/refresh")
public Set<String> refresh() {
return refresher.refresh(); // 刷新@RefreshScope的Bean
}
}
// 通过Spring Cloud Bus广播刷新事件
@EventListener
public void handleRefreshRemoteApplicationEvent(RefreshRemoteApplicationEvent event) {
refresher.refresh();
}
与观察者模式的对应关系:
- 主题:配置中心服务端
- 观察者:各微服务客户端实例
- 通知机制:长轮询(近实时推送)或消息总线(Spring Cloud Bus)
- 状态同步:拉模型,客户端主动获取最新配置
8. RxJava中的Observable与GoF观察者模式有何异同?背压机制解决了什么问题?
参考答案:
相同点:
- 都定义了生产者(Observable/Subject)和消费者(Observer)角色
- 都基于订阅(Subscribe)关系建立连接
- 核心交互都是生产者向消费者推送数据
关键差异:
| 特性 | GoF观察者模式 | RxJava Observable |
|---|---|---|
| 终止信号 | 无明确终止 | onComplete()明确结束 |
| 错误处理 | 观察者各自处理 | 统一onError()通道 |
| 背压 | 不支持 | Flowable支持request(n) |
| 操作符 | 无 | 丰富的map/filter/flatMap等 |
| 异步支持 | 需自行实现 | 内置Scheduler线程调度 |
| 组合能力 | 弱 | 强大的流式组合 |
| 冷热流 | 无区分 | Cold(订阅才发)vs Hot(即时发) |
背压(Backpressure)机制: 背压解决的是生产者生产速度快于消费者消费速度导致的数据积压问题。在传统观察者模式中,如果观察者处理速度跟不上主题的通知速度,数据会在观察者的处理队列中无限积压,最终导致内存溢出。
RxJava通过Flowable和Subscription.request(n)实现响应式拉取:
Flowable.range(1, 1_000_000)
.onBackpressureBuffer(1000) // 缓冲区大小
.observeOn(Schedulers.computation())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
s.request(10); // 初始请求10个元素
}
@Override
public void onNext(Integer i) {
process(i);
if (i % 5 == 0) {
subscription.request(5); // 处理完5个再请求5个
}
}
});
消费者通过request(n)明确告知上游自己还能处理多少数据,上游据此控制发送速率,实现了从推模型到拉模型的灵活切换。
9. 如何处理观察者执行过程中抛出异常导致后续观察者无法执行的问题?
参考答案:
这是观察者模式生产实践中的关键问题。处理策略可分为以下几个层次:
策略一:主题内部捕获隔离(推荐)
public void notifyObservers(Event event) {
for (Observer observer : observers) {
try {
observer.update(event);
} catch (Exception e) {
log.error("观察者 {} 执行失败", observer.getClass(), e);
// 可选:记录失败事件用于补偿
failedQueue.add(new FailedNotification(observer, event));
}
}
}
优点:简单可靠,确保一个观察者失败不影响其他。缺点:无法区分异常类型(业务异常 vs 系统异常)。
策略二:使用ErrorHandler抽象(Spring风格)
public interface ErrorHandler {
void handleError(Observer observer, Event event, Exception ex);
}
public class LoggingErrorHandler implements ErrorHandler {
@Override
public void handleError(Observer o, Event e, Exception ex) {
if (ex instanceof RecoverableException) {
retryQueue.add(o, e);
} else {
log.error("不可恢复错误", ex);
}
}
}
策略三:异步隔离 将观察者执行放到独立线程池,即使抛出异常也只影响当前线程:
observers.forEach(o -> executor.submit(() -> {
try {
o.update(event);
} catch (Exception e) {
errorHandler.handle(o, event, e);
}
}));
策略四:CompletableFuture异常处理
List<CompletableFuture<Void>> futures = observers.stream()
.map(o -> CompletableFuture.runAsync(() -> o.update(event))
.exceptionally(ex -> {
errorHandler.handle(o, event, ex);
return null;
}))
.collect(toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
策略五:带熔断的观察者包装器 对于频繁失败的观察者,可以引入熔断机制:
class CircuitBreakerObserver implements Observer {
private final Observer delegate;
private final CircuitBreaker breaker;
@Override
public void update(Event e) {
if (breaker.tryAcquire()) {
try {
delegate.update(e);
breaker.onSuccess();
} catch (Exception ex) {
breaker.onFailure();
}
}
}
}
10. 观察者模式与事件溯源(Event Sourcing)有什么联系?如何在微服务架构中结合使用?
参考答案:
概念辨析:
- 观察者模式:关注状态变化的实时通知,强调对象间的松耦合通信。
- 事件溯源(Event Sourcing):将对象的状态变更建模为一系列不可变事件,通过重放事件序列重建当前状态。关注的是状态变更的可追溯性与可恢复性。
两者的联系:
- 事件是共同的抽象:在事件溯源中,每次状态变更都产生一个事件对象;观察者模式可以将这些事件通知给订阅者。
- CQRS中的角色分工:命令端(写模型)使用事件溯源存储事件,并发布领域事件;查询端(读模型)作为观察者订阅这些事件,更新物化视图。
- Event Store作为主题:Event Store(事件存储)可以充当观察者模式中的主题,当新事件追加时通知所有订阅者。
微服务中的结合实践:
// 命令端:订单服务(写模型)
@Service
@Transactional
public class OrderCommandService {
@Autowired private EventStore eventStore;
@Autowired private DomainEventPublisher publisher;
public void createOrder(CreateOrderCommand cmd) {
// 1. 生成领域事件
OrderCreatedEvent event = new OrderCreatedEvent(cmd.getOrderId(), cmd.getAmount());
// 2. 事件持久化(Event Sourcing)
eventStore.append("order", cmd.getOrderId(), event, expectedVersion);
// 3. 发布事件(观察者模式通知)
publisher.publish(event);
}
}
// 查询端:订单视图服务(读模型观察者)
@Component
public class OrderViewUpdater {
@Autowired private OrderViewRepository repository;
@EventListener
@TransactionalEventListener(phase = AFTER_COMMIT) // 确保事件已持久化
public void onOrderCreated(OrderCreatedEvent event) {
OrderView view = new OrderView(event.getOrderId(), event.getAmount(), "CREATED");
repository.save(view);
}
}
结合的价值:
- 审计日志天然形成:事件溯源的事件存储本身就是完整的审计轨迹。
- 故障恢复能力:查询端可随时通过重放事件重建视图。
- 时间旅行调试:可以回到任意时间点的状态进行问题排查。
- 集成解耦:其他限界上下文通过订阅领域事件实现最终一致性。
挑战:
- 事件版本演进需要精心设计
- 最终一致性带来的用户体验问题
- 事件存储的查询能力较弱
结语
观察者模式作为设计模式家族中最具生命力的成员之一,其身影遍布从单机应用到分布式系统的每一个角落。从JDK 1.0时代的Observable,到响应式编程的Flow API,再到云原生时代的配置中心与事件驱动架构,观察者模式不断演化,但其核心思想——"解耦状态变更者与状态响应者"——始终未变。
理解观察者模式,不仅是掌握一个具体的设计模式,更是领会一种设计哲学:在软件系统中,变化是常态,如何优雅地应对变化是架构师的永恒课题。通过本文从基础实现到框架源码再到分布式变体的全景剖析,希望读者不仅能在面试中对答如流,更能在实际工作中灵活运用这一模式,构建出松耦合、高内聚、易扩展的优雅系统。
观察者模式的学习也是通向更广阔技术领域的桥梁——事件驱动架构(EDA)、响应式编程(Reactive Programming)、CQRS与Event Sourcing——这些现代架构范式都深深植根于观察者模式的思想土壤。愿本文成为您探索这些领域的坚实起点。