【JAVA基础】DeferredResult使用详解

简介

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. 浏览器发起异步请求;
  2. 请求到达服务端被挂起;
  3. 向浏览器进行响应,分为两种情况:1. 调用DeferredResult.setResult(),请求被唤醒,返回结果;2.超时,返回一个你设定的结果;
  4. 浏览得到响应,再次重复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 结果,直接返回超时了。
【JAVA基础】DeferredResult使用详解_第1张图片

正常操作

页面请求 http://localhost:7070/queryOrderPayResult?orderId=12345方法之后,并立即请求http://localhost:7070/payNotify?orderId=12345方法,得到了正确的结果。
【JAVA基础】DeferredResult使用详解_第2张图片

【JAVA基础】DeferredResult使用详解_第3张图片

你可能感兴趣的:(java基础,java,后端)