一、背景最近在做一个项目的过程中,有一个支付场景,前端需要根据支付结果跳转到不同的页面。但是我们的支付通知是由支付方异步返回的,所以发送支付请求后并不能立即得到支付结果。这时候我们就需要轮流训练交易结果来判断支付是否成功。2.分析后端通知前端支付结果的实现方式有很多种。ajax轮训longrotationwebsocketsse……经过考虑,最终决定使用longrotationtraining来实现。而Spring的DeferredResult是一个异步请求,可以用来实现长期训练。而这个异步是基于Servlet3的异步实现的。在Spring中,DeferredResult的结果会被另外一个线程处理,不会占用容器(Tomcat)的线程,所以也可以提高程序的吞吐量。3、实现需要前端请求查询交易方法(queryOrderPayResult),后端会阻塞请求3s。如果支付通知回调(payNotify)在3s内到来,那么之前查询交易的方法会立即返回支付结果,否则会返回一个timeoutup。四、后端代码现实packagecom.huan.study.controller;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;导入org.springframework.web.bind.annotation.RestController;导入org.springframework.web.context.request.async.DeferredResult;导入javax.annotation.PostConstruct;导入java.util.Optional;导入java。util.concurrent.ConcurrentHashMap;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.atomic.AtomicInteger;/***订单控制器**@authorhuan.fu2021/10/14-上午9:34*/@RestControllerpublicclassOrderController{privatestaticfinalLoggerlog=LoggerFactory.getLogger(OrderController.class);privatestaticvolatileConcurrentHashMap>DEFERRED_RESULT=newConcurrentHashMap<>(20000);私人静态挥发性原子整数ATOMIC_INTEGER=newAtomicInteger(0);@PostConstructpublicvoidprintRequestCount(){Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(()->{log.error(""+ATOMIC_INTEGER.get());},10,1,TimeUnit.SECONDS);}/***查询订单支付结果**@paramorderId订单号*@returnDeferredResult*/@GetMapping("queryOrderPayResult")publicDeferredResultqueryOrderPayResult(@RequestParam("orderId")StringorderId){log.info("订单orderId:[{}]发起支付",orderId);ATOMIC_INTEGER.incrementAndGet();//3秒超时DeferredResultresult=newDeferredResult<>(3000L);//超时操作结果.onTimeout(()->{DEFERRED_RESULT.get(orderId).setResult("超时");log.info("订单orderId:[{}]发起支付,结果超时。",订单编号);});//完成操作result.onCompletion(()->{log.info("订单orderId:[{}]完成。",orderId);DEFERRED_RESULT.remove(orderId);});//保存这个DeferredResult的结果DEFERRED_RESULT.put(orderId,result);返回结果;}/***支付回调**@paramorderId订单id*@return支付回调结果*/@GetMapping("payNotify")publicStringpayNotify(@RequestParam("orderId")StringorderId){log.info("orderorderId:[{}]支付完成回调",orderId);//默认结果有异常if("123".equals(orderId)){DEFERRED_RESULT.get(orderId).setErrorResult(newRuntimeException("订单有异常"));return"回调处理失败";}if(DEFERRED_RESULT.containsKey(orderId)){Optional.ofNullable(DEFERRED_RESULT.get(orderId)).ifPresent(result->result.setResult("完成支付"));//设置上一个orderId的结果为Pay请求return"Callbackprocessedsuccessed";}return"回调处理失败";}}五、运行结果1、超时操作页面请求http://localhost:8080/queryOrderPayResult?orderId=12345方法,如果3s内没有DeferredResult#setResult没有设置结果,会直接返回超时//localhost:8080/payNotify?orderId=12345方法,得到正确的结果。6、DeferredResult运行原理Controller返回一个DeferredResult对象,保存在一个可访问的内存队列或列表中。SpringMvc开始异步处理。与此同时,DispatcherServlet和所有配置的过滤器退出请求处理线程,但Response(响应)保持打开状态。应用程序从某个线程设置DeferredResult,SpringMVC将请求分派回Servlet容器。再次调用DispatcherServlet并使用异步生成的返回值继续处理。六、注意事项1、异常处理可以通过@ExceptionHandler来处理。2.异步进程中的拦截器。可以通过DeferredResultProcessingInterceptor或AsyncHandlerInterceptor来实现。需要注意拦截器方法的注释。如果调用setResult,有些方法将不会再次执行。Configuration:/***如果加上@EnableWebMvc注解,Spring的很多默认配置就没有了,需要自己配置**@authorhuan.fu2021/10/14-10:39AM*/@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{@OverridepublicvoidconfigureAsyncSupport(AsyncSupportConfigurerconfigurer){//默认超时60sconfigurer.setDefaultTimeout(60000);//注册延迟结果截屏configurer.registerDeferredResultInterceptors(newCustomDeferredResultProcessingInterceptor());}@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newCustomAsyncHandlerInterceptor()).addPathPatterns("/**");}}七、完整代码https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/spring-deferred-result8.参考链接https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async-deferredresult