Spring MVC 3.2引入了基于Servlet 3的异步请求处理。通常,控制器方法可以返回 java.util.concurrent.Callable并从Spring MVC管理的线程生成返回值,而不是像往常一样返回值。同时,主Servlet容器线程被退出并释放,并允许处理其他请求。在TaskExecutor的帮助下,Spring MVC调用一个单独的线程执行Callable;请求被调度回Servlet容器,以便使用Callable返回的值恢复处理。以下是此类控制器方法的示例:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
另一种选择是控制器方法返回一个DeferredResult实例。在这种情况下,返回值也将从任意线程产生,即不由Spring MVC管理的线程。例如,可以响应于诸如JMS消息,计划任务等的一些外部事件而产生结果。以下是此类控制器方法的示例:
@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异步请求处理功能的情况下,这可能很难理解。以下是有关基础机制的一些基本事实:
- ServletRequest可以通过调用request.startAsync()开启异步模式。这样做的主要作用是Servlet以及任何过滤器都可以退出,但响应将保持打开状态以允许稍后完成处理。
- 对request.startAsync()返回的调用AsyncContext,可用于进一步控制异步处理。例如,它提供的dispatch方法类似于Servlet API的转发,但它允许应用程序在Servlet容器线程上恢复请求处理。
- ServletRequest提供当前DispatcherType,可用于区分处理初始请求、一个异步调度、请求转发以及其他的调度类型。
考虑到上述情况,以下是通过Callable的异步请求处理的事件序列:
- 控制器返回一个Callable。
- Spring MVC启动异步处理并提交Callable到TaskExecutor以在单独的线程中进行处理。
- DispatcherServlet和所有的过滤器退出Servlet容器线程,但响应仍然是打开状态。
- Callable产生结果,Spring MVC转发请求回Servlet容器以恢复处理。
- DispatcherServlet再次调用,恢复处理来自Callable异步产生的结果。
DeferredResult处理序列非常相似,除了应用程序从任何线程产生异步结果:
- Controller返回DeferredResult并将其保存在可以访问它的某个内存队列或列表中。
- Spring MVC启动异步处理。
- DispatcherServlet和所有配置的过滤器退出请求处理线程,但响应仍然是打开状态。
- 应用程序从某个线程设置DeferredResult,Spring MVC将请求调度回Servlet容器。
- DispatcherServlet再次调用,恢复处理异步产生的结果。
1. 异步请求的异常处理
如果Callable从控制器方法返回的内容在执行时引发异常会发生什么?简单来说,与控制器方法引发异常时发生的情况相同。它通过常规异常处理机制。详细的解释是,当Callable 引发异常Spring MVC调度回Servlet容器时,Exception结果导致使用Exception而不是控制器方法返回值来恢复请求处理。当使用DeferredResult时,可以选择是通过Exception实例调用 setResult或setErrorResult。
2. 拦截异步请求
HandlerInterceptor还可以实现AsyncHandlerInterceptor以实现afterConcurrentHandlingStarted回调,当开始处理异步时,将调用该方法而不是postHandle和afterCompletion。
HandlerInterceptor还可以注册CallableProcessingInterceptor 或 DeferredResultProcessingInterceptor以便更深入地集成异步请求的生命周期,例如处理超时事件。
DeferredResult类型还提供如onTimeout(Runnable) 和 onCompletion(Runnable)方法。
当使用Callable时,可以使用WebAsyncTask实例来包装它,该实例还提供超时和完成的注册方法。
3. HTTP流
控制器方法可以使用DeferredResult和Callable异步生成其返回值,并且可以用于实现如长轮询之类的技术, 其中服务器可以尽快将事件推送到客户端。
如果想在单个HTTP响应上推送多个事件,该怎么办?这是与“长轮询”相关的技术,称为“HTTP流”。Spring MVC通过ResponseBodyEmitter返回值类型实现了这一点,该类型可用于发送多个对象,而不是通常情况下的一个@ResponseBody,其中发送的每个Object通过HttpMessageConverter转换后被写入响应。
示例:
@RequestMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
return emitter;
}
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
请注意,ResponseBodyEmitter也可以将其用作ResponseEntity体 ,以便自定义响应的状态和头。
4. 使用服务器发送事件的HTTP流式传输
SseEmitter是ResponseBodyEmitter为Server-Sent Events提供支持 的子类。服务器发送的事件是“HTTP Streaming”技术的另一个变体,除了从服务器推送的事件根据W3C服务器发送事件规范进行了格式化。
服务器发送事件可用于其预期目的,即将事件从服务器推送到客户端。在Spring MVC中很容易做到,只需要返回一个SseEmitter类型的值。
但请注意,IE不支持服务器发送事件,对于更高级的Web应用程序消息传递方案(如在线游戏,协作,财务应用程序等),最好考虑Spring的WebSocket支持,包括SockJS风格的WebSocket,可以追溯到各种各样的浏览器(包括IE)以及更高级别的消息传递模式,用于通过更加以消息传递为中心的体系结构中的发布-订阅模型与客户端进行交互。
6. HTTP流直接到OutputStream
ResponseBodyEmitter允许通过HttpMessageConverter将对象写入响应来发送事件。这可能是最常见的情况,例如在编写JSON数据时。但是,有时绕过消息转换并直接写入响应OutputStream (例如文件下载)很有用。这可以在StreamingResponseBody返回值类型的帮助下完成 。
示例:
@RequestMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
请注意,StreamingResponseBody也可以将其用作ResponseEntity体 ,以便自定义响应的状态和头。
7. 配置异步请求处理
7.1 Servlet容器配置
对于配置了web.xml的应用程序,一定要更新到3.0版:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
...
</web-app>
必须在DispatcherServlet开启异步支持,通过在web.xml中<async-supported>true</async-supported>子元素来启用。此外,任何参与异步请求处理的Filter都必须配置为支持ASYNC调度类型。为Spring框架提供的所有过滤器启用ASYNC调度程序类型应该是安全的,因为它们通常会扩展OncePerRequestFilter,并且运行时检查过滤器是否需要参与异步调度。
下面是一些web.xml配置示例:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<filter>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
</web-app>
如果使用Servlet 3,基于Java的配置,例如通过 WebApplicationInitializer,还需要像在web.xml一样设置“asyncSupported”标志以及ASYNC调度程序类型。要简化所有这些配置,请考虑扩展 AbstractDispatcherServletInitializer或AbstractAnnotationConfigDispatcherServletInitializer,它自动设置这些选项并使注册Filter实例变得非常容易。
7.2 Spring MVC配置
MVC Java配置和MVC命名空间提供了配置异步请求处理的选项。WebMvcConfigurer具有configureAsyncSupport方法,同时<mvc:annotation-driven>有 <async-support>子元素。
这些允许配置使用异步请求的默认超时值,如果未设置,则取决于底层Servlet容器(例如,Tomcat上的10秒)。还可以配置AsyncTaskExecutor用于执行从控制器方法返回的Callable实例。强烈建议配置此属性,因为默认情况下Spring MVC使用SimpleAsyncTaskExecutor。MVC Java配置和MVC命名空间还允许注册CallableProcessingInterceptor和 DeferredResultProcessingInterceptor实例。
如果需要覆盖特定DeferredResult的默认超时值,可以使用适当的类构造函数。类似地,对于Callable,可以将其包装在一个WebAsyncTask并使用适当的类构造函数来自定义超时值。WebAsyncTask类的构造函数也提供 AsyncTaskExecutor。