欢迎你来读这篇博客,这篇博客主要是关于观察者模式。
其中包括观察者模式的核心思想、适用场景、推模型与拉模型、同步通知与异步通知、与发布订阅模式/中介者模式/责任链模式的区别,以及 Java 后端开发中订单创建事件通知多个业务模块的完整案例。
序言
在软件开发中,我们经常会遇到这样的需求:
当一个对象发生变化时,希望其他多个对象自动收到通知并做出响应。
比如订单创建成功后,系统可能需要做很多后续动作:
- 扣减库存;
- 增加用户积分;
- 发送站内信;
- 发送短信;
- 记录审计日志;
- 推送运营数据;
- 触发风控分析;
- 通知仓储系统;
- 发布订单创建事件。
如果订单服务直接调用所有这些模块,代码可能会变成这样:
1 2 3 4 5 6 7 8
| orderService.createOrder(command);
inventoryService.lockStock(orderId); pointService.addPoint(userId); messageService.sendOrderCreatedMessage(userId); smsService.sendSms(userId); auditLogService.record(orderId); warehouseService.notify(orderId);
|
刚开始看起来没问题。
但后面需求越来越多,订单服务就会越来越胖。
更麻烦的是,订单服务开始依赖一堆并不属于核心下单逻辑的模块。
这会导致:
- 订单服务耦合太多下游模块;
- 新增一个后续动作就要改订单服务;
- 测试订单创建变得复杂;
- 后续动作失败可能影响主流程;
- 业务边界越来越模糊。
观察者模式就是为了解决这种问题:
当一个对象状态发生变化时,自动通知依赖它的其他对象,让这些对象各自处理自己的逻辑。
简单说:
观察者模式就是“我发生了变化,关心我的人自己来响应”。
在后端系统中,观察者模式经常以“事件”的形式出现:
订单服务只负责发布事件。
至于谁关心这个事件,由监听器自己决定。
这就是观察者模式的核心价值:解耦。
正文
chapter 1:什么是观察者模式
观察者模式,英文是 Observer Pattern,属于行为型设计模式。
它的定义是:
定义对象之间的一种一对多依赖关系,使得当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。
这里有两个关键点:
- 一对多依赖;
- 状态变化后自动通知。
观察者模式一般包含几个角色:
- Subject 被观察者 / 主题:维护观察者列表,并在状态变化时通知观察者。
- Observer 观察者:定义接收通知的接口。
- ConcreteSubject 具体主题:具体的被观察对象。
- ConcreteObserver 具体观察者:接收到通知后执行具体逻辑。
结构如下:
1 2 3 4
| Subject ├── Observer A ├── Observer B └── Observer C
|
当 Subject 发生变化时:
1
| Subject.notifyObservers()
|
然后多个观察者都会收到通知。
chapter 2:观察者模式解决什么问题
观察者模式主要解决的是:
一个对象变化后,需要通知多个对象,但又不希望这个对象直接依赖这些对象。
比如订单创建成功后:
1 2 3 4 5 6
| OrderService -> InventoryService -> PointService -> MessageService -> AuditLogService -> WarehouseService
|
如果订单服务直接调用这些模块,它就会知道太多。
观察者模式的做法是:
1 2 3 4 5
| OrderService 发布 OrderCreatedEvent -> InventoryObserver 监听事件 -> PointObserver 监听事件 -> MessageObserver 监听事件 -> AuditLogObserver 监听事件
|
订单服务只发布事件。
它不关心谁监听,也不关心监听器做什么。
这样就实现了解耦。
chapter 3:观察者模式的核心思想
观察者模式的核心思想可以概括为三句话:
- 被观察者维护一组观察者;
- 被观察者状态变化时通知观察者;
- 观察者收到通知后执行自己的逻辑。
例如:
1 2 3 4 5 6
| subject.addObserver(observerA); subject.addObserver(observerB);
subject.changeState();
subject.notifyObservers();
|
被观察者不需要知道观察者内部怎么处理。
观察者也不需要主动轮询被观察者。
这就是典型的“事件通知”思想。
chapter 4:生活中的观察者模式
观察者模式在生活中非常常见。
1. 公众号订阅
你关注一个公众号。
公众号发文章时,所有关注者都会收到推送。
公众号不需要知道每个用户看到文章后做什么。
2. 商品到货提醒
你订阅某商品到货通知。
商品到货后,系统通知所有订阅用户。
3. 股票价格提醒
股票价格变化后,所有订阅该股票的客户端都收到更新。
4. 事件报名通知
活动时间变更后,所有报名用户收到通知。
这些都体现了观察者模式:
被观察对象发生变化,自动通知关注它的人。
chapter 5:最简单的观察者模式实现
先写一个简单的主题接口。
1 2 3 4 5 6 7 8
| public interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message); }
|
观察者接口:
1 2 3 4
| public interface Observer {
void update(String message); }
|
具体主题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.util.ArrayList; import java.util.List;
public class NewsPublisher implements Subject {
private final List<Observer> observers = new ArrayList<>();
@Override public void addObserver(Observer observer) { observers.add(observer); }
@Override public void removeObserver(Observer observer) { observers.remove(observer); }
@Override public void notifyObservers(String message) { for (Observer observer : observers) { observer.update(message); } }
public void publish(String news) { System.out.println("发布新闻:" + news);
notifyObservers(news); } }
|
具体观察者:
1 2 3 4 5 6 7
| public class EmailSubscriber implements Observer {
@Override public void update(String message) { System.out.println("邮件订阅者收到消息:" + message); } }
|
1 2 3 4 5 6 7
| public class SmsSubscriber implements Observer {
@Override public void update(String message) { System.out.println("短信订阅者收到消息:" + message); } }
|
客户端:
1 2 3 4 5 6 7 8 9 10 11
| public class ObserverDemo {
public static void main(String[] args) { NewsPublisher publisher = new NewsPublisher();
publisher.addObserver(new EmailSubscriber()); publisher.addObserver(new SmsSubscriber());
publisher.publish("观察者模式开更了"); } }
|
输出类似:
1 2 3
| 发布新闻:观察者模式开更了 邮件订阅者收到消息:观察者模式开更了 短信订阅者收到消息:观察者模式开更了
|
这就是最基本的观察者模式。
chapter 6:观察者模式的推模型和拉模型
观察者模式中有两种常见通知方式:
1. 推模型
被观察者把数据直接推给观察者。
例如:
1
| observer.update(message);
|
观察者收到的就是完整消息。
优点:
- 使用简单;
- 观察者不用再查询数据;
- 适合事件数据比较明确的场景。
缺点:
- 推送的数据可能过多;
- 被观察者需要知道观察者需要什么数据;
- 事件对象设计不好会变臃肿。
2. 拉模型
被观察者只通知“发生变化了”,观察者自己再去拉取数据。
例如:
1
| observer.update(subject);
|
观察者可以从 subject 中获取自己需要的数据。
优点:
缺点:
- 观察者需要知道被观察者接口;
- 可能增加查询次数;
- 耦合度可能更高。
3. 后端项目中怎么选
在 Java 后端中,更常见的是推模型:
把订单 ID、用户 ID、金额等关键信息放到事件里。
监听器收到事件后,如果需要更多数据,再通过 Repository 或 Service 查询。
所以实际经常是:
事件推送关键数据,监听器按需拉取详细数据。
这是一种折中方式。
chapter 7:观察者模式的同步通知和异步通知
观察者模式还要考虑通知方式:
1. 同步通知
发布者调用观察者时,当前线程会等待所有观察者执行完成。
1 2 3
| for (Observer observer : observers) { observer.update(event); }
|
优点:
缺点:
- 一个观察者慢,会拖慢主流程;
- 一个观察者异常,可能影响其他观察者;
- 不适合耗时操作。
2. 异步通知
发布者把事件提交给线程池或消息队列,观察者异步执行。
1
| executor.submit(() -> observer.update(event));
|
优点:
- 主流程响应快;
- 观察者之间影响小;
- 适合耗时任务。
缺点:
- 一致性更复杂;
- 异常处理更麻烦;
- 需要考虑重试、幂等、顺序、事务。
3. 实践建议
如果观察者逻辑很轻,比如更新内存状态,可以同步。
如果观察者逻辑重,比如发短信、调用外部系统、生成报表,建议异步。
在后端系统中,事件监听通常要非常谨慎。
一个监听器失败,不应该轻易把主流程干崩。
chapter 8:案例背景:订单创建事件通知
下面用一个 Java 后端常见场景来讲观察者模式。
需求如下:
用户创建订单成功后,需要触发多个后续动作:
- 锁定库存;
- 增加积分;
- 发送站内信;
- 记录审计日志;
- 推送运营数据。
我们不希望订单服务直接依赖这些模块。
所以设计成:
1 2 3 4 5 6 7
| OrderService 创建订单 -> 发布 OrderCreatedEvent -> InventoryObserver 处理库存 -> PointObserver 处理积分 -> MessageObserver 处理消息 -> AuditLogObserver 处理审计 -> OperationDataObserver 处理运营数据
|
订单服务只负责创建订单和发布事件。
后续动作由观察者完成。
chapter 9:定义订单创建事件 OrderCreatedEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import java.math.BigDecimal; import java.time.LocalDateTime;
public class OrderCreatedEvent {
private final Long orderId;
private final Long userId;
private final Long productId;
private final Integer quantity;
private final BigDecimal amount;
private final LocalDateTime createdAt;
public OrderCreatedEvent(Long orderId, Long userId, Long productId, Integer quantity, BigDecimal amount) { this.orderId = orderId; this.userId = userId; this.productId = productId; this.quantity = quantity; this.amount = amount; this.createdAt = LocalDateTime.now(); }
public Long getOrderId() { return orderId; }
public Long getUserId() { return userId; }
public Long getProductId() { return productId; }
public Integer getQuantity() { return quantity; }
public BigDecimal getAmount() { return amount; }
public LocalDateTime getCreatedAt() { return createdAt; } }
|
这个事件表示:
订单已经创建成功。
注意语义。
它不是“请创建订单”,而是“订单已创建”。
这点非常重要。
事件表达的是已经发生的事实。
chapter 10:定义订单事件观察者接口
1 2 3 4
| public interface OrderCreatedObserver {
void onOrderCreated(OrderCreatedEvent event); }
|
所有关心订单创建事件的模块都实现这个接口。
chapter 11:库存观察者
1 2 3 4 5 6 7 8 9 10
| public class InventoryObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("库存模块收到订单创建事件,锁定库存,productId = " + event.getProductId() + ",quantity = " + event.getQuantity()); } }
|
库存模块只关心库存逻辑。
它不需要订单服务主动调用它。
chapter 12:积分观察者
1 2 3 4 5 6 7 8 9 10
| public class PointObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("积分模块收到订单创建事件,增加积分,userId = " + event.getUserId() + ",amount = " + event.getAmount()); } }
|
chapter 13:消息观察者
1 2 3 4 5 6 7 8 9 10
| public class MessageObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("消息模块收到订单创建事件,发送站内信,userId = " + event.getUserId() + ",orderId = " + event.getOrderId()); } }
|
chapter 14:审计日志观察者
1 2 3 4 5 6 7 8 9 10
| public class AuditLogObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("审计模块收到订单创建事件,记录操作日志,orderId = " + event.getOrderId() + ",createdAt = " + event.getCreatedAt()); } }
|
chapter 15:定义事件发布器 OrderEventPublisher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.util.ArrayList; import java.util.List;
public class OrderEventPublisher {
private final List<OrderCreatedObserver> observers = new ArrayList<>();
public void addObserver(OrderCreatedObserver observer) { observers.add(observer); }
public void removeObserver(OrderCreatedObserver observer) { observers.remove(observer); }
public void publish(OrderCreatedEvent event) { for (OrderCreatedObserver observer : observers) { observer.onOrderCreated(event); } } }
|
发布器维护观察者列表。
订单创建成功后,发布事件。
chapter 16:订单服务发布事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicLong;
public class OrderService {
private final AtomicLong idGenerator = new AtomicLong(10000);
private final OrderEventPublisher eventPublisher;
public OrderService(OrderEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; }
public Long createOrder(Long userId, Long productId, Integer quantity, BigDecimal amount) { Long orderId = idGenerator.incrementAndGet();
System.out.println("创建订单成功,orderId = " + orderId);
OrderCreatedEvent event = new OrderCreatedEvent( orderId, userId, productId, quantity, amount );
eventPublisher.publish(event);
return orderId; } }
|
订单服务只知道事件发布器。
它不直接依赖库存、积分、消息、审计等模块。
chapter 17:客户端使用观察者模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.math.BigDecimal;
public class OrderObserverDemo {
public static void main(String[] args) { OrderEventPublisher publisher = new OrderEventPublisher();
publisher.addObserver(new InventoryObserver()); publisher.addObserver(new PointObserver()); publisher.addObserver(new MessageObserver()); publisher.addObserver(new AuditLogObserver());
OrderService orderService = new OrderService(publisher);
orderService.createOrder( 1L, 100L, 2, new BigDecimal("99.00") ); } }
|
输出类似:
1 2 3 4 5
| 创建订单成功,orderId = 10001 库存模块收到订单创建事件,锁定库存,productId = 100,quantity = 2 积分模块收到订单创建事件,增加积分,userId = 1,amount = 99.00 消息模块收到订单创建事件,发送站内信,userId = 1,orderId = 10001 审计模块收到订单创建事件,记录操作日志,orderId = 10001
|
这就是观察者模式的基本后端用法。
chapter 18:观察者异常会带来什么问题
同步观察者有一个很现实的问题:
如果某个观察者抛异常,后面的观察者可能无法执行。
例如:
1 2 3 4 5
| public void publish(OrderCreatedEvent event) { for (OrderCreatedObserver observer : observers) { observer.onOrderCreated(event); } }
|
如果 PointObserver 抛异常,那么 MessageObserver 和 AuditLogObserver 可能就不会执行。
这不一定是我们想要的。
所以发布器可以单独捕获每个观察者异常:
1 2 3 4 5 6 7 8 9 10 11 12
| public void publish(OrderCreatedEvent event) { for (OrderCreatedObserver observer : observers) { try { observer.onOrderCreated(event); } catch (Exception e) { System.out.println("观察者执行失败,observer = " + observer.getClass().getSimpleName() + ",error = " + e.getMessage()); } } }
|
这样一个观察者失败,不会影响其他观察者。
但要注意:
吞掉异常也不是永远正确。
如果库存锁定失败,可能必须影响订单创建。
如果发送站内信失败,可能不应该影响订单创建。
不同观察者的失败语义不一样。
这就涉及业务一致性设计。
chapter 19:同步观察者的一致性问题
订单创建后通知库存锁定,这个动作可能非常重要。
如果库存锁定失败,订单可能不应该创建成功。
这时同步观察者可能合适,或者库存锁定应该直接放入主流程。
但如果是发送短信、记录运营数据,这些动作失败不应该影响下单主流程。
这时更适合异步观察者。
所以要区分:
1. 强一致后续动作
例如:
这类动作可能更适合放在主流程,或者使用事务/分布式事务/最终一致性方案。
2. 弱一致后续动作
例如:
- 发通知;
- 加积分;
- 记录审计;
- 推送运营数据;
- 发消息。
这类动作更适合事件监听异步处理。
观察者模式能解耦,但不能自动解决一致性。
别把所有后续动作都丢给事件,然后说“架构解耦了”。库存没扣,订单成了,那不叫解耦,那叫事故预约。
chapter 20:异步观察者实现
可以用线程池做一个简单异步事件发布器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class AsyncOrderEventPublisher {
private final List<OrderCreatedObserver> observers = new ArrayList<>();
private final ExecutorService executorService = Executors.newFixedThreadPool(4);
public void addObserver(OrderCreatedObserver observer) { observers.add(observer); }
public void publish(OrderCreatedEvent event) { for (OrderCreatedObserver observer : observers) { executorService.submit(() -> { try { observer.onOrderCreated(event); } catch (Exception e) { System.out.println("异步观察者执行失败,observer = " + observer.getClass().getSimpleName() + ",error = " + e.getMessage()); } }); } }
public void shutdown() { executorService.shutdown(); } }
|
这样订单服务发布事件后,不必等待所有观察者执行完。
但是异步执行要考虑:
- 线程池容量;
- 拒绝策略;
- 异常处理;
- TraceId 传递;
- MDC 传递;
- 事务提交时机;
- 事件丢失风险;
- 失败重试;
- 幂等处理。
如果业务对可靠性要求高,不建议只用本地线程池。
可以考虑 MQ 或事务消息。
chapter 21:观察者模式和 MQ
在分布式系统中,观察者模式经常通过 MQ 落地。
本地观察者模式:
1
| OrderService -> publish event -> local observers
|
MQ 事件模式:
1 2 3 4 5
| OrderService -> send OrderCreatedEvent to MQ -> InventoryConsumer -> PointConsumer -> MessageConsumer -> AuditLogConsumer
|
这样可以跨服务解耦。
好处:
- 服务之间不直接依赖;
- 事件可以异步消费;
- 消费失败可以重试;
- 可以削峰填谷;
- 新增消费者不影响生产者。
但也引入新问题:
- 消息可靠性;
- 重复消费;
- 顺序消息;
- 消息积压;
- 消费幂等;
- 事务一致性;
- 死信队列;
- 事件版本兼容。
观察者模式是思想。
MQ 是一种分布式工程实现。
chapter 22:Spring 事件机制
Spring 本身提供了事件发布和监听机制。
核心接口包括:
ApplicationEventPublisher
ApplicationListener
@EventListener
我们可以用 Spring 事件实现观察者模式。
chapter 23:Spring 中定义订单创建事件
可以使用普通对象作为事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import java.math.BigDecimal; import java.time.LocalDateTime;
public class OrderCreatedEvent {
private final Long orderId;
private final Long userId;
private final Long productId;
private final Integer quantity;
private final BigDecimal amount;
private final LocalDateTime createdAt;
public OrderCreatedEvent(Long orderId, Long userId, Long productId, Integer quantity, BigDecimal amount) { this.orderId = orderId; this.userId = userId; this.productId = productId; this.quantity = quantity; this.amount = amount; this.createdAt = LocalDateTime.now(); }
public Long getOrderId() { return orderId; }
public Long getUserId() { return userId; }
public Long getProductId() { return productId; }
public Integer getQuantity() { return quantity; }
public BigDecimal getAmount() { return amount; }
public LocalDateTime getCreatedAt() { return createdAt; } }
|
chapter 24:Spring 中发布事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service;
import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicLong;
@Service public class OrderApplicationService {
private final AtomicLong idGenerator = new AtomicLong(10000);
private final ApplicationEventPublisher eventPublisher;
public OrderApplicationService(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; }
public Long createOrder(Long userId, Long productId, Integer quantity, BigDecimal amount) { Long orderId = idGenerator.incrementAndGet();
System.out.println("创建订单成功,orderId = " + orderId);
OrderCreatedEvent event = new OrderCreatedEvent( orderId, userId, productId, quantity, amount );
eventPublisher.publishEvent(event);
return orderId; } }
|
订单服务只发布事件。
不直接调用监听器。
chapter 25:Spring 中监听事件
库存监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component;
@Component public class InventoryOrderCreatedListener {
@EventListener public void onOrderCreated(OrderCreatedEvent event) { System.out.println("[Spring Event] 库存监听器处理订单创建事件,productId = " + event.getProductId() + ",quantity = " + event.getQuantity()); } }
|
积分监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component;
@Component public class PointOrderCreatedListener {
@EventListener public void onOrderCreated(OrderCreatedEvent event) { System.out.println("[Spring Event] 积分监听器处理订单创建事件,userId = " + event.getUserId() + ",amount = " + event.getAmount()); } }
|
消息监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component;
@Component public class MessageOrderCreatedListener {
@EventListener public void onOrderCreated(OrderCreatedEvent event) { System.out.println("[Spring Event] 消息监听器发送通知,userId = " + event.getUserId() + ",orderId = " + event.getOrderId()); } }
|
chapter 26:Spring 事件默认是同步的
很多人以为 Spring 的 @EventListener 默认就是异步。
其实默认是同步执行。
也就是说:
1
| eventPublisher.publishEvent(event);
|
会在当前线程中执行监听器。
如果某个监听器很慢,主流程也会慢。
如果某个监听器抛异常,也可能影响发布事件的方法。
所以要根据业务选择同步还是异步。
chapter 27:Spring 异步事件监听
如果要异步执行监听器,可以使用 @Async。
先开启异步:
1 2 3 4 5 6 7
| import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync;
@Configuration @EnableAsync public class AsyncConfig { }
|
监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component;
@Component public class AsyncMessageOrderCreatedListener {
@Async @EventListener public void onOrderCreated(OrderCreatedEvent event) { System.out.println("[Async Spring Event] 异步发送通知,orderId = " + event.getOrderId()); } }
|
这样监听器会异步执行。
不过异步监听要注意:
- 异常不会直接抛回主线程;
- 需要配置线程池;
- 要考虑 MDC/TraceId 传递;
- 要考虑事务提交时机;
- 监听器不能依赖当前事务中的未提交数据。
chapter 28:事务事件监听 @TransactionalEventListener
如果订单创建方法有事务:
1 2 3 4 5 6
| @Transactional public Long createOrder(...) { eventPublisher.publishEvent(event); return orderId; }
|
默认 @EventListener 会在事务提交前执行。
如果监听器查询数据库,可能读到不稳定状态。
Spring 提供了 @TransactionalEventListener。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.event.TransactionPhase;
@Component public class OrderCreatedAfterCommitListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onOrderCreated(OrderCreatedEvent event) { System.out.println("[After Commit] 事务提交后处理订单创建事件,orderId = " + event.getOrderId()); } }
|
这表示:
当前事务提交成功之后,再执行监听器。
常见阶段包括:
BEFORE_COMMIT
AFTER_COMMIT
AFTER_ROLLBACK
AFTER_COMPLETION
在真实业务里,订单创建后发通知、发 MQ,经常希望在事务提交后执行。
否则可能出现:
订单事务回滚了,但消息已经发出去了。
这就很尴尬。
订单没了,短信说“您下单成功”。用户一脸问号,客服一脸痛苦。
chapter 29:观察者模式和发布订阅模式的区别
观察者模式和发布订阅模式非常像。
很多时候会混用。
但严格来说,它们有一些区别。
| 对比项 |
观察者模式 |
发布订阅模式 |
| 通信关系 |
Subject 直接通知 Observer |
Publisher 通过 Broker/EventBus 通知 Subscriber |
| 是否有中间层 |
通常没有 |
通常有 |
| 耦合程度 |
Subject 知道观察者列表 |
Publisher 不知道订阅者 |
| 典型实现 |
Java Observer、Spring Event 本地事件 |
MQ、EventBus、Kafka |
| 通信方式 |
多为进程内 |
可进程内,也可分布式 |
观察者模式:
发布订阅模式:
1
| Publisher -> EventBus/MQ -> Subscriber
|
所以发布订阅可以看作观察者思想的一种更解耦、更工程化的变体。
chapter 30:观察者模式和中介者模式的区别
观察者模式和中介者模式都能减少对象直接耦合,但目的不同。
| 对比项 |
观察者模式 |
中介者模式 |
| 核心目的 |
一个对象变化后通知多个对象 |
协调多个对象之间复杂交互 |
| 通信方向 |
通常是一对多通知 |
多个对象通过中介者双向/多向通信 |
| 角色关系 |
Subject 与 Observer |
Mediator 与 Colleague |
| 是否强调状态变化 |
是 |
不一定 |
| 示例 |
订单创建后通知库存、积分、消息 |
聊天室协调用户之间通信 |
观察者模式像:
中介者模式像:
订单创建事件适合观察者。
聊天室消息转发更适合中介者。
chapter 31:观察者模式和责任链模式的区别
| 对比项 |
观察者模式 |
责任链模式 |
| 核心目的 |
状态变化后通知多个观察者 |
请求沿处理链传递 |
| 处理者数量 |
多个观察者都可能处理 |
链上处理器按顺序处理 |
| 执行关系 |
广播式通知 |
链式传递 |
| 是否有顺序 |
通常不强调顺序 |
强调顺序 |
| 是否可中断 |
一般不强调 |
可以中断 |
| 示例 |
OrderCreatedEvent 多个监听器 |
下单参数校验 -> 库存校验 -> 风控校验 |
观察者模式像广播。
责任链模式像流水线。
如果多个模块都要响应同一个事件,用观察者。
如果请求必须按顺序经过多个处理器,用责任链。
chapter 32:观察者模式和命令模式的区别
| 对比项 |
观察者模式 |
命令模式 |
| 核心语义 |
某件事已经发生 |
希望系统执行某个操作 |
| 对象名称 |
OrderCreatedEvent |
CreateOrderCommand |
| 时间方向 |
过去事实 |
未来意图 |
| 接收者数量 |
可以多个观察者 |
通常一个处理器 |
| 是否期望结果 |
通常不直接期望 |
通常期望执行结果 |
命令是:
事件是:
这两个语义必须分清。
不要把 CreateOrderCommand 当事件发出去,也不要用 OrderCreatedEvent 去驱动“创建订单”。
命令是意图。
事件是事实。
chapter 33:观察者模式的优点
1. 解耦发布者和观察者
发布者不需要知道具体有哪些观察者。
2. 支持一对多通知
一个事件可以触发多个后续动作。
3. 易于扩展
新增一个观察者,不需要修改发布者。
4. 符合开闭原则
对新增监听器开放,对已有发布逻辑尽量关闭。
5. 适合事件驱动
非常适合订单创建、用户注册、支付成功等业务事件。
6. 可以异步化
观察者逻辑可以通过线程池或 MQ 异步处理。
chapter 34:观察者模式的缺点
1. 调用链不直观
发布者不知道有哪些观察者,排查问题时需要找监听器。
2. 观察者执行顺序不一定可靠
如果多个观察者有顺序依赖,观察者模式不一定合适。
3. 同步通知可能互相影响
一个观察者异常可能影响其他观察者或主流程。
4. 异步通知增加复杂度
需要考虑幂等、重试、事务、消息丢失等问题。
5. 过度使用会导致业务流程分散
如果所有逻辑都靠事件串起来,系统会变得难以理解。
事件不是胶水,别把所有业务都粘成一坨。
chapter 35:适用场景
观察者模式适合以下场景。
1. 一个对象变化后,需要通知多个对象
例如:
- 订单创建;
- 支付成功;
- 用户注册;
- 配置变更;
- 商品上架;
- 库存变化。
2. 发布者不希望依赖观察者
发布者只发布事件,不直接调用下游模块。
3. 新增后续动作频繁
比如订单创建后,后续新增积分、消息、审计、数据分析等。
4. 事件驱动架构
领域事件、本地事件、MQ 事件都可以体现观察者思想。
5. 状态变化需要广播
例如配置中心配置变更后,通知多个服务刷新。
chapter 36:不适合使用的场景
以下场景不建议使用观察者模式。
1. 后续动作有严格顺序
如果必须 A 执行完再执行 B,再执行 C,责任链或流程编排更合适。
2. 后续动作强一致
如果后续动作失败必须导致主流程失败,要谨慎使用异步观察者。
3. 业务流程本身需要清晰可见
如果核心业务流程全靠事件监听串起来,读代码会很痛苦。
4. 观察者数量过多且缺少治理
几十个监听器监听同一个事件,如果没有规范,很难维护。
5. 只是简单一对一调用
如果只有一个明确调用方和被调用方,直接调用更清晰。
chapter 37:真实项目中的实践建议
1. 事件命名要表达事实
推荐:
1 2 3 4
| OrderCreatedEvent PaymentSucceededEvent UserRegisteredEvent ConfigChangedEvent
|
不推荐:
1 2 3
| CreateOrderEvent DoPaymentEvent HandleUserEvent
|
事件应该表达“已经发生的事实”。
2. 事件内容不要太胖
事件里放关键数据即可。
例如:
- orderId;
- userId;
- amount;
- occurredAt。
如果监听器需要更多数据,可以按需查询。
3. 区分同步监听和异步监听
强一致、轻量逻辑可以同步。
耗时、弱一致逻辑建议异步。
4. 异步监听必须考虑幂等
同一个事件可能被重复处理。
监听器要支持幂等。
例如:
1
| orderId + eventType 唯一约束
|
5. 事务事件建议使用 AFTER_COMMIT
订单保存成功后再发布外部通知。
使用:
1
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
6. 监听器异常要有处理策略
不要随便吞异常,也不要让不重要监听器拖垮主流程。
要区分业务重要性。
7. 避免监听器之间互相依赖顺序
观察者模式不适合强顺序依赖。
如果顺序很重要,考虑责任链或流程编排。
8. 事件要有版本意识
事件结构可能变化。
尤其是 MQ 事件,要考虑兼容性。
9. 本地事件和 MQ 事件不要混为一谈
Spring 本地事件只在当前 JVM 内生效。
MQ 事件可以跨服务。
它们不是一个层级的东西。
10. 不要滥用事件
不是所有方法调用都要改成事件。
事件适合“某个事实发生后,多个模块各自响应”。
chapter 38:完整案例代码汇总
订单创建事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import java.math.BigDecimal; import java.time.LocalDateTime;
public class OrderCreatedEvent {
private final Long orderId;
private final Long userId;
private final Long productId;
private final Integer quantity;
private final BigDecimal amount;
private final LocalDateTime createdAt;
public OrderCreatedEvent(Long orderId, Long userId, Long productId, Integer quantity, BigDecimal amount) { this.orderId = orderId; this.userId = userId; this.productId = productId; this.quantity = quantity; this.amount = amount; this.createdAt = LocalDateTime.now(); }
public Long getOrderId() { return orderId; }
public Long getUserId() { return userId; }
public Long getProductId() { return productId; }
public Integer getQuantity() { return quantity; }
public BigDecimal getAmount() { return amount; }
public LocalDateTime getCreatedAt() { return createdAt; } }
|
观察者接口
1 2 3 4
| public interface OrderCreatedObserver {
void onOrderCreated(OrderCreatedEvent event); }
|
库存观察者
1 2 3 4 5 6 7 8 9 10
| public class InventoryObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("库存模块收到订单创建事件,锁定库存,productId = " + event.getProductId() + ",quantity = " + event.getQuantity()); } }
|
积分观察者
1 2 3 4 5 6 7 8 9 10
| public class PointObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("积分模块收到订单创建事件,增加积分,userId = " + event.getUserId() + ",amount = " + event.getAmount()); } }
|
消息观察者
1 2 3 4 5 6 7 8 9 10
| public class MessageObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("消息模块收到订单创建事件,发送站内信,userId = " + event.getUserId() + ",orderId = " + event.getOrderId()); } }
|
审计观察者
1 2 3 4 5 6 7 8 9 10
| public class AuditLogObserver implements OrderCreatedObserver {
@Override public void onOrderCreated(OrderCreatedEvent event) { System.out.println("审计模块收到订单创建事件,记录操作日志,orderId = " + event.getOrderId() + ",createdAt = " + event.getCreatedAt()); } }
|
事件发布器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.util.ArrayList; import java.util.List;
public class OrderEventPublisher {
private final List<OrderCreatedObserver> observers = new ArrayList<>();
public void addObserver(OrderCreatedObserver observer) { observers.add(observer); }
public void removeObserver(OrderCreatedObserver observer) { observers.remove(observer); }
public void publish(OrderCreatedEvent event) { for (OrderCreatedObserver observer : observers) { try { observer.onOrderCreated(event); } catch (Exception e) { System.out.println("观察者执行失败,observer = " + observer.getClass().getSimpleName() + ",error = " + e.getMessage()); } } } }
|
订单服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicLong;
public class OrderService {
private final AtomicLong idGenerator = new AtomicLong(10000);
private final OrderEventPublisher eventPublisher;
public OrderService(OrderEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; }
public Long createOrder(Long userId, Long productId, Integer quantity, BigDecimal amount) { Long orderId = idGenerator.incrementAndGet();
System.out.println("创建订单成功,orderId = " + orderId);
OrderCreatedEvent event = new OrderCreatedEvent( orderId, userId, productId, quantity, amount );
eventPublisher.publish(event);
return orderId; } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.math.BigDecimal;
public class OrderObserverDemo {
public static void main(String[] args) { OrderEventPublisher publisher = new OrderEventPublisher();
publisher.addObserver(new InventoryObserver()); publisher.addObserver(new PointObserver()); publisher.addObserver(new MessageObserver()); publisher.addObserver(new AuditLogObserver());
OrderService orderService = new OrderService(publisher);
orderService.createOrder( 1L, 100L, 2, new BigDecimal("99.00") ); } }
|
chapter 39:一句话总结
观察者模式的本质是:
当一个对象状态发生变化时,自动通知依赖它的多个对象,让它们各自做出响应。
它特别适合:
- 订单创建后通知多个模块;
- 支付成功后触发后续动作;
- 用户注册后发送欢迎消息;
- 配置变更后刷新缓存;
- 商品状态变化后通知订阅者;
- 领域事件;
- Spring 本地事件;
- MQ 发布订阅。
观察者模式最关键的不是“写一个 Observer 接口”,而是理解:
谁是事件发布者,谁是事件监听者,事件表达的是事实还是命令。
好的观察者模式像广播通知:事实发生后,关心的人各自响应。
坏的观察者模式像地下暗号:一个事件发出去,十几个监听器偷偷改系统状态,最后没人知道业务到底怎么跑的。
所以使用观察者模式时要记住一句话:
事件用来解耦,不是用来隐藏业务流程。
参考资料
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
- Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
- Martin Fowler. Patterns of Enterprise Application Architecture.
- Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software.
- Spring Framework Documentation: Application Events and Listeners.
- Spring Framework Documentation: Transaction-bound Events.
- Refactoring Guru: Observer Pattern.
- SourceMaking: Observer Design Pattern.
启示录
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。