Spring MVC系列之异步请求

概述

Spring MVC的本质其实就是一个Servlet。在理解Spring MVC如何支持异步请求之前,需要先知道Servlet3异步如何支持异步请求。参考Servlet系列之Servlet3异步

Spring MVC对异步请求的支持主要从三个类来看:

  • AsyncWebRequest:request
  • WebAsyncManager:处理异步请求的管理器
  • WebAsyncUtils:工具类

Spring MVC将异步请求细分为Callable、WebAsyncTask、DeferredResult三种类型,前两种是一类,核心是Callable。

组件

Spring MVC中异步请求涉及到的相关组件如下。

DeferredResult

Spring提供的一种用于保存延迟处理结果的泛型类,当一个处理器返回DeferredResult类型的返回值时将启动异步处理。

org.springframework.web.context.request.async.DeferredResult源码,版本为spring-web-6.1.5

java 复制代码
public class DeferredResult<T> {
	private static final Object RESULT_NONE = new Object();
	@Nullable
	private final Long timeoutValue;
	private final Supplier<?> timeoutResult;
	@Nullable
	private Runnable timeoutCallback;
	@Nullable
	private Consumer<Throwable> errorCallback;
	@Nullable
	private Runnable completionCallback;
	@Nullable
	private DeferredResultHandler resultHandler;
	@Nullable
	private volatile Object result = RESULT_NONE;
	private volatile boolean expired;
}

AsyncRestTemplate

模板类方法,位于org.springframework.web.client包路径下,引入spring-web模块即可使用,但自Spring 5.0版本就被标记为@Deprecated。替换类为org.springframework.web.reactive.function.client.WebClient

WebClient

Spring 5新增的非阻塞、响应式HTTP客户端,更适合于异步请求和响应处理。

AsyncWebRequest

org.springframework.web.context.request.async.AsyncWebRequest源码:

java 复制代码
public interface AsyncWebRequest extends NativeWebRequest {
	void setTimeout(@Nullable Long timeout);
	// 添加请求超时处理器
	void addTimeoutHandler(Runnable runnable);
	// 添加错误处理器
	void addErrorHandler(Consumer<Throwable> exceptionHandler);
	// 添加请求处理完成处理器
	void addCompletionHandler(Runnable runnable);
	void startAsync();
	// 判断是否启动异步处理
	boolean isAsyncStarted();
	void dispatch();
	// 判断异步处理是否已经处理完成
	boolean isAsyncComplete();
}

实现类有两个:

  • NoSupportAsyncWebRequest:不支持异步请求
  • StandardServletAsyncWebRequest:实际用作异步请求。除实现AsyncWebRequest接口外,还实现AsyncListener接口并继承ServletWebRequest

StandardServletAsyncWebRequest源码略,封装AsyncContext类型的属性asyncContext,在startAsync方法中会将Request#startAsync返回的AsyncContext设置给它,然后在别的地方主要使用它来完成各种功能。由于StandardServletAsyncWebRequest实现AsyncListener接口,所以它自己就是一个监听器,而且在startAsync方法中在创建出AsyncContext后会将自己作为监听器添加进去。监听器实现方法中onStartAsync方法和onError方法是空实现,onTimeout方法和onComplete方法分别调用封装的两个List<Runnable>类型的属性timeoutHandlers和completionHandlers所保存的Runnable方法,这样在使用时只需要简单地将需要监听超时和处理完成的监听方法添加到这两个属性中即可。

CallableProcessingInterceptor

拦截器接口,6个方法:

java 复制代码
default <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
}
default <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
}
default <T> void postProcess(NativeWebRequest request, Callable<T> task,
		@Nullable Object concurrentResult) throws Exception {
}
default <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
	return RESULT_NONE;
}
default <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
	return RESULT_NONE;
}
default <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
}

拦截器的作用:在不同的时间点通过执行相应的方法来做一些额外的事情,理解拦截器的核心是理解它里边的各个方法执行的时间点。beforeConcurrentHandling方法是在并发处理前执行的,会在主线程中执行,其他方法都在具体处理请求的子线程中执行。

DeferredResultProcessingInterceptor

拦截器接口,6个方法,命名和上面一模一样。

CallableInterceptorChain

CallableInterceptorChain用于封装CallableProcessingInterceptor,将多个相应的拦截器封装到一个List类型的属性,然后在相应的方法中调用所封装的Interceptor相应方法进行处理。责任链模式。方法名与Interceptor中稍有区别,对应关系如下:

  • applyBeforeConcurrentHandling:对应Interceptor中的beforeConcurrentHandling方法
  • applyPreProcess:对应Interceptor中的preProcess方法
  • applyPostProcess:对应Interceptor中的postProcess方法
  • triggerAfterTimeout:对应Interceptor中的afterTimeout方法
  • triggerAfterCompletion:对应Interceptor中的afterCompletion方法
  • triggerAfterError:对应Interceptor中的handleError方法

