Spring MVC 3.2开始引入了基于Servlet3的异步请求处理,相比以前,控制器方法已经不一定需要一个值,而是可以直接返回一个Callable对象,并通过Spring MVC所管理的线程来产生返回值,与此同时,Servlet容器的主线程则可以退出并释放其资源,同时也允许容器去处理其它请求。通过一个TaskExecutor,Spring MVC可以在另外的线程中调用Callable。当Callable返回时,请求在携带Callable返回的值,再次被分配到Servlet容器中恢复处理流程。如实例 异步调用方式一
另一个选择是让控制器返回一个DeferredResult的实例。例如 异步调用方式二
实例:
@RestController
public class TestController {
/**
* 异步调用 方式一,直接返回Callable
* 127.0.0.1:8886/get3
*
* @return
*/
@GetMapping(value = "/get3")
public Callable get3() {
return () -> "someView";
}
/**
* 异步调用 方式二,返回一个DeferredResult
* 127.0.0.1:8886/get4 先调用此方法,会等待
* 在调用
* 127.0.0.1:8886/get5 会返回 hello world
* 另一个选择是,让控制器方法返回一个DeferredResult的实例,在这种场景下,
* 返回值可以由任何一个线程产生,包括哪些不是由Spring MVC管理的线程,
* 举个例子,返回值可以是为了响应某些外部事件所产生,比如JMS消息,计划任务等等
*
* @return
*/
private DeferredResult deferredResult;
@GetMapping(value = "/get4")
public DeferredResult get4() {
DeferredResult deferredResult = new DeferredResult<>();
this.deferredResult = deferredResult;
return deferredResult;
}
@GetMapping(value = "/get5")
public void get5() {
if (Objects.nonNull(deferredResult)) {
deferredResult.setResult("hello world");
}
}
}
Servlet 3.0异步请求运作机制的部分原理
a.Servlet请求ServletRequest可以通过调用request.startAsync()方法而进入异步模式,这样做的主要结果就是该Servlet以及所有的过滤器都可以结束但其相应(Response)会留待异步处理结束后在返回调用。
b.request.startAsync()方法会返回一个AsyncContext对象,可以用他对异步处理进行进一步的控制和操作,比如说他也提供了一个与反转(forward)很相似的dispatch方法,只不过他允许应用恢复Servlet容器的请求处理进程。
c.ServletRequest提供了获取当前DispatherType的方式,后者可以用来区别当前处理的是原始请求,异步分发请求,转向或者是其它类型的请求分发类型。
下面再来看看Callable的异步请求被处理时所发生的事件
a.Spring MVC 开始执行异步处理,并且把该Callable对象提交给另一个独立线程的执行器TaskExecutor处理。
b.DispatcherServlet和所有过滤器都退出Servlet容器线程,但此时方法的响应对象仍未返回。
c.Callable对象最终产生一个返回结果,此时Spring MVC会重新把请求分派回Servlet容器,恢复处理。
d.DispatcherServlet再次被调用,恢复对Callable异步处理所返回结果的处理
对 DeferredResult对象请求的处理顺序也非常类似,区别在于应用可以通过任何线程来计算返回一个结果
a.控制器先返回一个DeferredResult对象,并把它存取在内存(队列或者列表等)中以便存取
b.Spring MVC开始异步处理
c.DispatcherServlet和所有过滤器都退出Servlet容器线程,但此时方法的响应对象仍未返回。
d.由处理该请求的线程对DeferredResult进行设值,然后Spring MVC会重新把 请求分派回Serlvet容器恢复处理
e.DispatcherServlet再次被调用,恢复对该异步返回结果处理。
异步结果的异常处理:
如果Callable在执行过程中抛出异常 与一般的控制器异常一样,会被正常的异常处理流程捕获处理
如果返回方法是一个DeferredResult对象,可以选择
deferredResult.setErrorResult()
拦截异步请求
处理连接器HandlerInterceptor可以实现AsyncHandlerInterceptor接口拦截异步请求,因为在异步请求的开始时,被调用的回调方法是该接口的afterConcurrentHandlingStarted方法,而不是一般的postHandle 和 afterCompletion方法。如果需要与异步请求处理的生命流程有更深入的集成,比如需要处理timeout的事件等,则HandlerInterceptor需要注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor拦截器,更多细节需要参考AsyncHandlerInterceptor类的Java文档。
DeferredResult类还提供了onTimeout(Runnable)和onCompletion(Runnable)等方法可以参考DeferredResult的java文档
Callable需要请求过期(timeout)和完成后的拦截时,可以把他包装在一个WebAsyncTask实例中,后者提供了相关技术支持。