Spring事件监听实践

257 阅读4分钟

背景

工作中遇到这样一个需求:我方需为客户开发一个清算支付应用,底层支付和提现功能使用依赖其他部门提供的产品。

该产品提供了交易账户创建,交易账户创建结果查询,支付,提现等API。其中:交易账户创建API接口内部采用异步实现方式,即:接收到我方传入参数后,立即返回参数接收响应,交易账户的创建结果需调用其他接口获取,创建交易账户API响应参数结构如下

1)requestStatus:FIN,BNK
2)returnCode:SUC0000,其他错误码
3)referenceNumber:业务号,用于结果查询(我方构建唯一参数)

FIN表示处理完成,此时returnCode一般是错误信息
BNK表示正处理中,可通过referenceNumber查询另一个接口获取交易账户号

问题分析

对于该API的处理方式,想到两种方案获取交易账户号

我方提供两个接口
  • 接口1:账户创建接口,返回referenceNumber
  • 接口2:交易账户创建结果查询接口,传入接口1的返回结果,将返回的账户号更新到交易账户表
异步处理

我方调用交易账户创建API后,发送一个异步消息,由消息接收者调用交易账户创建结果查询API获取交易账户号,并更新到交易账户表,有如下三种异步处理方案

  • Spring事件监听
  • MQ
  • 多线程

鉴于我方业务设计没有提供两个接口,故而采用异步的方式处理,而且功能比较简单,事前分析过,不太可能出现较大并发,因而从资源和实现复杂性等角度考虑,最终采用Spring事件监听实现该功能。

方案设计

Spring事件监听

A)业务逻辑

  • 前端调用后端提供的接口,并传入待创建的账户信息
  • Service层调用其他部门API,若返回结果是错误的,直接报错提示,若返回结果处理中,开启事务,保存账户信息,并发布账户创建事件
  • 监听器监听到对应的事件后,查询结果。若结果成功,更新账户编号;若结果失败,继续轮询;轮询超过3次,发送告警短信

B)异常处理

  • 调用创建交易账户API失败:该问题可能是网络,服务宕机等原因导致,由于对方产品已上线运行较长时间,让对方配合我方调整成分布式事务,可能性不大,针对这种情况,我方先保存账户信息,标注为无效。继续保存和创建结果查询事件发布。
  • 保存账户信息失败:回滚
  • 发布账户创建事件失败:回滚
  • 对于我方失败的场景,需要提供一个手动任务,任务里查询交易创建结果,若结果失败,报错提示,若结果成功,保存账户信息,若结果处理中,保存账户,发布账户创建查询事件。

C)Spring事件监听问题 创建监听器的流程: image.png

  • 往容器中注册一个Bean,name=applicationEventMulticaster,类型=ApplicationEventMulticaster
  • 默认为SimpleApplicationEventMulticaster
  • 该实例默认为同步方式发布消息

SimpleApplicationEventMulticaster同步发布

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   // 查询执行器
   // Spring启动时,会创建SimpleApplicationEventMulticaster,但该对象实例的Executor为空,故而发布事件走同步方式
   Executor executor = getTaskExecutor();
   for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         invokeListener(listener, event);
      }
   }
}

SimpleApplicationEventMulticaster异步发布

/**
 * 异步发布
 * 1)往BeanFactory中注入ApplicationEventMulticaster,beanName=applicationEventMulticaster
 * 2)创建线程池
 * 3)赋值给属性TaskExecutor
 */
@Bean
public ApplicationEventMulticaster eventMulticaster() {
    SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
    multicaster.setTaskExecutor(executor);
    return multicaster;
}

/**
 * Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor})
 * to invoke each listener with.
 * <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
 * executing all listeners synchronously in the calling thread.
 * <p>Consider specifying an asynchronous task executor here to not block the
 * caller until all listeners have been executed. However, note that asynchronous
 * execution will not participate in the caller's thread context (class loader,
 * transaction association) unless the TaskExecutor explicitly supports this.
 * @see org.springframework.core.task.SyncTaskExecutor
 * @see org.springframework.core.task.SimpleAsyncTaskExecutor
 */
public void setTaskExecutor(@Nullable Executor taskExecutor) {
// executor即属性taskExecutor,若我们不想阻塞主线程,我们可以注入一个name=applicationEventMulticaster的bean,并调用setTaskExceutor方法,为了减少麻烦,我们可以注入SimpleApplicationEventMulticaster。
   this.taskExecutor = taskExecutor;
}

代码实现

参考:gitee.com/magic_decry…

总结

总体来说,功能比较简单,使用Spring事件监听机制即可,业务设计方面考虑了多种可能出现的异常场景。