在介绍springMVC请求参数以及返回值解析时了解到几个返回值解析器支持异步处理请求,决定深入的研究一下。
在了解异步处理之前,我觉得有必要先了解Servlet3.0新增的特性。
- 传统Servlet处理
Web容器会为每个请求分配一个线程,默认情况下,响应完成前,该线程占用的资源都不会被释放。若有些请求需要长时间(例如长处理时间运算、等待某个资源),就会长时间占用线程所需资源,若这类请求很多,许多线程资源都被长时间占用,会对系统的性能造成负担。- Servlet 3.0新增了异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求,其响应将被延后,可以在处理完成(例如长时间运算完成、所需资源已获得)时再对客户端进行响应。
Servlet 3.0接收请求后步骤:
- Servlet 接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;
- Servlet 线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,
- Servlet 还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有 ServletRequest 和 ServletResponse 对象的引用),或者将请求继续转发给其它 Servlet。
而之后介绍的所有支持异步处理的返回值处理器,在真正业务处理逻辑之前,都调用了AsyncWebRequest#startAsync方法,这个方法正是对Servlet3.0异步处理新特性的实践。
支持请求异步处理的返回值解析器
- StreamingResponseBodyReturnValueHandler
/**
* @Description: 支持返回值为StreamingResponseBody或 ResponseEntity<StreamingResponseBody>
*/
public boolean supportsReturnType(MethodParameter returnType) {
if (StreamingResponseBody.class.isAssignableFrom(returnType.getParameterType())) {
return true;
}
else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric().resolve();
return (bodyType != null && StreamingResponseBody.class.isAssignableFrom(bodyType));
}
return false;
}
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
// 返回值为ResponseEntity<StreamingResponseBody>
if (returnValue instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
response.setStatus(responseEntity.getStatusCodeValue());
outputMessage.getHeaders().putAll(responseEntity.getHeaders());
returnValue = responseEntity.getBody();
if (returnValue == null) {
mavContainer.setRequestHandled(true);
outputMessage.flush();
return;
}
}
ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
Assert.state(request != null, "No ServletRequest");
ShallowEtagHeaderFilter.disableContentCaching(request);
Assert.isInstanceOf(StreamingResponseBody.class, returnValue, "StreamingResponseBody expected");
// 返回值为StreamingResponseBody
StreamingResponseBody streamingBody = (StreamingResponseBody) returnValue;
// 构建线程StreamingResponseBodyTask
Callable<Void> callable = new StreamingResponseBodyTask(outputMessage.getBody(), streamingBody);
// 执行
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
}
private static class StreamingResponseBodyTask implements Callable<Void> {
private final OutputStream outputStream;
private final StreamingResponseBody streamingBody;
public StreamingResponseBodyTask(OutputStream outputStream, StreamingResponseBody streamingBody) {
this.outputStream = outputStream;
this.streamingBody = streamingBody;
}
// 这个线程的逻辑很简单,就是将streamingBody中的内容直接写入outputStream
@Override
public Void call() throws Exception {
this.streamingBody.writeTo(this.outputStream);
return null;
}
}
了解StreamingResponseBody接口
@FunctionalInterface
public interface StreamingResponseBody {
/**
* 用于写入响应体的回调。官方文档中强烈建议使用它时需要显示配置TaskExecutor
* 来执行异步请求
*/
void writeTo(OutputStream outputStream) throws IOException;
}
demo
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public ThreadPoolTaskExecutor mvcTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setQueueCapacity(100);
executor.setMaxPoolSize(25);
return executor;
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(mvcTaskExecutor());
// 1.设置请求默认超时时间
//configurer.setDefaultTimeout (5000);
}
}
@Controller
public Class TestController{
@RequestMapping("/test")
public StreamingResponseBody test(){
return o->{
try(ObjectOutputStream oos = new ObjectOutputStream (o)){
oos.writeBytes ("abcdefg");
}
};
}
@RequestMapping(value="/test2",produces = {"text/html;charset=utf-8"})
@ResponseBody
public Callable<String> test2(){
log.info ("请求开始,线程为{}",Thread.currentThread ());
Callable<String> callable = () -> {
log.info ("异步请求开始,线程为{}",Thread.currentThread ());
Thread.sleep (10000);
log.info ("异步请求结束,线程为{}",Thread.currentThread ());
return "abcdefg";
};
log.info ("请求结束,线程为{}",Thread.currentThread ());
return callable;
}
}
如果我将设置请求默认超时的注释放开,5000<10000,请求将会报超时错误。
- CallableMethodReturnValueHandler
// 支持Callable以及其子类
public boolean supportsReturnType(MethodParameter returnType) {
return Callable.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
// 对比StreamingResponseBodyReturnValueHandler发现,其实它们最终都是调用线程池去执行
Callable<?> callable = (Callable<?>) returnValue;
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
}
我们跟踪下WebAsyncManager#startCallableProcessing
public void startCallableProcessing(Callable<?> callable, Object... processingContext) throws Exception {
Assert.notNull(callable, "Callable must not be null");
// 这里构建了一个异步任务,将Callable线程包装起来了
startCallableProcessing(new WebAsyncTask(callable), processingContext);
}
而AsyncTaskMethodReturnValueHandler是直接处理返回值为WebAsyncTask及其子类的,我们看下源码。
- AsyncTaskMethodReturnValueHandler
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return WebAsyncTask.class.isAssignableFrom(returnType.getParameterType());
}
@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);
}
demo
@RequestMapping(value="/test2",produces = {"text/html;charset=utf-8"})
@ResponseBody
public WebAsyncTask test2(){
Callable<String> callable = () -> {
log.info ("异步请求开始,线程为{}",Thread.currentThread ());
Thread.sleep (10000);
log.info ("异步请求结束,线程为{}",Thread.currentThread ());
return "abcdefg";
};
log.info ("请求结束,线程为{}",Thread.currentThread ());
WebAsyncTask webAsyncTask = new WebAsyncTask (5000,callable);
webAsyncTask.onTimeout (()->"请求已经超时");
return webAsyncTask;
}
认识WebAsyncTask
public class WebAsyncTask<V> implements BeanFactoryAware {
// 线程
private final Callable<V> callable;
// 超时时间
private Long timeout;
// 线程池
private AsyncTaskExecutor executor;
// 配置的线程池beanName
private String executorName;
// BeanFactory容器
private BeanFactory beanFactory;
// 超时回调
private Callable<V> timeoutCallback;
// 错误回调
private Callable<V> errorCallback;
// 完成回调
private Runnable completionCallback;
// get/set方法
...
// 线程执行拦截器,针对超时错误完成时回调处理
CallableProcessingInterceptor getInterceptor() {
return new CallableProcessingInterceptor() {
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
return (timeoutCallback != null ? timeoutCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
}
@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
return (errorCallback != null ? errorCallback.call() : CallableProcessingInterceptor.RESULT_NONE);
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
if (completionCallback != null) {
completionCallback.run();
}
}
};
}
}
springMVC中配置
<bean id="myThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" /><!--最小线程数 -->
<property name="maxPoolSize" value="10" /><!--最大线程数 -->
<property name="queueCapacity" value="50" /><!--缓冲队列大小 -->
<property name="threadNamePrefix" value="abc-" /><!--线程池中产生的线程名字前缀 -->
<property name="keepAliveSeconds" value="30" /><!--线程池中空闲线程的存活时间单位秒 -->
</bean>
<mvc:annotation-driven>
<!--开启异步支持 -->
<mvc:async-support task-executor="myThreadPool"
default-timeout="600">
</mvc:async-support>
</mvc:annotation-driven>
开启异步支持后,如果有配置对应请求的过滤器,过滤器也需要开启异步支持。 如果使用FilterRegistrationBean配置filter,默认异步是支持的,如果将asyncSupported设为false,程序会报错;如果在web.xml中进行配置,那么需要设置<async-supported>true</async-supported>。
小结
- StreamingResponseBody,Callable,WebAsyncTask作为返回值,虽然最终都会包装称为WebAsyncTask,但是侧重点不一样,StreamingResponseBody适合用于下载,而Callable更灵活一点,可以自定义对应功能,WebAsyncTask在此基础上还可以进行一些属性配置,例如超时时间,线程池,回调函数等。
- 对请求异步处理的好处就是能够快速释放servlet容器线程,想象一下,如果大批量长时间的请求一直占用着容器,容器是有瓶颈的,到了峰值后,新的请求将不能够进来,这是我们不希望看见的,所以当我们的接口处理时间长,请求频繁的情况下,我们可以使用异步来进行处理,这将能够极大的提高容器的吞吐量。