DeferredResultInterceptorChain

同上,用于封装DeferredResultProcessingInterceptor。

WebAsyncTask

WebAsyncTask是一个泛型类,封装Callable方法,并提供一些异步调用相关的属性:

java 复制代码
public class WebAsyncTask<V> implements BeanFactoryAware {
	// 用来实际处理请求
	private final Callable<V> callable;
	// 用于设置超时时间
	@Nullable
	private final Long timeout;
	// 用来调用callable
	@Nullable
	private final AsyncTaskExecutor executor;
	// 用容器中注册的名字配置executor
	@Nullable
	private final String executorName;
	// 用于根据名字获取executor
	@Nullable
	private BeanFactory beanFactory;
	// 用于执行超时的回调
	@Nullable
	private Callable<V> timeoutCallback;
	// 用于发生错误的回调
	@Nullable
	private Callable<V> errorCallback;
	// 用于请求处理完成的回调
	@Nullable
	private Runnable completionCallback;
}

executor可以直接设置到WebAsyncTask中,也可使用注册在容器中的名字来设置executorName属性:

java 复制代码
@Nullable
public AsyncTaskExecutor getExecutor() {
	if (this.executor != null) {
		return this.executor;
	} else if (this.executorName != null) {
		Assert.state(this.beanFactory != null, "BeanFactory is required to look up an executor bean by name");
		return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);
	} else {
		return null;
	}
}

WebAsyncManager

Spring MVC处理异步请求过程中最核心的类,管理着整个异步处理的过程。

几个重要属性:

  • timeoutCallableInterceptor:CallableProcessingInterceptor类型,专门用于Callable和WebAsyncTask类型超时的拦截器
  • timeoutDeferredResultInterceptor:DeferredResultProcessingInterceptor类型,专门用于DeferredResult和ListenableFuture类型超时的拦截器
  • callableInterceptors:Map类型,用于所有Callable和WebAsyncTask类型的拦截器
  • deferredResultInterceptors:Map类型,用于所有DeferredResult和ListenableFuture类型的拦截器
  • asyncWebRequest:为了支持异步处理而封装的request
  • taskExecutor:用于执行Callable和WebAsyncTask类型处理,如果WebAsyncTask中没有定义executor则使用WebAsyncManager中的taskExecutor。

最重要的两个方法是startCallableProcessing和startDeferredResultProcessing,是启动异步处理的入口方法。它们一共做三件事:

  • 启动异步处理;
  • 给Request设置相应属性(主要包括timeout、timeoutHandler和completionHandler);
  • 在相应位置调用相应的Spring MVC自定义的拦截器。

startCallableProcessing方法用于处理Callable和WebAsyncTask类型的异步请求,使用CallableProcessingInterceptor,拦截器封装在CallableInterceptorChain类型的拦截器链中统一调用。

startDeferredResultProcessing方法用于处理DeferredResult类型的异步请求,使用DeferredResultProcessingInterceptor拦截器,拦截器封装在DeferredResultInterceptorChain类型的拦截器链中统一调用。和startCallableProcessing方法执行过程类似,只是并没有使用taskExecutor来提交执行,这是因为DeferredResult并不需要执行处理。

startCallableProcessing方法主要做5件事:

  • 将webAsyncTask中相关属性取出并设置到对应的地方;
  • 初始化拦截器链;
  • 给asyncWebRequest设置timeoutHandler和completionHandler;
  • 执行处理器链中相应方法;
  • 启动异步处理并使用taskExecutor提交任务。

启动处理是调用startAsyncProcessing方法,源码如下:

java 复制代码
private void startAsyncProcessing(Object[] processingContext) {
	synchronized (WebAsyncManager.this) {
		this.concurrentResult = RESULT_NONE;
		this.concurrentResultContext = processingContext;
	}
	Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
	this.asyncWebRequest.startAsync();
}

做3件事:

  • 清空之前并发处理的结果
  • 将processingContext设置给concurrentResultContext属性
  • 调用asyncWebRequest的startAsync方法启动异步处理

processingContext参数传进来的是处理器中使用的ModelAndViewContainer,concurrent-ResultContext用来在WebAsyncManager中保存ModelAndViewContainer,在请求处理完成后会设置到RequestMappingHandlerAdapter中。

