SpringMVC 教程 - 异步请求
Spring MVC 集成了Servlet 3.0的异步请求处理:
-
controller 的方法返回
DeferredResult
,Callable
-
controller 流式处理多个值,包括
SSE
和原生数据。 - controller 使用reactive客户端,返回reactive 类型。
DeferredResult
在Servlet
容器
中启动异步支持之后,controller的方法可以通过
DeferredResult
包装返回值来支持异步处理。例如:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
// From some other thread...
deferredResult.setResult(data);
controller可以通过其他线程异步返回结果,例如:响应时间(例如JMS消息),一个调度任务或者其他类型。
Callable
java.util.Callable
也可以包装任何需要异步支持的返回值。例如:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
返回值由配置的
TaskExecutor
执行相应任务并返回结果。
处理异步请求
Servlet异步请求的处理过程如下:
-
通过调用
request.startAsync()
开始异步处理。调用后Servlet,Filter等可以退出,但是响应开发,直到处理完成返回。 -
调用
request.startAsync()
后返回AsyncContext
,可以在后续的处理中使用AsyncContext获取各种信息。 -
ServletRequest
提供接口访问当前的DispatcherType
以便区分初始请求,异步分配,重定向或者其他DispatcherType
。
DeferredResult
处理过程:
-
controller返回一个
DeferredResult
并且将其保存到内存中的队列或者列表中。 -
Spring MVC调用
request.startAsync()
-
同时
DispatcherServlet
,Fileter
,退出请求处理线程,响应保持开放。 -
应用从线程获取值设置
DeferredResult
,Spring MVC将请求发送回Servlet 容器。 -
再次调用
DispatcherServlet
,获取异步返回值,恢复请求处理。
Callable
处理过程:
-
controller 返回一个
Callable
-
Spring MVC 调用
request.startAsync()
,将Callable
提交到TaskExecutor
处理 -
同时
DispatcherServlet
,Fileter
,退出请求处理线程,响应保持开放。 -
Callable
产生结果,Spring MVC将请求发送回Servlet 容器。 -
再次调用
DispatcherServlet
,通过从Callable
获取的返回值恢复请求处理。
异常处理
使用
DeferredResult
可以调用
setResult
或者
setErrorResult
来返回结果,调用这两个函数后Spring MVC都会将请求发送回Servlet 容器以完成处理。接着会检查时正常返回还是返回了异常,如果有异常返回就走一般的异常处理流程,例如:调用
@ExceptionHandler
方法。
使用
Callable
的处理流程大体相同,主要的区别是又Callable返回结果或者抛出异常。
拦截器
AsyncHandlerInterceptor
可以在异步请求处理开始后接收
afterConcurrentHandlingStarted
的回调代替
postHandle
和
afterCompltetion
。
CallableProcessingInterceptor
或者
DeferredResultProcessingInterceptor
深度继承异步处理请求的生命周期,例如超时处理等。
DeferredResult
提供了
onTimeout(Runnable)
和
onCompletion(Runnable)
的回调。详情可以查看JavaDoc。
Callable
可以取代
WebAsyncTask
,它提供了超时和完成的回调。
与WebFlux对比
Servlet API之前是为Filter-Servlet请求处理链构建的。在Servlet 3.0 添加了异步处理后,允许应用退出Filter-Servlet请求处理链,只保留响应开放以便日后处理。Spring MVC支持的异步处理就是建立在这项技术之上的。当controller返回一个
DeferredResult
后,Filter-Servlet处理链退出,Servlet容器的请求线程释放。稍后
DeferredResult
返回结果,开始一个异步调用,重新映射到controller但是并不在调用controller,使用
DeferredResult
的返回值继续处理结果。
作为对比Spring WebFlux既没有使用Servlet API也不需要这样的一个异步处理模型,因为它完全是异步设计的。异步处理内置在所有的WebFlux框架中,并且支持异步处理的每一个步骤。
从编程模型来看,Spring MVC和Spring WebFlux都支持异步处理和返回Reactive类型。Spring MVC甚至支持流处理。然而并不想WebFlxu一样使用非阻塞IO,每次写入响应无需单独的线程,SpringMVC单独写入响应仍然是阻塞的。
另一项区别就是Spring MVC不支持异步或者reactive类型作为函数参数。Spring WebFlux支持。
HTTP 流
DeferredResult
和
Callback
每次只能异步返回一个值。如果要返回到个值则可以用HTTP 流。
Objects
ResponseBodyEmitter
返回值可以讲多个对象生成一个流,每个对象都通过
HttpMessageConverter
序列化发送,例如:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
return emitter;
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
ResponseBodyEmitter
同样也可以放入
ResponseEntity
,这样就可以定制响应的header和状态了。
emitter
抛出
IOException
异常的时候(例如,远程client关闭),应用并不负责回收连接,也不会调用
emitter.complete()
或者
emitter.completeWithError
。相反,Servlet容器会自动初始化一个AsyncListener错误通知,Spring MVC将会调用
completeWithError
,反过来执行异步分配,应用继续执行正常的异常处理流程。
SSE
SseEmitter
是
ResponseBodyEmitter
的子类,提供了Server-Send Events的支持。例如:
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter();
// Save the emitter somewhere..
return emitter;
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
SSE 是将流发送到浏览器的主要方式,但是IE浏览器不支持。如果想要支持更多浏览器,可以使用Spring的SockJS。
原始数据
有时绕过消息转换,直接将流写入到响应的
OutputStream
更加实用,例如:下载。可以使用
StreamingResponseBody
作为返回值处理:
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
StreamingResponseBody
也可以放入
ResponseEntity
中来定制响应header和状态。
Reactive 类型
Spring MVC支持在controller使用reactive client 库。包括spring-webflux中的
WebClient
和Spring Data 中的reactive 数据资源库。在一些场景中,从controller返回reactive类型非常的方便。
Reactive返回处理方式如下:
-
类似
DeferredResult
单一值的promise,例如:Reactor的Mono,RxJava的Single。 -
类似
ResponseBodyEmitter
或者SseEmitter
的多值流(multi-value stream),流的媒体类型是application/stream+json
或者text/event-stream
。例如,Reactor的Flux,RxJava的Observable。应用可以返回Flux