利用Spring监听器优化代码

456 阅读3分钟

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

订单流程

我们可以看到,在每一个动作之后都需要将订单同步到订单索引,同时,在特定节点的时候,还需要通知第三方平台做相应的操作。(这里只是个简单的流程图,实际的流程比这个要复杂很多)

在没使用到监听器模式之前,代码是这样的

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);
}

这样的代码就会产生几个问题:

  1. 代码冗余

    这个不必说了,每次状态变更都需要调用相同的代码,不仅写起来不够优雅,当你发送消息的参数改动的时候,也需要一个一个地方去修正,很浪费时间。

  2. 不利于扩展

    产品说:“为了打入商户内部,我们要接入商户现有ERP系统,现在给👴把订单同步过去”。好了,这下,你又需要在每一个节点中加同步的方法,不仅麻烦,还容易出错。

观察者模式

本篇的重点不在于观察者模式,而在于优化代码。所以关于观察者模式就不介绍了。这里仅放个定义

观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。

参考资料-> 设计模式

Spring EventListener

Spring在4.2之后提供了基于注解的事件监听器,我们可以在任意被管理的bean下,使用 @EventListener注册监听器,使用ApplicationContext来发布事件。下面看下改造流程。

  1. 定义一个订单事件,orderCode代表订单号,eventType代表事件类型。
@Getter
@Setter
public class OrderEvent extends ApplicationEvent {

    private Order order;

   private EventType eventType;
   public enum EventType {

        // 创建
        CREATE,
        // 支付
        PAY,
				// 确认收货
        CONFIRM
    }

}
  1. 创建事件监听器

    通用的监听器

    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());
          }	
        }
    }	
    
  2. 改造原来的代码

    @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));
    }
    

至此,改造已经完成。我们已经解决了之前那种写法里存在的问题,解藕了消息通知等工作,使得业务流程更加清晰,也更容易维护。

需要避过的坑

  1. 避免循环依赖

    监听器和被观察者如果互相依赖,有可能会导致循环调用。最后导致系统崩溃

  2. 异步导致的调用乱序

    如果使用了异步监听器,要注意给不同监听器加上@Order注解来保证调用不会乱序而影响订单流程。

    如果有一些操作,比如设置了0元订单无需支付,直接从 “下单->待发货”,那么监听器会在很短时间内发送两条同步订单索引的消息,有可能会导致消费者收到消息的顺序是乱的,需要我们做额外的保证,例如延时发送,抛弃旧消息等等。

  3. 待扩展...