22. 观察者模式:发布-订阅、事件驱动、实战:Spring事件机制
观察者模式,说白了就是“你盯着我,我变了就告诉你”。
我在团队里带新人时,经常用这个模式来解耦业务逻辑。比如用户注册成功后,要发邮件、送积分、记录日志。如果把这些代码全塞在注册方法里,那维护起来就是一场噩梦。观察者模式就是解决这类问题的利器。
22.1 核心概念:发布-订阅
观察者模式定义了一种一对多的依赖关系。当一个对象(主题)的状态发生变化时,所有依赖它的对象(观察者)都会得到通知并自动更新。
你想想看,这就像你关注了一个公众号。公众号发文章,你就能收到推送。你不用天天去刷它,它也不会单独给你打电话。这就是典型的发布-订阅模型。
- Subject(主题):持有观察者列表,提供注册、移除、通知方法。
- Observer(观察者):定义一个更新接口,接收主题的通知。
- ConcreteSubject(具体主题):维护状态,状态变化时通知所有观察者。
- ConcreteObserver(具体观察者):实现更新接口,处理通知。
我个人习惯把观察者模式分成两种实现方式:
- 推模型:主题把详细信息推给观察者。适合状态变化频繁的场景。
- 拉模型:主题只通知“我变了”,观察者自己来拉数据。适合观察者只关心部分信息的场景。
我在项目中遇到过推模型滥用的情况。有一次,一个订单状态变更事件,推了整整20个字段过去。结果大部分观察者只用了订单ID。后来我改成了拉模型,只传一个ID,观察者按需查询。性能提升了不少。
22.2 事件驱动:从轮询到回调
事件驱动编程,本质上是观察者模式的一种高级应用。它把“状态变化”抽象成“事件”,把“观察者”抽象成“监听器”。
传统的轮询方式,你得每隔几秒去检查一下状态。比如:
// 轮询:每隔1秒检查一次
while (true) {
if (order.isPaid()) {
sendEmail(order);
break;
}
Thread.sleep(1000);
}
这种写法,说白了就是浪费CPU。而且代码耦合严重,你很难扩展新的处理逻辑。
事件驱动的方式就优雅多了:
// 事件驱动:支付完成后自动触发
order.onPaid(() -> sendEmail(order));
order.onPaid(() -> addScore(order));
你看,代码瞬间变得干净了。每个监听器只关心自己那点事,互不干扰。
22.3 实战:Spring事件机制
Spring框架内置了完整的事件机制,底层就是观察者模式。我几乎在每个Spring Boot项目里都会用到它。
Spring事件机制有三个核心组件:
- 事件(Event):继承
ApplicationEvent的POJO类。 - 发布者(Publisher):注入
ApplicationEventPublisher,调用publishEvent()。 - 监听器(Listener):使用
@EventListener注解的方法。
来看一个完整的例子。假设我们要实现用户注册后发送欢迎邮件和赠送积分:
// 1. 定义事件
public class UserRegisteredEvent extends ApplicationEvent {
private final Long userId;
private final String email;
public UserRegisteredEvent(Object source, Long userId, String email) {
super(source);
this.userId = userId;
this.email = email;
}
// getters...
}
// 2. 发布事件
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher publisher;
public void register(String email) {
// 注册逻辑...
Long userId = saveUser(email);
// 发布事件
publisher.publishEvent(new UserRegisteredEvent(this, userId, email));
}
}
// 3. 监听事件
@Component
public class EmailListener {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
// 发送欢迎邮件
System.out.println("发送邮件到:" + event.getEmail());
}
}
@Component
public class ScoreListener {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
// 赠送积分
System.out.println("给用户 " + event.getUserId() + " 赠送100积分");
}
}
嗯,这里要注意:@EventListener 默认是同步执行的。也就是说,发布者线程会阻塞,直到所有监听器执行完毕。如果监听器里有耗时操作,会影响主流程的性能。
@Async 注解,把监听器改成异步执行,问题才解决。
Spring还支持事件排序和条件过滤。比如:
@EventListener
@Order(1) // 控制执行顺序
public void handleFirst(UserRegisteredEvent event) {
// 先执行
}
@EventListener(condition = "#event.email.endsWith('@company.com')")
public void handleCompanyUser(UserRegisteredEvent event) {
// 只处理公司邮箱注册的事件
}
条件过滤这个功能,我在做多租户系统时用过。不同租户的事件处理逻辑不一样,用condition就能优雅地分流。
22.4 知识体系图
下面这张图,帮你理清观察者模式、事件驱动和Spring事件机制的关系:
22.5 总结与避坑
观察者模式,说白了就是“别来烦我,有事我喊你”。它让代码的耦合度大大降低,扩展性也更好。
最后分享几个我踩过的坑:
- 内存泄漏:观察者注册后忘记移除,会导致对象无法被GC。我曾在长连接场景中吃过亏,后来改用弱引用解决了。
- 异常处理:同步监听器抛异常,会中断后续监听器的执行。我建议每个监听器都自己处理异常,或者用
@Order控制关键监听器先执行。 - 性能问题:事件机制虽好,但不要滥用。如果一个事件有几十个监听器,建议考虑用消息队列(如RabbitMQ)来解耦。
记住,设计模式是工具,不是目的。用对了地方,代码就优雅;用错了,反而增加复杂度。