Skip to content

Latest commit

 

History

History
143 lines (103 loc) · 8.23 KB

File metadata and controls

143 lines (103 loc) · 8.23 KB

Spring MVC Async IO

前一篇文章里介绍了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

WebAsyncManager

前一篇文章里提到过以下几种AsyncHandlerMethodReturnValueHandler

  1. CallableMethodReturnValueHandler
  2. DeferredResultMethodReturnValueHandler
  3. ResponseBodyEmitterReturnValueHandler
  4. StreamingResponseBodyReturnValueHandler

仔细看这些Handler的代码会发现最终都使用WebAsyncManager执行异步处理。

WebAsyncManager执行的大致动作有这么几个:

  1. 调用ServletRequest.startAsync()
  2. 使用AsyncTaskExecutor开启另一个线程执行任务
  3. 调用各种Interceptor的回调方法,比如CallableProcessingInterceptorDeferredResultProcessingInterceptor

下面以Callable<?>为例,详细讲解一下WebAsyncManager的执行过程(下面的T-*代表不同的线程):

  1. T-http-1: WebAsyncManager.startCallableProcessing
  2. T-http-1: CallableInterceptorChain.applyBeforeConcurrentHandling -> CallableProcessingInterceptor.beforeConcurrentHandling
  3. T-http-1: WebAsyncManager.startAsyncProcessing -> AsyncWebRequest.startAsync -> ServletRequest.startAsync
  4. T-http-1: AsyncTaskExecutor.submit(Callable)
  5. T-mvc-async: CallableInterceptorChain.applyPreProcess -> CallableProcessingInterceptor.preProcess
  6. T-mvc-async: Callable.run,等待执行完毕
  7. T-mvc-async: CallableInterceptorChain.applyPostProcess -> CallableProcessingInterceptor.postProcess
  8. T-mvc-async: WebAsyncManager.setConcurrentResultAndDispatch -> AsyncWebRequest.dispatch -> ServletRequest.dispatch
  9. T-http-2: DispatchServlet ...
  10. 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加到MVC异步处理过程

先来回顾一下使用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<?>必须阻塞。
  • ReadListenerCallable<?>需要有通信机制。
  • Callable<?>可以根据不同情况返回不同的结果。

如果在第7步CallableProcessingInterceptor.postProcess里使用ReadListener则:

  • Callable<?>事实上变成事先返回结果,而不是等ReadListener.onAllDataRead或者onError
  • CallableProcessingInterceptor.postProcess必须阻塞
  • ReadListenerCallable<?>无法存在通信机制。

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。。。。。。

将WriteListener加到MVC异步处理过程

TODO

相关资料