在我们的交易系统中,都是以订单为载体,通过客户和商家的操作来决定订单节点的流转,同时决定着下一步该做哪些操作,简单画个图来看一下。

我们可以看到,在每一个动作之后都需要将订单同步到订单索引,同时,在特定节点的时候,还需要通知第三方平台做相应的操作。(这里只是个简单的流程图,实际的流程比这个要复杂很多)
在没使用到监听器模式之前,代码是这样的
public void createOrder() {
check()
···
···创建订单流程
···
// 索引消息发送
indexProducer.send(order);
// 通知销售有人下单
messageCenter.send(order);
// 创建支付单
payTransactionProducer.send(tran);
}
public void pay() {
check()
···
···创建订单流程
···
// 索引消息发送
indexProducer.send(order);
// 通知销售已支付,去发货
messageCenter.send(order);
// 创建物流单
logisticsOrderProducer.send(logistic);
}
这样的代码就会产生几个问题:
-
代码冗余
这个不必说了,每次状态变更都需要调用相同的代码,不仅写起来不够优雅,当你发送消息的参数改动的时候,也需要一个一个地方去修正,很浪费时间。
-
不利于扩展
产品说:“为了打入商户内部,我们要接入商户现有ERP系统,现在给👴把订单同步过去”。好了,这下,你又需要在每一个节点中加同步的方法,不仅麻烦,还容易出错。
观察者模式
本篇的重点不在于观察者模式,而在于优化代码。所以关于观察者模式就不介绍了。这里仅放个定义
观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
参考资料-> 设计模式
Spring EventListener
Spring在4.2之后提供了基于注解的事件监听器,我们可以在任意被管理的bean下,使用 @EventListener注册监听器,使用ApplicationContext来发布事件。下面看下改造流程。
- 定义一个订单事件,orderCode代表订单号,eventType代表事件类型。
@Getter
@Setter
public class OrderEvent extends ApplicationEvent {
private Order order;
private EventType eventType;
public enum EventType {
// 创建
CREATE,
// 支付
PAY,
// 确认收货
CONFIRM
}
}
-
创建事件监听器
通用的监听器
public class OrderIndexListener { @Autowired private IndexProducer indexProducer; //这个注解支持监听器异步执行,可根据需要选择 @Async @EventListener public void handlerOrderEvent(OrderEvent orderEvent) { // 索引消息发送 indexProducer.send(orderEvent.getOrder()); } }收银台监听器
public class OrderIndexListener { @Autowired private PaymentOrderProducer paymentOrderProducer; @Async @EventListener public void handlerPay(OrderEvent orderEvent) { // 特殊节点发送 if(OrderEvent.EventType.CREATE.equals(orderEvent.getEventType())) { paymentOrderProducer.send(orderEvent.getOrder()); } } } -
改造原来的代码
@Autowired private ApplicationContext applicationContext; public void createOrder(Order order) { check() ··· ···创建订单流程 ··· applicationContext.publish(new OrderEvent(CREATE, order)); } public void pay() { check() ··· ···创建订单流程 ··· applicationContext.publish(new OrderEvent(CREATE, order)); }
至此,改造已经完成。我们已经解决了之前那种写法里存在的问题,解藕了消息通知等工作,使得业务流程更加清晰,也更容易维护。
需要避过的坑
-
避免循环依赖
监听器和被观察者如果互相依赖,有可能会导致循环调用。最后导致系统崩溃
-
异步导致的调用乱序
如果使用了异步监听器,要注意给不同监听器加上@Order注解来保证调用不会乱序而影响订单流程。
如果有一些操作,比如设置了0元订单无需支付,直接从 “下单->待发货”,那么监听器会在很短时间内发送两条同步订单索引的消息,有可能会导致消费者收到消息的顺序是乱的,需要我们做额外的保证,例如延时发送,抛弃旧消息等等。
-
待扩展...