鹏磊我之前有段时间特别迷 MQ,啥异步场景都往上怼,RabbitMQ、RocketMQ 搞得很花,有一次就为了做个下单后发短信加积分的需求,硬是在内部系统里引了一套 RabbitMQ 进来
被同事笑话说这不叫杀鸡用牛刀,这叫杀鸡用核武器
后来才想起来,Spring 本身就内置了一套事件驱动机制,轻量得很,专治同 JVM 内部的异步解耦场景,白瞎我那几天折腾
问题出在哪,先看看这段代码
你写过这种东西吗:
public void createOrder(OrderDTO dto) {
// 保存订单
orderService.save(dto);
// 发短信
smsService.sendOrderSuccessSms(dto.getPhone());
// 加积分
pointsService.addPoints(dto.getUserId(), 100);
// 推送站内信
notificationService.push(dto.getUserId(), "订单创建成功");
// 记操作日志
logService.record("下单成功", dto.getOrderId());
}
鹏磊见过不少人写,咱也写过,一个"创建订单"的方法,里头塞了短信、积分、通知、日志,五件事挤一块儿
核心问题是,短信发没发、积分加没加,跟订单创建成不成功其实没啥关系,你把这些非核心逻辑全硬编码进来,它们就和主流程死死绑在一块了
哪天产品说再加个功能,比如下单后更新用户等级,你就得打开这个方法,再加一行,方法越来越长,越来越臃肿,谁维护谁骂娘
如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 一万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的技术五百套,精品系列教程,免费提供。
Spring 事件机制是咋回事
Spring 从很早就有 ApplicationEvent 这套东西,原理是观察者模式(Observer Pattern),说白了就是发布订阅
发布方扔一个事件出去,不管谁在监听、有几个人在监听,它发完就走,不等,不管
监听方自己注册感兴趣的事件,事件来了自己处理,和发布方互不认识,互不干扰
整套机制跑在 JVM 内部,没有网络开销,没有消息队列中间件,比 MQ 省资源,延迟更低,部署也没额外的组件依赖,适合同服务内部的异步解耦这种场景
三步搞定,核心就这些
先定义事件
// 继承 ApplicationEvent,把要传递的业务数据带上
public class OrderCreatedEvent extends ApplicationEvent {
private final String orderId;
private final String phone;
private final Long userId;
public OrderCreatedEvent(Object source, String orderId, String phone, Long userId) {
super(source);
this.orderId = orderId;
this.phone = phone;
this.userId = userId;
}
// getter 略
}
就是个普通 Java 类,继承 ApplicationEvent,把业务数据搁里头,没啥复杂的
主流程发布事件
@Service
@RequiredArgsConstructor
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public void createOrder(OrderDTO dto) {
// 只干核心事:保存订单
orderRepository.save(buildOrder(dto));
// 发完就走,后续谁处理不管
eventPublisher.publishEvent(
new OrderCreatedEvent(this, dto.getOrderId(), dto.getPhone(), dto.getUserId())
);
}
}
注入 ApplicationEventPublisher,一句 publishEvent 了事,主流程干净利落,没有任何短信积分的影子
监听器各干各的
@Component
public class OrderEventListener {
// @Async 让监听器异步执行,不加默认同步
@EventListener
@Async
public void sendSms(OrderCreatedEvent event) {
smsService.sendOrderSuccessSms(event.getPhone());
}
@EventListener
@Async
public void addPoints(OrderCreatedEvent event) {
pointsService.addPoints(event.getUserId(), 100);
}
@EventListener
@Async
public void recordLog(OrderCreatedEvent event) {
logService.record("下单成功", event.getOrderId());
}
}
每个监听方法管一件事,互不干扰,Spring 自动根据事件类型路由过来
@Async 这个注解注意下,不加的话监听是同步执行的,发布方会等监听方跑完才往下走,这就跟没异步一样了,想真正解耦得加上,同时启动类加个 @EnableAsync 开关:
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
新增功能,核心代码一行不用动
这才是鹏磊最喜欢这套方案的地方
哪天产品说,下单后还要给用户推一条站内信,你怎么搞?直接在监听器里加个方法:
@EventListener
@Async
public void sendNotification(OrderCreatedEvent event) {
notificationService.push(event.getUserId(), "您的订单已提交成功");
}
OrderService 里的 createOrder 方法,一个字都不用动
以前那种写法,你得打开核心方法,加一行,提交,走 code review,万一改出问题还得背锅,现在你根本不需要碰那块代码,稳得很
啥时候用 Spring 事件,啥时候上 MQ
这俩不是替代关系,别搞混了
我鹏磊的判断是这样的:
用 Spring 事件:同一个服务内部的异步解耦,不需要跨服务通信,不需要消息持久化,不需要断了还能重投,对消息丢失不敏感的场景
发个短信失败了大不了记个日志,用 Spring 事件够了,零中间件,零配置,上手五分钟
上 MQ:需要跨服务消费,需要消息不能丢,需要消费方单独扩容,需要延时消息或者死信队列这些高级功能,那就老老实实引 MQ,别省这点事
两个工具用途不一样,别因为 MQ 听起来高端就啥都往上怼,也别因为 Spring 事件轻量就觉得它能全部替代 MQ,各有各的地盘,用对了才省心
鹏磊总结一下这套东西的核心价值:主流程只干主流程的事,旁路逻辑全交给监听器,新增功能加监听器,删功能删监听器,核心代码稳如老狗,Spring 里头这种内置的好东西其实挺多的,白嫖就完了