异步请求
1. 启用异步请求
- xml配置:
在web.xml
中的servlet
和`filter-mapping中配置.
// 添加到<servlet>中
<async-supported>true</async-supported>
// 添加到filter-mapping中
<dispatcher>ASYNC</dispatcher>
2. 实现方式
2.1 DeferredResult
使用DeferredResult
包装返回值, 这种形式用其他线程来处理请求并生成结果.
@RequestMapping("/deferred")
public DeferredResult<String> deferred() {
DeferredResult<String> result = new DeferredResult<>();
// 可以创建线程去处理请求(处理结果设置在result对象中)
return result;
}
2.2 Callable
使用Callable
包装返回值, 这种方式可以调用TaskExecutor
处理请求.
@RequestMapping("/call")
public Callable<String> call() {
return new Callable<String>() {
@Override
public String call() throws Exception {
return "callable";
}
};
}
3. 工作过程
3.1 ServletRequest的工作过程
- a.进入异步处理模式, 这样可以退出当前的这个servlet请求, 但是响应仍然是打开的.
request.startAsync();
- b.获取
AsyncContext
, 以便可以继续处理请求.
request.getAsyncContext();
- c. 获取
DispatcherType
以便应用适当的过滤器.
request.getDispatcherType().
3.2 DeferredResult的工作过程
- a. 控制器返回一个DeferredResult并将其保存在能被访问的内存队列或列表中.
- b. spring mvc调用request.startAsync(). 同时,
DispatcherServlet
和所有配置的过滤器退出请求处理线程,但是响应仍然打开。 - c. 应用程序从某个线程设置DeferredResult, Spring MVC将请求分派回Servlet容器。
- d. 再次调用DispatcherServlet,然后使用异步生成的返回值继续处理。
注: 使用DeferredResult
时,可以选择调用setResult
还是setErrorResult
。在这两种情况下,Spring MVC都将请求发回Servlet容器以完成处理。然后将其视为控制器方法返回给定值或产生给定异常。然后通过常规异常处理机制进行处理(例如,调用@ExceptionHandler
方法)。
3.3 Callable的工作过程
- a. 控制器返回一个Callable.
- b. Spring MVC调用
request.startAsync()
并将Callable
提交给TaskExecutor
,以便在单独的线程中处理。同时,DispatcherServlet
和所有过滤器退出Servlet容器线程,但是响应仍然打开。 - c.
Callable
生成一个结果,Spring MVC将请求发送回Servlet容器以完成处理。 - d. 再次调用
DispatcherServlet
,然后使用从Callable异步生成的返回值继续处理。
4. 异步请求拦截器
4.1 AsyncHandlerInterceptor
实现这个接口, 接口中定义了方法afterConcurrentHandlingStarted()
, 异步请求开始后调用的回调方法.
当处理程序启动异步请求时,DispatcherServlet
将退出,不会调用postHandle
和afterCompletion
,因为请求处理的结果(例如ModelAndView)可能还没有准备好,并且将从另一个线程并发生成。此时, 将由afterConcurrentHandlingStarted()
方法来代替它们. 做一些线程资源释放工作.
当异步处理完成之后, 请求会被发回容器进行进一步的处理, 此时DispatcherServlet
会调用preHandle, postHandle, afterCompletion
方法, 拦截器通过检查请求的DispatcherType
是REQUEST
还是ASYNC
来区分请求是初始请求还是异步处理后的请求.
注意:当异步请求超时, 则请求不会被再次分发给容器进行处理, 因此拦截器方法就不会被调用.这可以通过WebAsyncManager
的registerCallbackInterceptor
或registerDeferredResultInterceptor
方法注册拦截器, 这可以保证无论异步请求是否启动, 都可以执行preHandle
操作.