一、问题背景:为什么写这篇?
最近在做一个小需求——订单状态变更后,要同时触发库存扣减、短信通知、积分变更、日志记录……
我第一反应是什么?简单啊!
if(order.getStatus()==PAID){inventoryService.deduct(order);smsService.sendNotice(order);pointsService.addPoints(order);logService.saveLog(order);}if(order.getStatus()==PAID){inventoryService.deduct(order);smsService.sendNotice(order);pointsService.addPoints(order);logService.saveLog(order);couponService.issueCoupon(order);// 新加的statsService.record(order);// 新加的pushService.push(order);// 新加的// 未来还有 N 个服务等着加...}这就是典型的"牵一发动全身"——每加一个功能,都得改订单核心逻辑,改完还得重新测试整个流程。
程序跑起来了,但维护的人头发少了 😅
后来我想起来了——这不就是观察者模式的经典场景吗?
谁该干活,谁自己订阅就行了,别都挤到订单里来!
二、观察者模式是什么?
用一句话说:
一个对象状态变了,所有依赖它的对象自动收到通知并更新。
生活中的例子:
- 微信群:有人发消息,所有群成员都能收到
- B站关注:UP主发新视频,粉丝收到推送
- 红绿灯:灯变了,所有车和行人同时响应
核心思想就四个字:发布-订阅。
三、UML 结构(先看图再写代码)
┌─────────────┐ ┌─────────────┐ │ Subject │ │ Observer │ │ (被观察者) │<>─────│ (观察者接口) │ │─────────────│ │─────────────│ │+attach() │ │+update() │ │+detach() │ └─────────────┘ │+notify() │ △ └──────┬──────┘ │ △ │ │ ┌───────┴───────┐ │ │ │ ┌──────┴──────┐ ┌────┴────┐ ┌──────┴────┐ │ConcreteSubj │ │ObserverA│ │ObserverB │ │(具体被观察者) │ │(具体观察者)│ │(具体观察者) │ └─────────────┘ └─────────┘ └───────────┘两个角色:
- Subject(被观察者):维护观察者列表,状态变化时通知所有人
- Observer(观察者):定义更新接口,收到通知后做自己该做的事
关键原则:
被观察者不关心观察者具体是谁、具体干什么,只负责"喊一嗓子"。
这就是松耦合的精髓。
四、手写实现:从 0 到 1 撸一个观察者模式
1. 定义观察者接口
/** * 观察者接口 - 所有订阅者必须实现 */publicinterfaceObserver{/** * 收到通知后的处理逻辑 * @param order 订单信息 */voidupdate(Orderorder);}2. 定义被观察者(Subject)
/** * 被观察者 - 订单事件发布者 */publicclassOrderEventPublisher{/** 观察者列表:谁订阅了,谁就在这里面 */privatefinalList<Observer>observers=newArrayList<>();/** * 订阅 */publicvoidattach(Observerobserver){observers.add(observer);}/** * 取消订阅 */publicvoiddetach(Observerobserver){observers.remove(observer);}/** * 通知所有观察者:广播模式 */publicvoidnotifyObservers(Orderorder){for(Observerobserver:observers){observer.update(order);}}}3. 具体观察者——各干各的
/** * 库存扣减观察者 */publicclassInventoryObserverimplementsObserver{@Overridepublicvoidupdate(Orderorder){System.out.println("库存扣减:商品 "+order.getProductId()+" 扣减 "+order.getQuantity());}}/** * 短信通知观察者 */publicclassSmsObserverimplementsObserver{@Overridepublicvoidupdate(Orderorder){System.out.println("短信通知:订单 "+order.getOrderId()+" 已支付,请尽快发货");}}/** * 积分变更观察者 */publicclassPointsObserverimplementsObserver{@Overridepublicvoidupdate(Orderorder){System.out.println("积分变更:用户 "+order.getUserId()+" 增加 "+order.getAmount()+" 积分");}}4. 客户端调用
publicclassOrderService{privatefinalOrderEventPublisherpublisher=newOrderEventPublisher();publicOrderService(){// 订阅:谁该干活,自己注册publisher.attach(newInventoryObserver());publisher.attach(newSmsObserver());publisher.attach(newPointsObserver());// 以后要加功能?new 一个 Observer,attach 进来就行!}/** * 订单支付成功 */publicvoidpayOrder(Orderorder){// 1. 核心业务:更新订单状态order.setStatus(OrderStatus.PAID);System.out.println("订单 "+order.getOrderId()+" 支付成功");// 2. 发布事件:一嗓子喊完,各干各的publisher.notifyObservers(order);}}看明白了吗?
OrderService只负责核心逻辑——更新订单状态。
至于库存、短信、积分?它根本不管!
以后要加优惠券?加两行代码:
publisher.attach(newCouponObserver());// 搞定!订单核心逻辑一行都不用改!
这就是观察者模式的威力——开闭原则的完美体现。
五、JDK 自带观察者:别重复造轮子
Java 其实早就内置了观察者模式的支持:
java.util.Observable(被观察者)java.util.Observer(观察者接口)
用法很简单:
// 被观察者publicclassOrderObservableextendsObservable{publicvoidorderPaid(Orderorder){setChanged();// 标记状态已改变notifyObservers(order);// 通知观察者}}// 观察者publicclassInventoryObserverimplementsObserver{@Overridepublicvoidupdate(Observableo,Objectarg){Orderorder=(Order)arg;System.out.println("库存扣减:"+order.getProductId());}}但是注意——Observable和Observer在Java 9 已被标记为 @Deprecated!
官方理由:
Observable是类不是接口,无法继承其他类setChanged()是 protected,扩展不灵活notifyObservers()是同步的,线程不安全- 整体设计老旧,不符合现代 Java 风格
官方推荐用
java.beans.PropertyChangeListener或第三方框架替代。
六、实战推荐:Spring 事件机制才是 Javaer 的首选
在企业级 Spring 项目中,观察者模式的最佳实践是 Spring Event。
1. 定义事件
/** * 订单支付事件 */publicclassOrderPaidEventextendsApplicationEvent{privatefinalOrderorder;publicOrderPaidEvent(Objectsource,Orderorder){super(source);this.order=order;}publicOrdergetOrder(){returnorder;}}2. 发布事件
@ServicepublicclassOrderService{@AutowiredprivateApplicationEventPublishereventPublisher;publicvoidpayOrder(Orderorder){// 1. 核心业务order.setStatus(OrderStatus.PAID);// 2. 发布事件:一行搞定eventPublisher.publishEvent(newOrderPaidEvent(this,order));}}3. 监听事件——各模块自己管自己
@ComponentpublicclassInventoryListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){Orderorder=event.getOrder();System.out.println("库存扣减:"+order.getProductId());}}@ComponentpublicclassSmsListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){Orderorder=event.getOrder();System.out.println("短信通知:订单 "+order.getOrderId()+" 已支付");}}优势明显:
- 完全解耦:发布者不知道谁在监听
- 零侵入:新功能加个
@EventListener就行 - Spring 原生:不用自己维护观察者列表
- 支持异步:加个
@Async就能异步处理
4. 异步监听(性能优化利器)
@ComponentpublicclassPointsListener{@Async@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){// 积分变更不急,异步处理就行Orderorder=event.getOrder();System.out.println("异步积分变更:"+order.getUserId());}}以为短信是同步阻塞的?加个
@Async秒变异步,订单接口响应速度直接翻倍。
七、观察者模式 vs 其他模式(别搞混了)
| 对比项 | 观察者模式 | 策略模式 | 发布-订阅模式 |
|---|---|---|---|
| 核心目的 | 一对多通知 | 算法替换 | 消息解耦 |
| 耦合度 | 松耦合(知道对方存在) | 松耦合 | 完全解耦(通过 Broker) |
| 通信方式 | 直接调用 | 直接调用 | 通过消息中间件 |
| 典型场景 | 事件通知 | 支付方式切换 | Kafka/RabbitMQ |
| Java 实现 | Observer/Event | Strategy 接口 | MessageQueue |
最容易混淆的:观察者模式 vs 发布-订阅模式
- 观察者:Subject 直接调用 Observer,两者有直接依赖
- 发布-订阅:通过 EventBroker 中转,发布者和订阅者互不知道
简单说:观察者是"当面喊",发布-订阅是"发群里"。
八、避坑指南(这坑我替你踩过了)
我踩过的坑
坑 1:观察者执行异常导致链路中断
publicvoidnotifyObservers(Orderorder){for(Observerobserver:observers){observer.update(order);// 如果某个 Observer 抛异常,后面的全挂!}}修复:加 try-catch,一个挂了不影响其他人
publicvoidnotifyObservers(Orderorder){for(Observerobserver:observers){try{observer.update(order);}catch(Exceptione){log.error("观察者执行异常: {}",observer.getClass().getSimpleName(),e);}}}别急着 Ctrl+C / Ctrl+V,先想清楚异常会不会传染。
坑 2:Spring @EventListener 默认同步执行
如果某个监听器很慢(比如发邮件),整个订单接口都会被阻塞。
修复:
@Async@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){// 耗时操作放这里}别忘了在启动类加@EnableAsync!
坑 3:观察者注册顺序依赖
如果库存扣减必须在积分变更之前执行——观察者模式本身不保证顺序!
修复:Spring Event 支持@Order注解
@Component@Order(1)publicclassInventoryListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){...}}@Component@Order(2)publicclassPointsListener{@EventListenerpublicvoidonOrderPaid(OrderPaidEventevent){...}}容易忽略的问题
- 内存泄漏:观察者没及时 detach,对象无法被 GC 回收(尤其是 Android 开发中常见)
- 死循环:观察者 A 更新后触发了被观察者状态变化,又被通知,又更新……无限循环
- 并发问题:多线程下观察者列表的增删和遍历可能抛
ConcurrentModificationException
线上环境注意事项
- 异步观察者一定要加异常处理,否则错误会被吞掉,日志都看不到
- 生产环境建议使用Spring Event + @Async + 线程池,别用默认线程池
- 关键业务通知考虑用消息队列(RocketMQ/Kafka)替代内存级观察者,防止服务重启丢消息
性能建议
- 观察者数量控制在合理范围(别搞 50 个监听者,那是设计有问题)
- 耗时操作务必异步化
- 高频事件注意节流和防抖
安全建议
- 敏感操作不要放观察者中无鉴权执行
- 事件对象中不要传递敏感信息的明文(如密码、密钥)
- 监听器中的异常日志脱敏处理,别把用户隐私打到日志里
九、总结
| 项目 | 说明 |
|---|---|
| 模式类型 | 行为型模式 |
| 核心思想 | 一对多依赖,状态变化自动通知 |
| 解决问题 | 对象间紧耦合、if-else 嵌套地狱 |
| 实现方式 | 手写 Observer / JDK Observable(已废弃)/ Spring Event |
| 推荐方案 | Spring Event + @Async |
| 适用场景 | 事件通知、消息广播、状态变更联动 |
一句话总结:
观察者模式的本质就是——你别来找我,我变了会通知你。
这跟现实生活中的道理一样:
与其天天盯着领导脸色(轮询),不如等领导发通知(观察者)。
技术不难,难的是没人告诉你坑在哪。
如果这篇帮到你,点个赞,顺便帮我抢救一下发际线 😄
👇 三连支持,动力源泉
如果这篇文章帮你省下了踩坑的时间,欢迎:
🔹点赞—— 让更多人看到这篇干货
🔹在看—— 你的认可是我持续输出的动力
🔹转发—— 分享给身边正在做AI Agent的朋友
你的每一个小动作,对我都很重要 ❤️
🙏 关于作者
你好,我是空门技术栈,一个常年和Bug战斗、持续填坑的Java开发者。
专注分享:
- ✅ Java / Spring Boot / Spring AI Alibaba 企业级实战
- ✅ RAG知识库、AI Agent、多智能体协作落地经验
- ✅ Docker部署、微服务架构、线上问题排查
- ✅ 偶尔聊聊「如何保住头发」这类程序员终极话题 😂
不搞水文,不贩卖焦虑,只写能跑通、能落地、能帮你少加班的实战内容。
关注我,咱们一起少踩坑,多写优雅代码。
📖 更多干货推荐
- 告别手动复制接口文档!Apifox MCP + AI 自动测试让开发效率起飞
- 别再纠结学哪个了!Java AI 三大框架深度对比:Spring AI vs AgentScope-Java
- MySQL MCP Server 从零安装到使用实战,AI 直接查询数据库
- Spring 注入三剑客:@Resource、@Autowired、@RequiredArgsConstructor 到底该用哪个?
- Spring Event 用了三年,同事一句话把我问懵了
- Java 抽象类(Abstract Class)彻底讲透:从基础到多态实战
- Spring AI Alibaba 多智能体(Multi-agent)实战:6 大协作模式 + 完整代码
- Spring AI Alibaba 智能体作为工具实战:别再让主 Agent 当"人肉路由器"了
🎁 粉丝福利
免费获取方式:
私信空门技术栈,领取源码包。
有任何问题,也欢迎后台私信,门主看到都会回复~
💬 对文章内容有疑问?想看下一篇写什么主题?
直接在评论区留言,你的每一条评论我都会看。说不定下一篇文章的主题,就来自你的问题!
🤝 项目合作 / 技术咨询
平时工作之余,也会接一些技术项目和咨询,主要方向:
⚔️企业级开发
- Java / Spring Boot 项目开发与重构
- 微服务架构设计与落地
- 系统性能调优、线上问题排查
🤖AI 应用落地(这是我最近的主力方向)
- Spring AI Alibaba / RAG / Agent 应用开发
- 企业私有知识库搭建
- AI能力接入现有业务系统
- 大模型本地化部署与调优
🛠️技术顾问 / 疑难Bug排查
- 项目架构评审与方案设计
- 线上疑难问题定位解决
- 技术选型与团队培训
如果你正遇到以下情况,欢迎找我聊聊:
- ✅ 想做AI项目,但技术方案拿不准
- ✅ 项目卡在某个Bug上很久,团队搞不定
- ✅ 想把AI接入现有业务,不知道从哪下手
- ✅ 需要靠谱的开发外包或长期技术顾问
📮联系渠道(按回复速度排序):
- 最快:私信空门技术栈
- 邮件:2929119150@qq.com(请注明来意和具体需求)
一个人踩坑,是事故;一群人踩坑,就是《避坑宝典》。
—— IT 空门,与诸君共修技术大道 😎