观察者模式-实现
在 Spring 中,这个基于观察者模式设计的机制,通常被称为 Spring 事件驱动机制或 ApplicationEvent 机制。它本质上是观察者模式在 Spring 框架内的一种优雅实现,用于实现应用内部各组件间的松耦合通信。
核心三要素
这套机制围绕三个核心角色展开:
- 事件(Event):信息的载体,相当于观察者模式中的被观察者。它封装了业务数据,比如一次订单支付、一个用户注册动作,都需要对应的事件类。
- 事件发布者(Publisher):事件的触发者。它不需要知道谁会处理事件,只需将事件发布到Spring容器中。角色上类似于观察者模式中的主题(Subject)。
- 事件监听者(Listener):事件的响应者。它监听并处理感兴趣的事件,对应于观察者模式中的观察者(Observer)。每个监听器只关心自己订阅的事件类型,当事件发生时执行相应的业务逻辑。
如何使用(分步详解)
通过一个“用户注册”的经典场景来梳理具体的使用步骤
第一步:定义事件(事件类)
首先,创建一个类来表示“用户注册成功”这件事。推荐直接继承 ApplicationEvent类,也可以使用普通POJO,但继承是更标准的方式。
// 1. 自定义事件,继承 ApplicationEvent
public class UserRegisteredEvent extends ApplicationEvent {
private final String userEmail;
// 构造方法,source是事件源,通常传this即可
public UserRegisteredEvent(Object source, String userEmail) {
super(source);
this.userEmail = userEmail;
}
// 提供getter方法,让监听器能获取数据
public String getUserEmail() {
return userEmail;
}
}
第二步:发布事件(发布者)
在核心业务逻辑里注入 ApplicationEventPublisher,然后在合适的时机(比如用户数据保存成功后)调用 publishEvent()方法把事件发出去。
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher; // 注入事件发布器
public void registerUser(String email) {
// 1. 核心业务逻辑:保存用户信息到数据库...
System.out.println("用户 " + email + " 注册成功!");
// 2. 发布事件:把“注册成功”这件事广播出去
UserRegisteredEvent event = new UserRegisteredEvent(this, email);
eventPublisher.publishEvent(event);
// 发布后,所有监听这个事件的监听器就会自动执行
}
}
第三步:监听事件并处理(监听者)
这是最灵活的环节,Spring 提供了两种常用的监听方式。
方式一:使用 @EventListener 注解(最推荐)
这是最简洁、最现代的方式,无需实现任何接口,在方法上标注注解即可。
@Component
public class NotificationService {
// 方法参数即为要监听的事件类型
@EventListener
public void handleUserRegistration(UserRegisteredEvent event) {
// 处理逻辑:比如发送欢迎邮件
String email = event.getUserEmail();
System.out.println("📧 正在给 " + email + " 发送欢迎邮件...");
// 实际项目里这里会调用邮件服务
}
}
方式二:实现 ApplicationListener 接口
这是一种更传统的方式,可以实现接口,并重写 onApplicationEvent方法。
@Component
public class CouponListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
// 处理逻辑:比如赠送新人优惠券
String email = event.getUserEmail();
System.out.println("🎁 给 " + email + " 赠送一张新人优惠券!");
}
}
深入一点:高级特性
- 异步处理(
@Async):默认事件是同步处理的,发布者线程会等待所有监听器执行完毕。如果某个监听器逻辑耗时(如发送邮件),可以结合@Async注解实现异步处理,避免阻塞主流程。使用时需要在配置类上添加@EnableAsync开启支持。 - 条件过滤(
condition):可以给@EventListener添加condition属性,通过SpEL表达式控制监听器在满足特定条件时才执行。例如,只处理特定域名的用户注册事件。
总结:为什么用它?
- 解耦:发布者(
UserService)和监听者(NotificationService、CouponListener)完全不知道对方的存在,业务逻辑清晰,修改一方不会影响另一方。 - 扩展性强:未来想增加一个新功能,比如“用户注册后记录日志”,只需新增一个监听器,无需改动
UserService的代码,符合开闭原则。 - 灵活性高:支持同步、异步、带条件监听等多种处理方式,能应对复杂业务场景。
可以说,这套事件机制是构建高内聚、低耦合Spring应用的一把利器。
注意点
1.@TransactionalEventListener
// @Transactional + @EventListener:无法控制时机
@Component
class Listener1 {
@Transactional
@EventListener
public void handle(OrderPaidEvent event) {
// ⚠️ 事件发布后立即执行
// ⚠️ 无法延迟到事务提交后
}
}
// @TransactionalEventListener:精确控制时机
@Component
class Listener2 {
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(OrderPaidEvent event) {
// ✅ 事务提交前执行(可以做最后验证)
// ⚠️ 抛异常会让事务回滚
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderPaidEvent event) {
// ✅ 事务提交后执行(最常用)
// ✅ 可以安全发送消息、邮件
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollback(OrderPaidEvent event) {
// ✅ 事务回滚后执行(记录失败日志)
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletion(OrderPaidEvent event) {
// ✅ 事务完成后执行(无论成功还是失败)
}
}
2.发布-订阅模式(Pub-Sub)
- 观察者模式:被观察者(Subject) 直接持有 观察者(Observer) 的引用,并直接调用其方法。它们是直接通信的。(服务内)
- 发布-订阅模式:发布者(Publisher) 和 订阅者(Subscriber) 互不知道对方,它们通过一个 消息代理(Broker,即中间人) 来通信。发布者把消息发给Broker,Broker再转发给订阅者。(不同服务间)
都是为了解耦
不能用的场景
使用场景
-
操作必须和主流程强一致(必须在同一个事务中)
-
操作极其简单
-
需要返回值
-
用户注册/登录后的系列操作
@Service public class UserService { @Transactional public User register(UserRegisterDTO dto) { // 1. 核心逻辑:保存用户 User user = userRepository.save(new User(dto)); // 2. 发布事件(触发各种副作用) eventPublisher.publishEvent(new UserRegisteredEvent(this, user)); return user; } } // 多个监听器各司其职 @Component public class UserEventListeners { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void sendWelcomeEmail(UserRegisteredEvent event) { // 发送欢迎邮件(耗时操作) emailService.send(event.getUser().getEmail(), "欢迎注册!"); } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void initDefaultData(UserRegisteredEvent event) { // 初始化用户默认配置 configService.initDefaultConfig(event.getUser().getId()); } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void sendCoupon(UserRegisteredEvent event) { // 赠送新人优惠券 couponService.giveNewUserCoupon(event.getUser().getId()); } @EventListener public void updateCache(UserRegisteredEvent event) { // 更新缓存(非关键,失败不影响) cacheService.put("user:" + event.getUser().getId(), event.getUser()); } } -
日志记录和审计
@Service public class ProductService { @Transactional public void updatePrice(Long productId, BigDecimal price) { // 核心逻辑:更新价格 productRepository.updatePrice(productId, price); // 发布事件(日志记录) eventPublisher.publishEvent(new ProductPriceChangedEvent(this, productId, price)); } } @Component public class AuditListener { @EventListener public void logPriceChange(ProductPriceChangedEvent event) { // 记录审计日志(不依赖事务,失败不影响主流程) auditLogService.save(AuditLog.builder() .operation("修改价格") .productId(event.getProductId()) .newPrice(event.getNewPrice()) .operator(SecurityContextHolder.getContext().getAuthentication().getName()) .build()); } } -
缓存更新/清除
@Service public class ArticleService { @Transactional public void updateArticle(Long id, String content) { // 核心逻辑:更新文章 articleRepository.updateContent(id, content); // 发布事件 eventPublisher.publishEvent(new ArticleUpdatedEvent(this, id)); } } @Component public class CacheListener { @EventListener public void clearArticleCache(ArticleUpdatedEvent event) { // 清除缓存(失败不影响主流程) redisTemplate.delete("article:" + event.getArticleId()); redisTemplate.delete("article:list:" + event.getUserId()); } } -
事件驱动的微服务通信(配合消息队列)
@Service public class OrderService { @Transactional public void createOrder(OrderDTO dto) { // 1. 创建订单 Order order = orderRepository.save(new Order(dto)); // 2. 发布本地事件 eventPublisher.publishEvent(new OrderCreatedEvent(this, order)); } } // 主要就是用来解耦 便于换中间件 且 事务提交后才发送消息 @Component public class EventBridgeListener { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void sendToKafka(OrderCreatedEvent event) { // 事务提交后,发送消息到Kafka kafkaTemplate.send("order-topic", event.getOrder()); // 其他微服务(库存、支付、通知)会消费这个消息 } }
缺点
- 事件链路“不可见”
- 监听器执行顺序“不可靠”
- 异常处理“一片狼藉”
- 无界的事件队列(异步场景)