Java 内置观察者模式
一、基础概念
观察者模式属于行为型设计模式,核心思想:
一对多依赖关系:一个目标(被观察者/主题)状态发生变化时,自动通知所有订阅它的观察者,观察者收到通知后执行自身更新逻辑,二者解耦。
Java 早期 JDK 直接提供了一套原生 API 实现观察者模式,位于java.util包下,由两个核心类/接口构成:
java.util.Observable:被观察者(主题、发布者)抽象父类(类,非接口)java.util.Observer:观察者函数式接口
⚠️ 重要前提:Java 9 开始被标记为@Deprecated(过时废弃),官方不再推荐使用。
二、两大核心组件详解
1. Observer 观察者接口
@FunctionalInterfacepublicinterfaceObserver{/** * 被观察者状态变更,回调此方法通知观察者 * @param o 触发通知的被观察者对象 * @param arg 推送携带的自定义参数(可选) */voidupdate(Observableo,Objectarg);}- 函数式接口:可以直接用 Lambda 简写实现
- 所有观察者必须实现该接口,重写
update()接收消息推送
2. Observable 被观察者(主题)
是一个普通类,不能多继承,内部维护了观察者集合,提供订阅、取消订阅、状态变更通知全套方法:
核心成员变量
// 存放所有已订阅的观察者,线程安全容器privatefinalVector<Observer>obs;// 标记被观察者自身状态是否发生改变privatebooleanchanged=false;常用核心方法
| 方法 | 作用 |
|---|---|
void addObserver(Observer o) | 订阅:添加观察者到集合 |
void deleteObserver(Observer o) | 取消订阅:移除指定观察者 |
void deleteObservers() | 清空所有观察者 |
int countObservers() | 获取当前订阅的观察者数量 |
protected void setChanged() | 关键:标记自身状态已修改;只有标记后,才会执行通知 |
protected void clearChanged() | 清除状态变更标记 |
boolean hasChanged() | 查询是否标记了状态变更 |
void notifyObservers() | 无参推送:通知所有观察者,arg=null |
void notifyObservers(Object arg) | 带参数推送:携带数据传给观察者update方法 |
通知执行流程
- 业务修改被观察者数据 → 调用
setChanged()打上变更标记 - 调用
notifyObservers() - 内部遍历所有观察者,挨个调用
observer.update(this, 参数) - 通知完毕自动调用
clearChanged()清空标记
三、完整代码示例(手把手演示)
场景:公众号(被观察者)发布文章,订阅用户(观察者)收到推送通知
importjava.util.Observable;importjava.util.Observer;// 1. 自定义被观察者:公众号classOfficialAccountextendsObservable{// 发布文章方法publicvoidpublishArticle(Stringcontent){System.out.println("公众号发布新文章:"+content);// 第一步:标记状态发生改变(必须调用!否则不会推送通知)setChanged();// 第二步:推送消息给所有订阅者,携带文章内容notifyObservers(content);}}// 2. 测试主类publicclassObserverDemo{publicstaticvoidmain(String[]args){// 创建被观察者OfficialAccountaccount=newOfficialAccount();// 观察者1:用户AObserveruserA=(obs,arg)->{System.out.println("用户A收到推送:新文章 -> "+arg);};// 观察者2:用户BObserveruserB=(obs,arg)->{System.out.println("用户B收到推送:新文章 -> "+arg);};// 订阅account.addObserver(userA);account.addObserver(userB);// 发布文章,触发通知account.publishArticle("Java观察者模式详解");System.out.println("=====用户B取消订阅后====");// 取消订阅account.deleteObserver(userB);account.publishArticle("Spring源码阅读笔记");}}运行结果:
公众号发布新文章:Java观察者模式详解 用户B收到推送:新文章 -> Java观察者模式详解 用户A收到推送:新文章 -> Java观察者模式详解 =====用户B取消订阅后==== 公众号发布新文章:Spring源码阅读笔记 用户A收到推送:新文章 -> Spring源码阅读笔记四、JDK内置实现的优缺点
优点
- 开箱即用,无需自己维护观察者集合、订阅/取消订阅、通知遍历逻辑,代码极简
- 自带状态变更标记机制,可灵活控制什么时候推送消息
- 自带线程安全(底层Vector),基础多线程场景不用自己加锁
致命缺点(也是被废弃的根本原因)
Observable是类,不是接口,违反面向接口编程
Java是单继承,如果你的业务类已经继承了其他父类,就无法再继承Observable,扩展性极差。无法自定义通知顺序、通知策略
内部遍历Vector有序推送,不能调整观察者回调顺序;同步阻塞推送,一个观察者耗时过长会阻塞后续所有观察者。耦合度偏高
被观察者、观察者都强依赖JDK util包内置类,不方便替换实现。无法精细控制通知粒度
只能全体推送,不支持按话题分组订阅(类似MQ的主题订阅)。线程模型单一:默认同步调用,没有异步通知能力。
五、为什么 Java 9+ 废弃这套API?
- 设计缺陷:Observable作为具体类,继承限制太大,不符合开闭原则;
- 功能简陋:不支持异步、分组订阅、事件类型区分;
- 官方推荐替代方案:
- 小型场景:自己手写观察者模式(自定义主题接口+观察者接口)
- 企业级开发:使用事件驱动框架:Guava EventBus、Spring 事件监听(ApplicationEvent)、RxJava、消息队列(RabbitMQ/Kafka)
六、对比:手写观察者 vs JDK内置
- JDK内置:省事,但受继承约束、扩展性差;
- 手写自定义:自己定义
Subject主题接口、Observer观察者接口,任意类都能实现,自由度最高,日常开发首选。
七、拓展:Spring中的事件监听(观察者模式最佳实践)
Spring框架基于观察者思想封装了事件机制,彻底规避了JDK原生的所有问题:
- ApplicationEvent:事件(消息载体)
- ApplicationEventPublisher:发布者(被观察者)
- ApplicationListener:事件监听者(观察者)
支持异步监听、条件监听、注解驱动(@EventListener),是Java后端最常用的观察者落地方案。