前面我们说了很多关于异步接口的实现,而且看的都是 SpringMVC 包装了一层的之后的实现,现在让我们抛开 SpringMVC 那一层包装再看看是怎样的。
好了,我们先看下没有了 SpringMVC 包装的异步实现代码:
1 |
|
上面代码中其实主要是看从接收到请求,servletRequest 调用 startAsync 方法得到 asyncContext 实例,然后在业务线程池中调用 service 层的方法执行得到结果,再通过 asyncContext 拿到 response 将结果写入响应,最后在 finally 代码块里面执行 complete 方法,整个请求结束。
整个执行顺序如下:
- 客户端发送请求
- servlet 容器分配一个线程处理请求
- servlet request 调用 startAsync,得到 asyncContext
- 容器线程退出,但 response 保持打开
- 业务线程使用 asyncContext 来完成响应
- 客户端收到请求响应
上面能够实现异步的前提是 servlet 引入了对异步的支持,对异步处理的支持是在 servlet 3.0 之后 引入的,其实也已经很久了,之前都不知道这个特性,主要是我们平常一个请求从头到尾由同一个线程来处理的这种线程模型基本上就能满足大部分的需求了。
上面的执行效果和之前的返回结果是 Callable,DeferredResult 大体上是一样的,只不过 SpringMVC 帮我们包装了,根据接口的返回值来决定是否开启异步请求,不需要自己处理响应信息,同时也让我们更方便的实现我们的业务代码而已,但其实底层同样也是通过 servletRequest 拿到 asyncContext 来实现的,我之前的文章中在截图中其实也标记了一下,不过当时没提,主要是想单独用一篇文章来说明。
再回到上面那张图,可以看到异步的实现不可避免的会涉及到线程间频繁的切换上下文,但是如果这样也带来了其他的好处,比如说可以处理的并发连接数更多,请求的处理和业务的处理分开,同时可以自定义业务线程池,更好地监控执行状况。
最后我还想说一下在使用这个异步特性的过程中需要注意的一些地方:
- 首先由于并不是像普通的一个请求一个线程处理直到结束,所以 ThreadLocal 保存的信息并不能在业务线程中获取。
- 如果实现了自己的 Filter,记得一定加上 asyncSupported=true,否则会报错
1 |
|
- 如果需要实现拦截器,可以实现 AsyncHandlerInterceptor 接口,里面 afterConcurrentHandlingStarted 方法在异步线程执行前会回调。
- 实现 AsyncHandlerInterceptor 接口,接口中 preHandle 会执行两次,要注意实现业务逻辑幂等性。
文中代码地址:
https://github.com/rookiedev-z/sample/tree/master/async-controller-sample
参考:
https://docs.oracle.com/javaee/7/tutorial/servlets012.htm
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support