Java Spring 应用内事件

190 阅读2分钟

事件是异步的一种方式,比异步调用更解耦。

例如用户支付订单后,统计用户下单数据。不把统计写在主业务,因为这样会影响原本下单的流程。

我这业务量不大,所以用应用内事件的方式,没有用常用的 MQ、kafka。

Java 的应用内事件主要有 Guava 的 EventBus 和 Spirng 自带的 Event。使用方式类似的,为了方便后期转到 MQ,我用 Spring 的 Event。

事件三要素,事件,发布者,监听者。

image.png

事件就是一堆信息的打包,在 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;
    }
}

发布者使用 ApplicationEventPublisherApplicationContext 的 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% 消费,记得让运维在关闭服务时延迟几秒。