DeferredResult使用详解
DeferredResult使用详解
DeferredResult是Spring MVC中处理异步请求的重要工具,掌握其使用方法对于提升系统性能和处理能力非常有帮助。本文将详细介绍DeferredResult的基本概念、使用方法和具体应用场景。
DeferredResult简介
Servlet 3.0提供了基于servlet的异步处理API,Spring MVC只是将这些API进行了一系列的封装,从而实现了DeferredResult。DeferredResult字面意思是"延迟结果",它允许Spring MVC收到请求后,立即释放(归还)容器线程,以便容器可以接收更多的外部请求,提升吞吐量。与此同时,DeferredResult将陷入阻塞,直到我们主动将结果set到DeferredResult,最后,DeferredResult会重新申请容器线程,并将本次请求返回给客户端。
DeferredResult的监听器
1. onTimeout()
当deferredResult被创建出来之后,执行setResult()之前,这之间的时间超过设定值时(比如下方案例中设置为5秒超时),则被判定为超时。
DeferredResult<String> deferredResult = new DeferredResult<String>(5 * 1000L);
// 设置超时事件
deferredResult.onTimeout(() -> {
System.out.println("异步线程执行超时, 异步线程的名称: " + Thread.currentThread().getName());
deferredResult.setResult("异步线程执行超时");
});
2. onError()
当onTimeout()或onCompletion()等回调函数中的代码报错时,则会执行监听器onError()的回调函数。DeferredResult之外的代码报错不会影响到onError()。
DeferredResult<String> deferredResult = new DeferredResult<String>(5 * 1000L);
// 设置异常事件
deferredResult.onError((throwable) -> {
System.out.println("异步请求出现错误,异步线程的名称: " + Thread.currentThread().getName() + "异常: " + throwable);
deferredResult.setErrorResult("异步线程执行出错");
});
3. onCompletion()
代码任意位置调用了同一个 DeferredResult 的setResult()后,则会被 DeferredResult 的onCompletion()监听器捕获到。Spring会任选一条容器线程来执行onCompletion( )中的代码,由于请求线程已被释放(归还),所以此处可能再次由同一条请求线程来处理,也可能由其他线程来处理。
DeferredResult<String> deferredResult = new DeferredResult<String>(5 * 1000L);
// 设置完成事件
deferredResult.onCompletion(() -> {
System.out.println("异步线程执行完毕,异步线程的名称: " + Thread.currentThread().getName());
});
使用场景
浏览器向A系统发起请求,该请求需要等到B系统(如MQ)给A推送数据时,A才会立刻向浏览器返回数据,如果指定时间内B未给A推送数据,则返回超时。
使用流程
- 浏览器发起异步请求;
- 请求到达服务端被挂起;
- 向浏览器进行响应,分为两种情况:
- 调用DeferredResult.setResult(),请求被唤醒,返回结果;
- 超时,返回一个你设定的结果;
- 浏览得到响应,再次重复1,处理此次响应结果。
代码实现
前端发起查询交易方法(queryOrderPayResult)请求,后端将请求阻塞住 5s,如果在5s之内,支付通知回调(payNotify)过来了,那么之前查询交易的方法立即返回支付结果,否则返回超时了。
@RestController
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
private static ConcurrentHashMap<String, DeferredResult<String>> DEFERRED_RESULT = new ConcurrentHashMap<>();
/**
* 查询订单支付结果
*
* @param orderId 订单编号
* @return DeferredResult
*/
@GetMapping("queryOrderPayResult")
public DeferredResult<String> queryOrderPayResult(@RequestParam("orderId") String orderId) {
log.info("订单orderId:[{}]发起了支付", orderId);
// 5s 超时
DeferredResult<String> result = new DeferredResult<>(5000L);
// 超时操作
result.onTimeout(() -> {
DEFERRED_RESULT.get(orderId).setResult("超时了");
});
// 完成操作
result.onCompletion(() -> {
DEFERRED_RESULT.remove(orderId);
});
// 保存此 DeferredResult 的结果
DEFERRED_RESULT.put(orderId, result);
return result;
}
/**
* 支付回调
*
* @param orderId 订单id
* @return 支付回调结果
*/
@GetMapping("payNotify")
public String payNotify(@RequestParam("orderId") String orderId) {
if (DEFERRED_RESULT.containsKey(orderId)) {
Optional.ofNullable(DEFERRED_RESULT.get(orderId)).ifPresent(result -> result.setResult("完成支付"));
// 设置之前orderId toPay请求的结果
return "回调处理成功";
}
return "回调处理失败";
}
}
结果演示
超时操作
页面请求 http://localhost:7070/queryOrderPayResult?orderId=12345方法,在5s之内没有setResult 结果,直接返回超时了。
正常操作
本文原文来自CSDN