概述

Spring MVC 3.2 M1 引入了基于 Servlet 3.0 支持的异步请求处理功能。我将针对 Spring MVC 3.2 的新特性发布一系列文章,通过对背景知识和相关内容的充分介绍,帮助你理解为什么需要这些新特性,以及如何使用它们。这是系列文章中的第一篇。

Spring MVC 3.2 的更新内容已经可以在 Spring Framework Github 中查看。你也可以将 http://repo.springsource.org/snapshot 配置到你的项目仓库中,以获取快照版本。在后续文章中,我将提供更多的源码示例链接。

如果你想现在就尝试这些新特性,可以在 GitHub 上签出 spring-mvc-async 分支中的 spring-mvc-showcase 项目,并通过 提交记录 查看其中的更新信息。

特性预览

从编程模型的角度来看,新功能看似简单。现在,控制器(Controller)的方法可以返回 Callable 类型来完成异步请求处理。Spring MVC 3.2 会在 TaskExecutor 的帮助下,在一个独立的线程中调用这个返回值。示例代码如下:

@RequestMapping(method = RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
 
  return new Callable<String>() {
    public Object call() throws Exception {
      // ...
      return "someView";
    }
  };
}

另一种方式是在控制器方法中返回 DeferredResult 类型(这是 Spring MVC 3.2 中的新成员),并在任意线程中完成异步处理。例如,对某个外部事件(如 JMS 消息、AMQP 消息、Redis 消息等)作出反应。下面是另一个代码片段:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult quotes() {
  DeferredResult deferredResult = new DeferredResult();
  // Add deferredResult to a Queue or a Map...
  return deferredResult;
}
 
// In some other thread..
// Set the return value on the deferredResult
 
deferredResult.set(data);

大家可能对上面的代码片段有很多疑问,我会在后续文章中给出更多的细节信息。在深入了解之前,我先介绍一些相关的技术背景知识。

异步请求背景:长连接与线程模型

当前一些网络应用最常用的异步处理方式就是长连接方式,例如运行一个缓慢的数据库查询、调用一个外部的 REST API 或者执行其他 I/O 操作。这些方式很快就会消耗光 Servlet 容器的线程池,影响程序的可扩展性。

在一些情况下,你可能需要等待一个处理完成,例如发送邮件、执行数据库操作等。在这种“即发即忘”(fire-and-forget)情况下,你可以使用 Spring 注解 @Async 或设置 Spring Integration 事件并迅速返回,也许还可以返回一个用于确认的 ID,为后续的响应所用。这在 Spring MVC 3.2 之前就可以实现,并且可以避免请求死锁。

对于需要在结果返回之前释放资源的其他情况,你需要先释放处理请求的 Servlet 容器线程来提高程序的可扩展性。为了实现这个功能,Servlet 3 允许一个 Servlet 在返回请求之后声明保持响应为打开状态,这样请求就可以在一个独立线程中完成。

具体来说,可以调用 Servlet 3 中的 request.startAsync() 方法,并使用返回的 AsyncContext 在一个独立线程内继续写入(并最终完成)响应。这在客户端看来没有任何变化,请求仍然看起来像是其他 HTTP 标准的“请求—响应”一样。但是,在服务器端看来,异步请求处理可以让你以扩展性更好的方式处理请求。

Servlet 3 规范定义了异步请求处理的标准流程。关于 Servlet 对异步请求处理的支持,还有很多内容,你可以找到 很多 示例文章(注:部分外链可能因网络原因无法访问),但是上面总结的这些是所需要的最基本的概念。

总结

在下一篇文章中,我将介绍第二种异步请求处理的方式:客户端浏览器无延时的实时获取服务器的更新信息。过去已经发展出了很多方式实现这个功能,一些停留在 HTTP 标准的“请求—响应”的语义环境下,另一些则以更好的方式实现。

原文地址:http://blog.springsource.org/2012/05/06/spring-mvc-3-2-preview-introducing-servlet-3-async-support/

说明:本文基于 Spring MVC 3.2 预览版编写,属于历史技术文章。当前 Spring Framework 已演进至 6.x 版本,Servlet 规范也已更新至 6.0。文中提到的 CallableDeferredResult 异步处理机制在现代版本中依然适用且更为成熟,但具体配置与依赖版本请以官方最新文档为准。