源码基于SpringMVC 5.2.7
SpringMVC在执行完业务方法后要对其返回值进行处理,这项工作是在返回值处理器完成的。在SpringMVC支持的返回值处理器中有多种处理器会触发SpringMVC的WebAsyncManager机制,例如DeferredResultMethodReturnValueHandler、CallableMethodReturnValueHandler、AsyncTaskMethodReturnValueHandler等。今天我们聊聊WebAsyncManager机制。
WebAsyncManager主要用来管理异步请求。日常业务开发中难免遇到业务处理比较耗时的情况,例如某个sql很慢。这种时候如果还用同步的请求返回,那么只要业务未处理完,server线程就被占用。可能会导致server线程池耗尽,影响其他请求进来。这种时候WebAsyncManager就能排上用场。它可以让http server释放server线程同时保持response是open的状态,当业务处理完后再往response写回数据。
一般情况,跟WebAsyncManager机制配合使用的是业务线程池。请求到达server,由server线程处理请求,需求异步处理时启动一个业务线程处理。原来的server线程就迅速结束运行并被释放到server线程池。此时response是open状态。业务线程处理完业务后将数据写回到response。这样的配合其实没有真正解决阻塞的问题。只是能保证server线程能快速回到server线程池去接待其他请求。业务还是要在另一个业务线程处理。不过,至少它能确保其他的请求能够进来并且得到处理,只要这个请求不使用同一个业务线程池。真正解决阻塞性问题得在阻塞的各个环节使用上响应式编程。
servlet对异步请求的支持
异步请求的关键是servlet容器在server线程返回的保持response是open状态。所以异步请求离不开servlet容器的支持。servlet规范在3.0的版本中规定了对异步请求的支持。servlet对异步请求的支持主要体现两个接口上javax.servlet.ServletRequest和javax.servlet.AsyncContext。
在javax.servlet.ServletRequest接口规范中增加了以下方法
AsyncContext startAsync()
使request进入异步模式并用原始的(未包装)request和response初始化AsyncContext对象,同时使关联的response处于open状态直到调用AsyncContext#complete或超时。
AsyncContext startAsync(ServletRequest, ServletResponse)
是request进入异步模式并用给定的request和response初始化AsyncContext对象,同时使关联的response处于open状态直到调用syncContext#complete或超时。给定的request和response必须是当前request及response的本身或者其包装对象(ServletRequestWrapper、ServletResponseWrapper)。
boolean isAsyncStarted()
检查当前request是否进入异步模式。当request调用了startAsync且未被分发(AsyncContext#dispatch)或者未释放异步模式(AsyncContext#complete)时返回true。
boolean isAsyncSupported()
检查request是否支持异步模式。如果请求在不支持异步模式(asyncSupported=true表示支持异步,默认不支持)的servlet或者filter中,则请求的异步模式将关闭
AsyncContext getAsyncContext()
返回请求关联的AsyncContext对象。该对象由最近一次调用startAsync创建或重写初始化。
javax.servlet.AsyncContext
void dispatch()
分发当前request到sevlet容器。如果启动异步模式是通过startAysnc(ServletRequest, ServletResponse),分发的路径是参数ServletRequest的RequestURL。如果是通过startAysnc()启动,则分发的路径是当前请求上一次分发的路径。
void dispatch(String path)
指定分发路径分发请求
void dispatch(ServletContext context, String path)
指定分发路径分发请求,并且路径在指定的ServletContext范围内。
void complete()
完成异步操作,关闭response。
SpringMVC中异步请求
SpringMVC提供了WebAsyncManager对异步请求进行管理和流程控制,极大的方便了开发者使用异步请求。
WebAsyncManager
先来看看WebAsyncManager几个主要的成员变量,
asyncWebRequest:org.springframework.web.context.request.async.StandardServletAsyncWebRequest类型,包装了真实的请求,asyncWebRequest本身也是异步请求的监听器
taskExecutor:任务执行器,包装了线程池
callableInterceptors:CallableProcessing异步流程的拦截器
deferredResultInterceptors:DeferredResultProcessing异步流程的拦截器
WebAsyncManager支持两种形式的异步请求管理:
CallableProcessing
这种方式是业务提交一个Callable任务,由WebAsyncManager启动线程执行任务。任务执行结束,WebAsyncManager将请求分发到servlet container。源码WebAsyncManager#startCallableProcessing,其过程如下:
1 如果任务有超时时间,设置异步请求超时时间
2 如果任务自带线程池,设置WebAsyncManager的任务线程池
3 构建拦截器链CallableInterceptorChain:拦截器来源任务自带、开发者配置、默认超时拦截器
4 给asyncWebRequest挂上超时、错误、完成事件处理器:处理器是lambda表达式执行CallableInterceptorChain中相关逻辑
5 开启异步模式,并将asyncWebRequest设置为异步请求的事件监听器
6 启动执行任务
6.1 执行CallableInterceptorChain的预处理
6.2 执行任务java.util.concurrent.Callable#call
6.3 执行CallableInterceptorChain的后处理
6.4 分发请求到servlet container,如果任务处理已经关闭异步请求则不会分发
public final class WebAsyncManager {
... ...
public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext)
throws Exception {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = webAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
AsyncTaskExecutor executor = webAsyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
else {
logExecutorWarning();
}
//笔者注:组装拦截器
List<CallableProcessingInterceptor> interceptors = new ArrayList<>();
interceptors.add(webAsyncTask.getInterceptor());
interceptors.addAll(this.callableInterceptors.values());
interceptors.add(timeoutCallableInterceptor);
final Callable<?> callable = webAsyncTask.getCallable();
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);
//笔者注:给asyncWebRequest挂上超时、错误、完成处理器,异步请求触发相应事件时执行
this.asyncWebRequest.addTimeoutHandler(() -> {
if (logger.isDebugEnabled()) {
logger.debug("Async request timeout for " + formatRequestUri());
}
Object result = interceptorChain.triggerAfterTimeout(this.asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
});
this.asyncWebRequest.addErrorHandler(ex -> {
if (!this.errorHandlingInProgress) {
if (logger.isDebugEnabled()) {
logger.debug("Async request error for " + formatRequestUri() + ": " + ex);
}
Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, ex);
result = (result != CallableProcessingInterceptor.RESULT_NONE ? result : ex);
setConcurrentResultAndDispatch(result);
}
});
this.asyncWebRequest.addCompletionHandler(() ->
interceptorChain.triggerAfterCompletion(this.asyncWebRequest, callable));
interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable);
//笔者注:开启异步请求模式
startAsyncProcessing(processingContext);
try {
//笔者注:启动任务执行
Future<?> future = this.taskExecutor.submit(() -> {
Object result = null;
try {
interceptorChain.applyPreProcess(this.asyncWebRequest, callable);
result = callable.call();
}
catch (Throwable ex) {
result = ex;
}
finally {
result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, result);
}
setConcurrentResultAndDispatch(result);
});
interceptorChain.setTaskFuture(future);
}
catch (RejectedExecutionException ex) {
Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
setConcurrentResultAndDispatch(result);
throw ex;
}
}
.......
}
DeferredResultProcessing
这种方式是业务返回org.springframework.web.context.request.async.DeferredResult类型的对象。WebAsyncManager给DeferredResult设置resultHandler,当DeferredResult完成触发resultHandler执行,分发请求到servlet container。源码WebAsyncManager#startDeferredResultProcessing,其过程跟CallableProcessing差不多,区别在于DeferredResultProcessing不需要启动线程执行任务。
1 如果任务有超时时间,设置异步请求超时时间
3 构建拦截器链DeferredResultInterceptorChain:拦截器来源任务自带、开发者配置、默认超时拦截器
4 给asyncWebRequest挂上超时、错误、完成事件处理器:处理器是lambda表达式执行DeferredResultInterceptorChain中相关逻辑
5 开启异步模式,并将asyncWebRequest设置为异步请求的事件监听器
6 执行DeferredResultInterceptorChain的预处理
7 给DeferredResult挂上resultHandler
7.1 执行DeferredResultInterceptorChain的后处理
7.2 分发请求到servlet container,如果任务处理已经关闭异步请求则不会分发
public final class WebAsyncManager {
... ...
public void startDeferredResultProcessing(
final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
//笔者注:设置超时时间
Long timeout = deferredResult.getTimeoutValue();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
//笔者注:组装拦截器
List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<>();
interceptors.add(deferredResult.getInterceptor());
interceptors.addAll(this.deferredResultInterceptors.values());
interceptors.add(timeoutDeferredResultInterceptor);
final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
//笔者注:给asyncWebRequest挂上超时、错误、完成处理器,异步请求触发相应事件时执行
this.asyncWebRequest.addTimeoutHandler(() -> {
try {
interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult);
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
});
this.asyncWebRequest.addErrorHandler(ex -> {
if (!this.errorHandlingInProgress) {
try {
if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) {
return;
}
deferredResult.setErrorResult(ex);
}
catch (Throwable interceptorEx) {
setConcurrentResultAndDispatch(interceptorEx);
}
}
});
this.asyncWebRequest.addCompletionHandler(()
-> interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult));
interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult);
startAsyncProcessing(processingContext);
try {
interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
//笔者注:给DeferredResult挂上result处理器,当DeferredResult有结果时触发handler执行,分发请求
deferredResult.setResultHandler(result -> {
result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);
setConcurrentResultAndDispatch(result);
});
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
}
... ...
}
SpringMVC异步请求开发
首先,DispatcherServlet在请求的入口处创建一个WebAsyncManager并设置在request的属性上
public class DispatcherServlet extends FrameworkServlet {
... ...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
... ...
//笔者注:获取请求上的关联的的WebAsyncManager对象,没有则创建一个关联上去
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
... ...
}
... ...
}
//笔者注:获取请求上的关联的的WebAsyncManager对象,没有则创建一个关联上去
public abstract class WebAsyncUtils {
... ...
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
WebAsyncManager asyncManager = null;
Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
if (asyncManagerAttr instanceof WebAsyncManager) {
asyncManager = (WebAsyncManager) asyncManagerAttr;
}
if (asyncManager == null) {
asyncManager = new WebAsyncManager();
servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
}
return asyncManager;
}
... ...
}
其次,请求处理方法执行完,SpringMVC对返回值处理,在某些返回类型的处理器中开启异步模式。会触发开启异步模式的返回类型有java.util.concurrent.Callable或org.springframework.web.context.request.async.DeferredResult或org.springframework.web.context.request.async.WebAsyncTask等等,具体哪些处理器支持参考SpringMVC的返回处理器。以CallableMethodReturnValueHandler为例
public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
... ...
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
Callable<?> callable = (Callable<?>) returnValue;
//笔者注:开启异步模式
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
}
... ...
}
再次,Dispatcher执行请求处理逻辑后,如果返回处理器开启了异步模式,则直接返回并不会执行后续的视图渲染等逻辑
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
... ...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//笔者注:异步模式开启则后续处理不处理 直接返回以释放server线程
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
... ...
}
}
然后,异步处理有结果以后,执行分发请求到servlet container。如果是Callable类型,当Callable#call执行完即分发;如果是DeferredResult,当DeferredResult的result被设置值后触发分发请求。具体逻辑前面介绍过。分发请求的目标URL不变,还是当前的URL。那么问题来了,既然是同一个URL,servlet container又会将请求转发到DispatcherServlet,这样不会造成死循环吗?往下继续分析。
再然后,分发的请求重新进入DispatcherServlet,随之也会进入RequestMappingHandlerAdapter处理。那么关键的地方来了,RequestMappingHandlerAdapter识别request关联的WebAsyncManager对象是否有结果,如果有结果将请求处理方法替换为Callable的匿名实现类对象,这个对象直接将结果返回,而不真正调用业务处理方法。这样就不会循环下去!
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
... ...
//笔者注:如果asyncManager 有结果
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
// 笔者注:Callable的匿名类包装result
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
... ...
}
}
看下ServletInvocableHandlerMethod#wrapConcurrentResult就是创建一个ConcurrentResultHandlerMethod对象。ConcurrentResultHandlerMethod是ServletInvocableHandlerMethod的子类也是它的内部类,其构造函数就是创建一个Callable的匿名类对象,这个对象的call方法实现就是返回result
private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
private final MethodParameter returnType;
public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
//笔者注:这个创建一个callable的匿名类,该类的call方法直接返回result,CALLABLE_METHOD是Callable接口call方法的反射
super((Callable<Object>) () -> {
if (result instanceof Exception) {
throw (Exception) result;
}
else if (result instanceof Throwable) {
throw new NestedServletException("Async processing failed", (Throwable) result);
}
return result;
}, CALLABLE_METHOD);
if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {
setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
}
this.returnType = returnType;
}
/**
* Bridge to actual controller type-level annotations.
*/
@Override
public Class<?> getBeanType() {
return ServletInvocableHandlerMethod.this.getBeanType();
}
/**
* Bridge to actual return value or generic type within the declared
* async return type, e.g. Foo instead of {@code DeferredResult<Foo>}.
*/
@Override
public MethodParameter getReturnValueType(@Nullable Object returnValue) {
return this.returnType;
}
/**
* Bridge to controller method-level annotations.
*/
@Override
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
}
/**
* Bridge to controller method-level annotations.
*/
@Override
public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
return ServletInvocableHandlerMethod.this.hasMethodAnnotation(annotationType);
}
}
最后,Callable的匿名类对象返回结果后,后面的逻辑跟其他的请求一致了:异常处理、渲染视图、数据返回客户端等等。这样整个异步请求的流程就处理完了。
总结
本文主要讲了SpringMVC对异步请求的处理。主要涉及以下几点
- 1 异步请求的应用场景
- 2 servlet规范对异步请求的支持
- 3 SpringMVC管理异步请求的流程
目录 目录
上一篇 返回处理器HandlerMethodReturnValueHandler