执行处理,执行处理使用的是taskExecutor,这里并没直接使用taskExecutor.submit(callable)来提交,而是提交新建的Runnable,并将Callable的call方法直接放在run方法里调用:

java 复制代码
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 (Throwable ex) {
	Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
	setConcurrentResultAndDispatch(result);
}

这么做主要有两个作用:

  • 可以在处理过程中的相应位置调用拦截器链中相应的方法;
  • 在call方法执行完之前不会像Future#get()那样阻塞线程。

Runnable是没有返回值的,所以Callable处理的结果需要自己从run方法内部传递出来,WebAsyncManager.setConcurrentResultAndDispatch方法来处理返回的结果,这里边会将处理的结果传递出来:

java 复制代码
// 省略日志打印
private void setConcurrentResultAndDispatch(@Nullable Object result) {
	// 检查asyncWebRequest和状态
	Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
	synchronized (WebAsyncManager.this) {
		if (!this.state.compareAndSet(State.ASYNC_PROCESSING, State.RESULT_SET)) {
			return;
		}
		// 设置异步处理结果
		this.concurrentResult = result;
		// 检查Request是否已设置为异步处理完成状态(网络中断会造成Request设置为异步处理完成状态)
		if (this.asyncWebRequest.isAsyncComplete()) {
			return;
		}
		// 发送请求
		this.asyncWebRequest.dispatch();
	}
}

concurrentResult用来保存异步处理结果的属性。Spring MVC中异步请求处理完成后会再次发起一个相同的请求,然后在HandlerAdapter中使用一个特殊的HandlerMethod来处理它,具体过程后面再讲解,不过通过Request的dispatch方法发起的请求使用的还是原来的Request,也就是说原来保存在Request中的属性不会丢失。

WebAsyncUtils

源码省略。两个重载的getAsyncManager方法通过Request获取WebAsyncManager,分别是ServletRequest、WebRequest类型的Request,获取过程都是先判断Request属性里是否有保存的WebAsyncManager对象,如果有则取出后直接返回,如果没有则新建一个设置到Request的相应属性中并返回,下次再获取时直接从Request属性中取出。

createAsyncWebRequest方法用于创建AsyncWebRequest,调用上面提到的getAsyncManager方法获取WebAsyncManager,然后获取AsyncWebRequest。进而创建StandardServletAsyncWebRequest类型的Request并返回。

原理

Spring MVC对异步请求的处理主要在四个地方进行支持:

  • FrameworkServlet中给当前请求的WebAsyncManager添加CallableProcessingInterceptor类型的拦截器RequestBindingInterceptor,这是定义在FrameworkServlet内部的私有拦截器,其作用还是跟FrameworkServlet处理正常请求一样,在请求处理前将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder中,并在请求处理完成后恢复,添加过程在processRequest方法中
  • RequestMappingHandlerAdapter的invokeHandleMethod方法提供对异步请求的核心支持,其中做四件跟异步处理相关的事情,下文详述
  • 返回值处理器:一共有四个处理异步请求的返回值处理器,它们分别是AsyncTaskMethodReturnValueHandler、CallableMethodReturnValueHandler、DeferredResultMethodReturnValueHandler和ListenableFutureReturnValueHandler,每一个对应一种类型的返回值,作用主要是使用WebAsyncManager启动异步处理
  • 在DispatcherServlet的doDispatch方法中,当HandlerAdapter使用Handler处理完请求时,会检查是否已经启动异步处理,如果启动则不再往下处理,直接返回

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter.invokeHandleMethod()方法源码略,四件事:

  • 创建AsyncWebRequest并设置超时时间,具体时间可以通过asyncRequestTimeout属性配置到RequestMappingHandlerAdapter中。
  • 对当前请求的WebAsyncManager设置了四个属性:taskExecutor、asyncWebRequest、callableInterceptors和deferredResultInterceptors,除了asyncWebRequest的另外三个都可以在RequestMappingHandlerAdapter中配置,taskExecutor如果没配置将默认使用MvcSimpleAsyncTaskExecutor(继承自SimpleAsyncTaskExecutor)。
  • 如果当前请求是异步请求而且已经处理出结果,则将异步处理结果与之前保存到WebAsyncManager里的ModelAndViewContainer取出来,并将WebAsyncManager里的结果清空,然后调用ServletInvocableHandlerMethod的wrapConcurrentResult方法创建ConcurrentResultHandlerMethod类型(ServletInvocableHandlerMethod内部类)的ServletInvocable-HandlerMethod来替换自己,创建出来的ConcurrentResultHandlerMethod并不执行请求,它的主要功能是判断异步处理的结果是不是异常类型,如果是则抛出,如果不是则使用ReturnValueHandler对其进行解析并返回。
  • 如果requestMappingMethod的invokeAndHandle方法执行完后检查到当前请求已经启动了异步处理,则会直接返回null。

