事件是异步的一种方式,比异步调用更解耦。
例如用户支付订单后,统计用户下单数据。不把统计写在主业务,因为这样会影响原本下单的流程。
我这业务量不大,所以用应用内事件的方式,没有用常用的 MQ、kafka。
Java 的应用内事件主要有 Guava 的 EventBus 和 Spirng 自带的 Event。使用方式类似的,为了方便后期转到 MQ,我用 Spring 的 Event。
事件三要素,事件,发布者,监听者。
事件就是一堆信息的打包,在 Java 中用一个类封装。如果继承 ApplicationEvent 构造器多加个 source 参数,标识事件从哪来,也可以不继承,为了规范最好继承。
@Getter
@Setter
@ToString(callSuper = true)
public class OrderEvent extends ApplicationEvent {
private Orders order;
private OrderOperationReqDto orderOperationReqDto;
public OrderEvent(Object source) {
super(source);
}
public OrderEvent(Object source, Orders order, OrderOperationReqDto orderOperationReqDto) {
super(source);
this.order = order;
this.orderOperationReqDto = orderOperationReqDto;
}
}
发布者使用 ApplicationEventPublisher 或 ApplicationContext 的 publishEvent 方法发布。
@Component
public class CommonEventPublisher {
@Resource
ApplicationEventPublisher applicationEventPublisher;
public void publish(OrderEvent event) {
applicationEventPublisher.publishEvent(event);
}
}
监听者使用 @EventListener 注解,@Async 异步消费。
@Slf4j
@Component
public class CommonEventListener {
@Async
@EventListener(OrderEvent.class)
public void listener(OrderEvent orderEvent) {
log.info("1-异步事件监听" + Thread.currentThread().getName());
}
}
最后加上一段测试代码。
@ApiOperation(value = "发送事件")
@PostMapping(value = "/event")
public Rs event() {
eventPublisher.publish(new OrderEvent(this, new Orders(), new OrderOperationReqDto()));
log.info("线程id" + Thread.currentThread().getName());
return Rs.success();
}
结果显示发送和消费的线程是不同的,说明消费成功并且是异步消费。
2023-02-04 09:34:03.861 INFO 24856 --- [io-20882-exec-1] com.macal.pzcps.web.OrdersController : 线程idhttp-nio-20882-exec-1
2023-02-04 09:34:03.862 INFO 24856 --- [ommonExecutor-1] c.m.p.mq.listener.CommonEventListener : 1-异步事件监听commonExecutor-1
因为是应用内调用,无法保证 100% 消费,记得让运维在关闭服务时延迟几秒。