Spring MVC 3.2 中引进了基于异步请求处理的 Servlet 3。除了返回一个值,一个控制器方法现在可以返回一个java.util.concurrent.Callable并生产来自 Spring MVC 管理的线程的返回值。同时主 Servlet 容器线程退出、释放并允许处理其他请求。Spring MVC 在 TaskExecutor 的帮助下,在一个独立的线程中调用 Callable,当 Callable 返回时,请求被发回 Servlet 容器,使用 Callable 的返回值继续执行。这里有一个这样的控制器方法的例子:

1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping
public Callable<String> processUpload(final MultipartFile file)
{
return new Callable<String>()
{
public String call() throws Exception
{
// ...
return "someView";
}
};
}

  这个控制器方法的另一个选择是返回一个DeferredResult实例。这种情况下,返回值可以由任何线程产生,比如一个没有被 Spring MVC 管理的线程。比如,结果可能产生于外部事件的响应,比如一个 JMS 消息,一个定时任务等。这里有一个这样的控制器方法的例子:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes()
{
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);

如果没有任何对 Servlet 3.0 中异步请求处理特性的了解的话,这可能很难理解。去了解一下会很有帮助。这里列出几个这个机制的基本事实:

  • 通过request.startAsync()调用,一个 ServletRequest 可以被置为异步模式。这么做的主要影响是,Servlet,以及任何 Filter,可以退出,但是响应依旧会保持开放来允许之后完成处理过程。

  • 对request.startAsync()的调用返回 AsyncContext,AsyncContext 可以被用于异步处理之上的进一步控制。比如它提供了方法调度功能,这很像 Servlet API 中的 forward,但是它允许应用程序继续在 Servlet 容器线程中进行请求处理。

  • ServletRequest 提供对当前 DispatcherType 的访问,DispatcherType 可以用于区别处理初始化请求、异步调度、forward 和其他调度器(dispatcher)类型。

有了上面的意识,下面是用于带有 Callable 的异步请求处理的事件序列:

  • 控制器返回一个 Callable

  • Spring MVC 开始异步处理,并把 Callable 提交给一个 TaskExecutor 用于在一个单独的线程中处理

  • DispatcherServlet 和所有的 Filter 退出 Servlet 容器线程,但是响应保持打开

  • 这个 Callable 产生一个结果,Spring MVC 把这个请求调回 Servlet 容器继续处理

  • DispatcherServlet 继续执行,并继续处理来自 Callable 的异步产生的结果

DeferredResult 的事件序列很相似,除了它应该由应用程序从任何线程中来产生异步结果:

  • 控制器返回一个 DeferredResult,并把它保存在内存中的队列或者列表中用于访问

  • Spring MVC 开始异步处理

  • DispatcherServlet 和所有配置的 Filter 退出请求处理线程,但是响应依旧打开

  • 应用程序设置来自一些线程中的 DeferredResult,Spring MVC 把请求调回 Servlet 容器

  • DispatcherServlet 再次被调用,并继续处理异步产生的结果

    要了解更多使用异步请求处理的动机的背景,以及什么时候或为什么使用它,请读这个博客文章系列.

来源:https://segmentfault.com/a/1190000007026783