调用ServletInvocableHandlerMethod的wrapConcurrentResult方法创建新的ServletInvocableHandlerMethod来处理异步处理的结果。ConcurrentResultHandlerMethod是在ServletInvocableHandlerMethod中定义的继承自ServletInvocableHandlerMethod的内部类:

java 复制代码
private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
	private final MethodParameter returnType;

	public ConcurrentResultHandlerMethod(@Nullable Object result, ConcurrentResultMethodParameter returnType) {
		super((Callable<Object>) () -> {
			if (result instanceof Exception exception) {
				throw exception;
			} else if (result instanceof Throwable throwable) {
				throw new ServletException("Async processing failed: " + result, throwable);
			}
			return result;
		}, CALLABLE_METHOD);
		if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {
			setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
		}
		this.returnType = returnType;
	}
	// 省略4个方法
}

ConcurrentResultHandlerMethod调用父类的构造方法(super)将HandlerMethod中的Handler和Method都替换掉,Handler用新建的匿名Callable,Method使用ServletInvocableHandlerMethod的静态属性CALLABLE_METHOD,它代码Callable的call方法。新建的Callable的执行逻辑也非常简单,就是判断异步处理的返回值是不是异常类型,如果是则抛出异常,不是则直接返回,然后使用和原来请求一样的返回值处理器处理返回值(因为在构造方法中将原来ServletInvocableHandlerMethod的返回值处理器设置给自己)。

流程

主要处理流程是这样的:首先在处理器中返回需要启动异步处理的类型时(三种类型)相应返回值处理器会调用WebAsyncManager的相关方法启动异步处理,然后在DispatcherServlet中将原来请求直接返回,当异步处理完成后会重新发出一个相同的请求,这时在RequestMappingHandlerAdapter中会使用特殊的ServletInvocableHandlerMethod来处理请求,处理方法是:如果异步处理返回的结果是异常类型则抛出异常,否则直接返回异步处理结果,然后使用返回值处理器处理,接着返回DispatcherServlet中按正常流程往下处理。

异步处理完成后会重新发起一个请求,这时会重新查找HandlerMethod并初始化PathVariable、MatrixVariable等参数,重新初始化Model中的数据并再次执行HandlerInterceptor中相应的方法。这么做主要是可以复用原来的那套组件进行处理而不需要重新定义。不过新请求的HandlerMethod是用的专门的类型,而Model是使用的原来保存在WebAsyncManager的concurrentResultContext属性中的ModelAndViewContainer所保存的Model,所以这里的查找HandlerMethod和初始化Model的过程是没用的,可进行一些优化,如,将创建ConcurrentResultHandlerMethod的过程放在HandlerMapping中(这样也更符合组件的功能),然后在调用ModelFactory的initModel方法前判断是不是异步处理dispatcher过来的请求,如果是则不再初始化,或者干脆创建新的HandlerAdapter来处理。

返回

当处理器方法返回WebAsyncTask或Callable类型时将自动启用异步处理。

当处理器方法返回WebAsyncTask类型的返回值时,Spring MVC使用AsyncTaskMethodReturnValueHandler来加以处理:

java 复制代码
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	if (returnValue == null) {
		mavContainer.setRequestHandled(true);
		return;
	}
	WebAsyncTask<?> webAsyncTask = (WebAsyncTask<?>) returnValue;
	if (this.beanFactory != null) {
		webAsyncTask.setBeanFactory(this.beanFactory);
	}
	WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(webAsyncTask, mavContainer);
}

如果返回值为null,就会给mavContainer设置为请求已处理,然后返回。如果返回值不为null,调用WebAsyncManager的startCallableProcessing方法处理请求。WebAsyncManager是使用WebAsyncUtils获取的。

当返回Callable类型时,使用CallableMethodReturnValueHandler来处理,源码略。

参考

  • 看透Spring MVC:源码分析与实践
相关推荐
初晴~4 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
雷神乐乐6 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding6 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
文大。7 小时前
2024年广西职工职业技能大赛-Spring
java·spring·网络安全
小马爱打代码8 小时前
Spring Boot 中 Map 的最佳实践
java·spring boot·spring
智慧老师13 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
hanbarger16 小时前
mybatis框架——缓存,分页
java·spring·mybatis
龙少954319 小时前
【深入理解@EnableCaching】
java·后端·spring
啦啦右一1 天前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
荆州克莱1 天前
mysql中局部变量_MySQL中变量的总结
spring boot·spring·spring cloud·css3·技术