在Spring boot中实现一个servlet,需要再启动类加上@ServletComponentScan注解来扫描Servlet
1 | @WebServlet(urlPatterns = "/async",asyncSupported = true) |
异步Servlet的内部原理
上面主要是:final AsyncContext ctx = req.startAsync()和ctx.complete(),查看源码:
1 | public AsyncContext startAsync(ServletRequest request, |
req.startAsync()只是保存了一个异步上下文,同时设置一些基础信息,比如Timeout,这里设置的默认超时时间是30S,如果你的异步处理逻辑超过30S,此时执行ctx.complete()就会抛出IllegalStateException 异常。
1 | public void complete() { |
所以,这里最终会调用AbstractEndpoint的processSocket方法,EndPoint是用来接受和处理请求的,接下来就会交给Processor去进行协议处理。
1 | ///类:AbstractProcessorLight |
AbstractProcessorLight会根据SocketEvent的状态来判断是不是要去调用service(socketWrapper),该方法最终会去调用到容器,从而完成业务逻辑的调用,我们这个请求是执行完成后调用的,肯定不能进容器了,不然就是死循环了,这里通过isAsync()判断,就会进入dispatch(status),最终会调用CoyoteAdapter的asyncDispatch方法。
1 | public boolean asyncDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res, |
ctx.complete()执行最终的方法了,完成了数据的输出,最终输出到浏览器。
第一次doGet请求执行完成后,Tomcat是怎么知道不用返回到客户端的呢?关键代码在CoyoteAdapter中的service方法,部分代码如下:
1 | postParseSuccess = postParseRequest(req, request, res, response); |
调用完Servlet后,会通过request.isAsync()来判断是否是异步请求,如果是异步请求,就设置async = true。如果是非异步请求就执行输出数据到客户端逻辑,同时销毁request和response。这里就完成了请求结束后不响应客户端的操作。
Spring Boot的@EnableAsync注解不是异步Servlet
从业务层面来说,确实是异步编程,但是有一个问题,抛开业务的并行处理来说,针对整个请求来说,并不是异步的,也就是说不能立即释放Tomcat的线程,从而不能达到异步Servlet的效果。为什么它不是异步的。
1 | @RestController |
这里我请求之后,在调用容器执行业务逻辑之前打了一个断点,然后在返回之后的同样打了一个断点,在Controller执行完之后,请求才回到了CoyoteAdapter中,并且判断request.isAsync(),根据图中看到,是为false,那么接下来就会执行request.finishRequest()和response.finishResponse()
来执行响应的结束,并销毁请求和响应体。很有趣的事情是,我实验的时候发现,在执行request.isAsync()之前,浏览器的页面上已经出现了响应体,这是SpringBoot框架已经通过StringHttpMessageConverter类中的writeInternal方法已经进行输出了。
以上分析的核心逻辑就是,Tomcat的线程执行CoyoteAdapter调用容器后,必须要等到请求返回,然后再判断是否是异步请求,再处理请求,然后执行完毕后,线程才能进行回收。而我一最开始的异步Servlet例子,执行完doGet方法后,就会立即返回,也就是会直接到request.isAsync()的逻辑,然后整个线程的逻辑执行完毕,线程被回收。