首页>>后端>>Spring->Spring DeferredResult 异步请求

Spring DeferredResult 异步请求

时间:2023-11-29 本站 点击:0

一、背景

最近在做项目的过程中,有一个支付的场景,前端需要根据支付的结果,跳转到不同的页面中。而我们的支付通知是支付方异步通知回来的,因此在发出支付请求后 无法立即获取到支付结果,此时我们就需要轮训交易结果,判断是否支付成功。

二、分析

要实现后端将支付结果通知给前端,实现的方式有很多种。

ajax 轮训

长轮训

websocket

sse ......

经过考虑,最终决定使用 长轮训 来实现。 而 Spring 的 DeferredResult 是一个异步请求,正好可以用来实现长轮训。而这个异步是基于 Servlet3的异步来实现的,在Spring中DeferredResult结果会另起线程来处理,并不会占用容器(Tomcat)的线程,因此还能提高程序的吞吐量。

三、实现要求

前端请求 查询交易方法(queryOrderPayResult),后端将请求阻塞住 3s,如果在3s之内,支付通知回调(payNotify)过来了,那么之前查询交易 的方法立即返回支付结果,否则返回超时了。

四、后端代码实现

packagecom.huan.study.controller;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importorg.springframework.web.context.request.async.DeferredResult;importjavax.annotation.PostConstruct;importjava.util.Optional;importjava.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<String,DeferredResult<String>>DEFERRED_RESULT=newConcurrentHashMap<>(20000);privatestaticvolatileAtomicIntegerATOMIC_INTEGER=newAtomicInteger(0);@PostConstructpublicvoidprintRequestCount(){Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(()->{log.error(""+ATOMIC_INTEGER.get());},10,1,TimeUnit.SECONDS);}/***查询订单支付结果**@paramorderId订单编号*@returnDeferredResult*/@GetMapping("queryOrderPayResult")publicDeferredResult<String>queryOrderPayResult(@RequestParam("orderId")StringorderId){log.info("订单orderId:[{}]发起了支付",orderId);ATOMIC_INTEGER.incrementAndGet();//3s超时DeferredResult<String>result=newDeferredResult<>(3000L);//超时操作result.onTimeout(()->{DEFERRED_RESULT.get(orderId).setResult("超时了");log.info("订单orderId:[{}]发起支付,获取结果超时了.",orderId);});//完成操作result.onCompletion(()->{log.info("订单orderId:[{}]完成.",orderId);DEFERRED_RESULT.remove(orderId);});//保存此DeferredResult的结果DEFERRED_RESULT.put(orderId,result);returnresult;}/***支付回调**@paramorderId订单id*@return支付回调结果*/@GetMapping("payNotify")publicStringpayNotify(@RequestParam("orderId")StringorderId){log.info("订单orderId:[{}]支付完成回调",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("完成支付"));//设置之前orderIdtoPay请求的结果return"回调处理成功";}return"回调处理失败";}}

五、运行结果

1、超时操作

页面请求 http://localhost:8080/queryOrderPayResult?orderId=12345方法,在3s之内没有DeferredResult#setResult没有设置结果,直接返回超时了。

2、正常操作

页面请求 http://localhost:8080/queryOrderPayResult?orderId=12345方法之后,并立即请求http://localhost:8080/payNotify?orderId=12345方法,得到了正确的结果。

六、DeferredResult运行原理

Controller 返回一个 DeferredResult 对象,并且把它保存在一个可以访问的内存队列或列表中。

Spring Mvc 开始异步处理。

同时,DispatcherServlet 和所有配置的过滤器退出请求处理线程,但Response(响应)保持打开状态。

应用程序从某个线程设置 DeferredResult,Spring MVC 将请求分派回 Servlet 容器。

DispatcherServlet 再次被调用,并以异步产生的返回值恢复处理 。

六、注意事项

1、异常的处理

可以通过 @ExceptionHandler 来处理。

2、异步过程中的拦截器。

可以通过 DeferredResultProcessingInterceptor 或者 AsyncHandlerInterceptor 来实现。需要注意看拦截器方法上的注释,有些方法,如果调用了setResult等是不会再次执行的。

配置:

/***如果加了@EnableWebMvc注解的话,Spring很多默认的配置就没有了,需要自己进行配置**@authorhuan.fu2021/10/14-上午10:39*/@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{@OverridepublicvoidconfigureAsyncSupport(AsyncSupportConfigurerconfigurer){//默认超时时间60sconfigurer.setDefaultTimeout(60000);//注册deferredresult拦截器configurer.registerDeferredResultInterceptors(newCustomDeferredResultProcessingInterceptor());}@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newCustomAsyncHandlerInterceptor()).addPathPatterns("/**");}}

七、完整代码

https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/spring-deferred-result

八、参考链接

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async-deferredresult


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Spring/335.html