下单成功之后,方法为什么越写越长
场景复刻:createOrder() 里锁库存、扣款之后,还有发短信、加积分、记日志、通知仓储——一个方法塞了一串「顺便干的事」。主干被噪音淹没,每加一个通知点就要改一次下单代码,写单测还得把短信网关 mock 掉。这是架构问题:主干和旁支耦合太紧。Spring 事件机制就是进程内解耦的工具:主干只管发布「订单已创建」这个事实,谁关心谁监听。
三步上手:定义、发布、监听
// 1. 定义事件:普通 POJO 即可,不强制继承任何类
public class OrderCreatedEvent {
private final Long orderId;
public OrderCreatedEvent(Long orderId) {
this.orderId = orderId;
}
public Long getOrderId() {
return orderId;
}
}
// 2. 发布:注入 ApplicationEventPublisher
@Service
public class OrderService {
private final ApplicationEventPublisher publisher;
public OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void createOrder() {
// ...下单主干...
publisher.publishEvent(new OrderCreatedEvent(9527L));
}
}
// 3. 监听:@EventListener 标在任意 Bean 的方法上
@Component
public class OrderListeners {
@EventListener
public void addPoints(OrderCreatedEvent event) {
// 加积分
}
@EventListener
public void notifyWarehouse(OrderCreatedEvent event) {
// 通知仓储
}
}一个事件多个监听器各干各的;加监听器不用动发布方;监听方法的参数就是事件对象,按类型自动路由。发布方和监听方互不知道对方存在,只认识事件本身——这就是解耦。
三个真相:默认同步,这不是 MQ
第一,事件默认同步执行:publishEvent 在哪个线程调用,监听器就在哪个线程跑;监听器抛异常会一路传回发布方——短信网关超时能拖垮下单主流程,要异步就给监听方法再加 @Async。第二,它是进程内的:应用重启事件就没了,没有持久化、没有重试;要跨服务、可重放的通信,那是 MQ 的活,事件机制替代不了。第三,监听器之间没有顺序保证(除非显式加 @Order),有先后依赖的逻辑别拆进两个监听器。
@TransactionalEventListener:等提交了再喊
经典暗坑:下单方法带着 @Transactional,监听器里去查库——publishEvent 发生在 commit 之前,监听器执行的瞬间事务还没提交,查库查不到刚写入的数据,短信内容空白、下游查询落空。解法是把 @EventListener 换成 @TransactionalEventListener(phase = AFTER_COMMIT):事务成功提交后才触发,时序对齐。它还有 BEFORE_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION 三个相位,回滚补偿用 AFTER_ROLLBACK 接。再记一个细节:AFTER_COMMIT 的监听器里如果还要写库,必须标 @Transactional(propagation = Propagation.REQUIRES_NEW)——原事务已经提交结束,默认传播行为会让新写库操作误以为自己在事务里却没真提交,传播行为那篇的知识点在这里接上了。
下一篇从运行时回到启动时:SpringApplication.run 背后,Spring Boot 的启动流程到底按什么顺序干了哪些事。
评论 (0)