简介
Servlet3.0提供了基于servlet的异步处理api,Spring MVC只是将这些api进行了一系列的封装,从而实现了DeferredResult。
DeferredResult字面意思是"延迟结果",它允许Spring MVC收到请求后,立即释放(归还)容器线程,以便容器可以接收更多的外部请求,提升吞吐量,与此同时,DeferredResult将陷入阻塞,直到我们主动将结果set到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的流程:
- 浏览器发起异步请求;
- 请求到达服务端被挂起;
- 向浏览器进行响应,分为两种情况:1. 调用
DeferredResult.setResult()
,请求被唤醒,返回结果;2.超时,返回一个你设定的结果; - 浏览得到响应,再次重复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 结果,直接返回超时了。
正常操作
页面请求 http://localhost:7070/queryOrderPayResult?orderId=12345
方法之后,并立即请求http://localhost:7070/payNotify?orderId=12345
方法,得到了正确的结果。