1. 概述
本文我们将学习如何在Spring MVC中使用 DeferredResult 来执行异步请求处理。
异步支持是在Servlet 3.0中引入的,它允许在接收请求的线程中新开另一个线程处理HTTP请求,同时客户端连接不会被中断。
Spring 3.2 开始提供DeferredResult,允许将耗时任务从HTTP Worker线程放到独立线程中执行,Work线程可以继续新的HTTP请求而不会被阻塞,大大提供系统吞吐量,特别是对于IO密集型应用。
2. 阻塞请求
我们平常开发的Controller默认就是阻塞,请求处理完成并返回结果之前,Http Worker线程会一直阻塞。
@GetMapping("/process-blocking")
public ResponseEntity<?> handleReqSync(Model model) {
// ...
return ResponseEntity.ok("ok");
}
3. 使用DeferredResult实现非阻塞
为了避免Worker线程阻塞,我们使用回调编程模型,返回类型改为DeferredResult,真实的结果通过回调的方式通知:
@GetMapping("/async-deferredresult")
public DeferredResult<ResponseEntity<?>> handleReqDefResult(Model model) {
LOG.info("收到 async-deferredresult 请求");
DeferredResult<ResponseEntity<?>> output = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
LOG.info("在新线程中执行");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
}
output.setResult(ResponseEntity.ok("ok"));
});
LOG.info("servlet 线程释放");
return output;
}
耗时任务在新线程中处理,这里我们用sleep模拟耗时操作。执行完成后调用DeferredResult的setResult方法通知结果
我们可以通过查看日志验证结果:
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController:
收到 async-deferredresult 请求
[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController:
Servlet 线程释放
[nio-8080-exec-6] java.lang.Thread : 在新线程中执行
4. DeferredResult 注册回调事件
DeferredResult 同时提供了3个注册回调事件的方法,用来捕捉完成、超时和错误事件。
onCompletion()
—— 当任务完成后触发:
deferredResult.onCompletion(() -> LOG.info("Processing complete"));
onTimeout()
—— 可设置 DeferredResult 超时时间限制处理时长,超时未完成会回调此函数:
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(500l);
deferredResult.onTimeout(() ->
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
.body("Request timeout occurred.")));
下面模拟超时操作:
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
...
}
deferredResult.setResult(ResponseEntity.ok("OK")));
});
输出日志:
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController:
servlet thread freed
[nio-8080-exec-6] java.lang.Thread: Processing in separate thread
[nio-8080-exec-6] com.baeldung.controller.DeferredResultController:
Request timeout occurred
onError()
会在发生错误和异常时执行:
deferredResult.onError((Throwable t) -> {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An error occurred."));
});
5. 总结
本文,我们研究了SpringMVC DeferredResult 如何简化异步接口的创建。
文中代码可在 Github 中查看。