在前一篇文章里介绍了Spring MVC对于Servlet 3.0 Async Processing的支持。 同时也提到了Spring MVC并没有提供对Servlet 3.1 Async IO的直接支持,本文介绍一些在Spring MVC中使用到Servlet 3.1 Async Processing的方法。
在开始讲解前要先了解Spring MVC是如何做Async Processing的,这里涉及到一个关键类WebAsyncManager
。
在前一篇文章里提到过以下几种AsyncHandlerMethodReturnValueHandler
:
CallableMethodReturnValueHandler
DeferredResultMethodReturnValueHandler
ResponseBodyEmitterReturnValueHandler
StreamingResponseBodyReturnValueHandler
仔细看这些Handler的代码会发现最终都使用WebAsyncManager
执行异步处理。
WebAsyncManager
执行的大致动作有这么几个:
- 调用
ServletRequest.startAsync()
- 使用
AsyncTaskExecutor
开启另一个线程执行任务 - 调用各种Interceptor的回调方法,比如
CallableProcessingInterceptor
和DeferredResultProcessingInterceptor
下面以Callable<?>
为例,详细讲解一下WebAsyncManager
的执行过程(下面的T-*代表不同的线程):
- T-http-1:
WebAsyncManager.startCallableProcessing
- T-http-1:
CallableInterceptorChain.applyBeforeConcurrentHandling
->CallableProcessingInterceptor.beforeConcurrentHandling
- T-http-1:
WebAsyncManager.startAsyncProcessing
->AsyncWebRequest.startAsync
->ServletRequest.startAsync
- T-http-1:
AsyncTaskExecutor.submit(Callable)
- T-mvc-async:
CallableInterceptorChain.applyPreProcess
->CallableProcessingInterceptor.preProcess
- T-mvc-async:
Callable.run
,等待执行完毕 - T-mvc-async:
CallableInterceptorChain.applyPostProcess
->CallableProcessingInterceptor.postProcess
- T-mvc-async:
WebAsyncManager.setConcurrentResultAndDispatch
->AsyncWebRequest.dispatch
->ServletRequest.dispatch
- T-http-2: DispatchServlet ...
- T-http-2:
AsyncWebRequest.onComplete
->CallableInterceptorChain.triggerAfterCompletion
->CallableProcessingInterceptor.afterCompletion
上面的结果可以通过观察源代码、访问http://localhost:8080/callable-trace查看日志,下面是日志样例(注意观察CallableProcessingLogger
):
2017-12-25 16:58:09.632 DEBUG 19794 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/callable-trace]
2017-12-25 16:58:09.632 DEBUG 19794 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /callable-trace
2017-12-25 16:58:09.632 DEBUG 19794 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public java.util.concurrent.Callable<java.lang.String> me.chanjar.learning.CallableTraceController.hello()]
2017-12-25 16:58:09.632 DEBUG 19794 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Last-Modified value for [/callable-trace] is: -1
2017-12-25 16:58:09.633 INFO 19794 --- [nio-8080-exec-1] orConfiguration$CallableProcessingLogger : beforeConcurrentHandling
2017-12-25 16:58:09.633 DEBUG 19794 --- [nio-8080-exec-1] o.s.w.c.request.async.WebAsyncManager : Concurrent handling starting for GET [/callable-trace]
2017-12-25 16:58:09.633 DEBUG 19794 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Leaving response open for concurrent processing
2017-12-25 16:58:09.633 INFO 19794 --- [ my-mvc-async-1] orConfiguration$CallableProcessingLogger : preProcess
2017-12-25 16:58:10.636 INFO 19794 --- [ my-mvc-async-1] orConfiguration$CallableProcessingLogger : postProcess
2017-12-25 16:58:10.636 DEBUG 19794 --- [ my-mvc-async-1] o.s.w.c.request.async.WebAsyncManager : Concurrent result value [Hi from CallableTraceController. Current Thread: my-mvc-async-2] - dispatching request to resume processing
2017-12-25 16:58:10.636 DEBUG 19794 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' resumed processing GET request for [/callable-trace]
2017-12-25 16:58:10.637 DEBUG 19794 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /callable-trace
2017-12-25 16:58:10.637 DEBUG 19794 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public java.util.concurrent.Callable<java.lang.String> me.chanjar.learning.CallableTraceController.hello()]
2017-12-25 16:58:10.637 DEBUG 19794 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Last-Modified value for [/callable-trace] is: -1
2017-12-25 16:58:10.637 DEBUG 19794 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerAdapter : Found concurrent result value [Hi from CallableTraceController. Current Thread: my-mvc-async-2]
2017-12-25 16:58:10.638 DEBUG 19794 --- [nio-8080-exec-2] m.m.a.RequestResponseBodyMethodProcessor : Written [Hi from CallableTraceController. Current Thread: my-mvc-async-2] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter@70444987]
2017-12-25 16:58:10.639 DEBUG 19794 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
2017-12-25 16:58:10.639 DEBUG 19794 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Successfully completed request
2017-12-25 16:58:10.639 INFO 19794 --- [nio-8080-exec-2] orConfiguration$CallableProcessingLogger : afterCompletion
先来回顾一下使用ReadListener
的大致步骤:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext asyncCtx = req.startAsync();
ServletInputStream is = req.getInputStream();
is.setReadListener(new ReadListener() {
@Override
public void onDataAvailable() {
while (is.isReady() && !is.isFinished()) {
int length = is.read(buffer);
if (length == -1 && is.isFinished()) {
asyncCtx.complete();
return;
}
// 处理buffer
}
}
@Override
public void onAllDataRead() {
// ...
}
@Override
public void onError(Throwable t) {
// ...
}
});
}
所以如果要使用ReadListener
,那么能够切入的点有两个:第5步和第7步。
如果在第5步CallableProcessingInterceptor.preProcess
里使用ReadListener
则:
- 在
ReadListener.onAllDataRead
或者onError
之前,Callable<?>
必须阻塞。 ReadListener
和Callable<?>
需要有通信机制。Callable<?>
可以根据不同情况返回不同的结果。
如果在第7步CallableProcessingInterceptor.postProcess
里使用ReadListener
则:
Callable<?>
事实上变成事先返回结果,而不是等ReadListener.onAllDataRead
或者onError
。CallableProcessingInterceptor.postProcess
必须阻塞ReadListener
和Callable<?>
无法存在通信机制。
spring.http.multipart.resolve-lazily=true
,否则被Multipart***事先消费掉inputStream中的内容。
curl -X POST -F "bigfile=@src/main/resources/bigfile" --limit-rate 10k http://localhost:8080/async-io-read-hook-pre-process
Tomcat 8.5.24及之前存在BUG 61932,会导致出现。。。。。。TODO。。。。。。
TODO