Tomcat异步Servlet实现分析——开启异步上下文
前言
异步servlet是servlet 3.0的新特性,具体的使用方式就不说了,我们主要看异步Servlet的代码实现。有两种场景:
- 可立即返回给请求方数据的。很简单不说了。
- 另外一些情况,需要等待处理结果,这个时候如果我们使用容器中的线程池,则线程池很快会耗尽。Servlet 3.0提供了异步支持,能够指示Respons在Servlet容器的线程退出之后保持开发状态。
源码
源码的入口在org.apache.catalina.connector.Request,先由一个请求开启异步(startAsync方法):
public AsyncContext startAsync(ServletRequest request,
ServletResponse response) {
if (!isAsyncSupported()) {
IllegalStateException ise =
new IllegalStateException(sm.getString("request.asyncNotSupported"));
log.warn(sm.getString("coyoteRequest.noAsync",
StringUtils.join(getNonAsyncClassNames())), ise);
throw ise;
}
if (asyncContext == null) {
asyncContext = new AsyncContextImpl(this);
}
asyncContext.setStarted(getContext(), request, response,
request==getRequest() && response==getResponse().getResponse());
asyncContext.setTimeout(getConnector().getAsyncTimeout());
return asyncContext;
}
注意这里的前半部分,需要判断是否支持异步,如果不支持就直接抛异常了,设置异步支持这里也不说了,我们看后面,初始化AsyncContextImpl,这个是异步线程上下文非常重要,后面我们细看其作用。
然后是设置超时时间(异步的请求设置超时时间这个没啥疑问),然后是设置开始状态(asyncContext.setStarted):
synchronized (asyncContextLock) {
this.request.getCoyoteRequest().action(
ActionCode.ASYNC_START, this);
this.context = context;
this.servletRequest = request;
this.servletResponse = response;
this.hasOriginalRequestAndResponse = originalRequestResponse;
this.event = new AsyncEvent(this, request, response);
List<AsyncListenerWrapper> listenersCopy = new ArrayList<>();
listenersCopy.addAll(listeners);
listeners.clear();
for (AsyncListenerWrapper listener : listenersCopy) {
try {
listener.fireOnStartAsync(event);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
}
}
这里我们可以看到,AsyncContextImpl里缓存了很多信息,包括请求、相应和上下文。
然后触发了AsyncListenerWrapper的fireOnStartAsync方法,这个AsyncListenerWrapper实际上是AsyncListener的包装类,我们可以通过AsyncContext.addListener设置。
那么这里我们可以看到,AsyncContextImpl的作用:主要是缓存了很多的请求的中间信息,如request,response,listener,dispatch对象,event,还有servlet实例化的instanceManager等,那么为什么需要缓存这些信息呢?
是因为Tomcat工作线程在Request.startAsync之后,把该异步servlet的后续代码执行完毕后,Tomcat工作线程直接就结束了,也就是返回线程池中了,相当于线程根本不会保存记录信息。
而我们这时的状态相当于一个请求到服务器了,业务正在执行,执行到一半,线程没了,这就需要至少有个缓存的地方,而这个缓存就是AsyncContextImpl。
再看这里:this.request.getCoyoteRequest().action(ActionCode.ASYNC_START, this);,这个设计到Tomcat的Http11Processor中的处理器来处理这个事件的:
// Servlet 3.0 asynchronous support
case ASYNC_START: {
asyncStateMachine.asyncStart((AsyncContextCallback) param);
break;
}
然后是Tomcat中的状态机,网上说,这个状态机是JAVA EE明确规定要实现的:在该类中,对Servlet出于异步环境中的状态进行监控和管理,对于每一个状态都有严格的要求,其下一个状态做什么,如果有兴趣可以查一下Servlet 3.1的规范,有图有说明。
我们继续看这个状态机如何处理:
if (state == AsyncState.DISPATCHED) {
state = AsyncState.STARTING;
this.asyncCtxt = asyncCtxt;
lastAsyncStart = System.currentTimeMillis();
} else {
throw new IllegalStateException(
sm.getString("asyncStateMachine.invalidAsyncState",
"asyncStart()", state));
}
就是设置状态和这个asyncCtxt,这个其实是AsyncContextImpl,这个类实现了AsyncContextCallback接口,拥有fireOnComplete方法。
到这里我们的开启异步上下文就结束了。