设计模式:观察者模式

欢迎你来读这篇博客,这篇博客主要是关于观察者模式
其中包括观察者模式的核心思想、适用场景、推模型与拉模型、同步通知与异步通知、与发布订阅模式/中介者模式/责任链模式的区别,以及 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);

刚开始看起来没问题。

但后面需求越来越多,订单服务就会越来越胖。

更麻烦的是,订单服务开始依赖一堆并不属于核心下单逻辑的模块。

这会导致:

  • 订单服务耦合太多下游模块;
  • 新增一个后续动作就要改订单服务;
  • 测试订单创建变得复杂;
  • 后续动作失败可能影响主流程;
  • 业务边界越来越模糊。

观察者模式就是为了解决这种问题:

当一个对象状态发生变化时,自动通知依赖它的其他对象,让这些对象各自处理自己的逻辑。

简单说:

观察者模式就是“我发生了变化,关心我的人自己来响应”。

在后端系统中,观察者模式经常以“事件”的形式出现:

1
OrderCreatedEvent

订单服务只负责发布事件。

至于谁关心这个事件,由监听器自己决定。

这就是观察者模式的核心价值:解耦。

正文

chapter 1:什么是观察者模式

观察者模式,英文是 Observer Pattern,属于行为型设计模式。

它的定义是:

定义对象之间的一种一对多依赖关系,使得当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。

这里有两个关键点:

  1. 一对多依赖
  2. 状态变化后自动通知

观察者模式一般包含几个角色:

  1. Subject 被观察者 / 主题:维护观察者列表,并在状态变化时通知观察者。
  2. Observer 观察者:定义接收通知的接口。
  3. ConcreteSubject 具体主题:具体的被观察对象。
  4. 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. 观察者收到通知后执行自己的逻辑。

例如:

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 后端中,更常见的是推模型:

1
OrderCreatedEvent event

把订单 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. 推送运营数据。

我们不希望订单服务直接依赖这些模块。

所以设计成:

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 抛异常,那么 MessageObserverAuditLogObserver 可能就不会执行。

这不一定是我们想要的。

所以发布器可以单独捕获每个观察者异常:

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
Subject -> Observer

发布订阅模式:

1
Publisher -> EventBus/MQ -> Subscriber

所以发布订阅可以看作观察者思想的一种更解耦、更工程化的变体。

chapter 30:观察者模式和中介者模式的区别

观察者模式和中介者模式都能减少对象直接耦合,但目的不同。

对比项 观察者模式 中介者模式
核心目的 一个对象变化后通知多个对象 协调多个对象之间复杂交互
通信方向 通常是一对多通知 多个对象通过中介者双向/多向通信
角色关系 Subject 与 Observer Mediator 与 Colleague
是否强调状态变化 不一定
示例 订单创建后通知库存、积分、消息 聊天室协调用户之间通信

观察者模式像:

1
我发生变化了,关心我的人自己响应。

中介者模式像:

1
你们不要互相找,统一找我协调。

订单创建事件适合观察者。

聊天室消息转发更适合中介者。

chapter 31:观察者模式和责任链模式的区别

对比项 观察者模式 责任链模式
核心目的 状态变化后通知多个观察者 请求沿处理链传递
处理者数量 多个观察者都可能处理 链上处理器按顺序处理
执行关系 广播式通知 链式传递
是否有顺序 通常不强调顺序 强调顺序
是否可中断 一般不强调 可以中断
示例 OrderCreatedEvent 多个监听器 下单参数校验 -> 库存校验 -> 风控校验

观察者模式像广播。

责任链模式像流水线。

如果多个模块都要响应同一个事件,用观察者。

如果请求必须按顺序经过多个处理器,用责任链。

chapter 32:观察者模式和命令模式的区别

对比项 观察者模式 命令模式
核心语义 某件事已经发生 希望系统执行某个操作
对象名称 OrderCreatedEvent CreateOrderCommand
时间方向 过去事实 未来意图
接收者数量 可以多个观察者 通常一个处理器
是否期望结果 通常不直接期望 通常期望执行结果

命令是:

1
请创建订单。

事件是:

1
订单已经创建。

这两个语义必须分清。

不要把 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.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:观察者模式
https://allendericdalexander.github.io/2026/04/01/java/design/20observer-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议