【Spring】抽丝剥茧SpringMVC-异步请求WebAsyncManager

源码基于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

下一篇 异常处理器HandlerExceptionResolver

再下一篇 视图解析及渲染ViewResolver&View

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个网上书城项目采用了当前最流行的框架spring-springmvc-mybatis设计。这个项目旨在提供一个方便、快捷的购书平台,用户可以通过该平台浏览、搜索并购买自己喜欢的图书。 在这个项目中,我们使用了Spring作为项目的核心框架,它提供了依赖注入和面向切面编程等功能,使得项目的开发更加简洁和易于维护。Spring MVC作为项目的Web框架,负责将用户的请求路由到对应的Controller,并负责视图的渲染和返回。而MyBatis则是作为持久层框架,将数据库的操作和Java代码的实现解耦,提供了灵活的SQL映射和缓存机制。 在这个网上书城项目中,我们设计了一套完整的功能模块:用户管理模块、图书管理模块、订单管理模块等。用户可以通过注册、登录等功能来进行用户管理,并可以在平台上对图书进行购买、收藏等操作。同时,平台还提供了搜索功能,用户可以根据图书的名称、作者等进行快速查找。 这个项目的设计更加便于扩展和维护,使用了分层架构,将业务逻辑、持久层和展示层进行了有效的分离。同时,也采用了面向接口编程的思想,降低了模块之间的耦合度,提高了代码的复用性和可测试性。 总之,这个网上书城项目基于spring-springmvc-mybatis框架的设计,充分利用了各自的优势,提供了一个高效、稳定和易于维护的购书平台。期望能为用户提供良好的购书体验,并为图书销售行业的发展做出贡献。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值