HTTP流
Controller方法能使用DeferredResult和Callable来异步地产生它的返回值,并且能用来实现类似long polling(长轮询)的技术,在这种技术中,服务器能够实时地推送事件到客户端。
如果你想要在单个HTTP响应中推送多个事件时怎么办?这种技术与long polling技术相关,它叫”HTTP Streaming(HTTP流)”。Spring MVC是通过ResponseBodyEmitter来返回这个对应类型的值来实现这种技术的。这个值的类型能被用来发送多个对象,来替代使用@ResponseBody时返回的普通值,每一个发送的对象都使用HttpMessageConverter来写入到响应中。
下面是一个例子:
@RequestMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..(在某处保存这个emitter(发送器))
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的主体,来自定义响应的status(状态)和报头。
Server-SentEvents(服务器端发送事件)的HTTP流
SseEmitter是ResponseBodyEmitter的所属类,是为Server-Sent Events(服务器端发送事件)服务的。Server-Sent Events(服务器端发送事件)是“HTTP流”技术的另一种变种,这种事件能根据W3C Server-Sent Events的定义来格式化事件。
服务器端发送事件能被用来从服务器端推送事件到客户端。在Spring MVC中是易于使用的,并且只需要简单地返回一个SseEmitter类型的值。
请注意,IE浏览器不支持服务器端发送事件,并且大多数高级的web应用里的消息发送场景中,比如:在线游戏,在线协同,财经应用等等,最好是考虑使用Spring的WebSocket,它包括了SockJS式的WebSocket模拟,能够广泛地支持各种浏览器(包括IE),同时支持在以消息作为核心的应用架构中,使用发布-订阅模型来在客户端间进行交互的更高层次的消息模式。如需了解更多,请参阅这个博客的文章。
HTTP流直接写到OutputStream中
ResponseBodyEmitter可以使用HttpMessageConverter来把对象写入到响应中,并发送这个事件。比如:写入JSON数据这种较为普遍的情况。然而,有些时候,比如像文件下载这种,分流消息的转换和直接写到相应的OutputStream中是很有用的。可以通过StreamingResponseBody返回的值类型来实现。
下面是一个例子:
@RequestMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
请注意,ResponseBodyEmitter也同样能作为ResponseEntity的主体,来自定义响应的status(状态)和报头。
配置异步的请求处理
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中,使用<async-supported>true</async-supported>来启用。一般来讲,在异步请求处理中用到的Filter(过滤器)必须配置好,以支持ASYNC的dispatcher(分派器)类型。为Spring框架中的filter启用ASYNC dispatcher(分派器)类型应该是安全的。因为这些filter通常继承了OncePerRequestFilter,会在运行时检查这个filter是否参与异步的分派。
下面是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中的ASYNC dispatcher(分派器)类型那样,来设置”asyncSupported”标识。为了简化这些配置,考虑继承AbstractDispatcherServletInitializer或者AbstractAnnotationConfigDispatcherServletInitializer,来设置这些选项,就可以轻松地注册Filter实例了。
Spring MVC配置
MVC Java配置和MVC 命名空间提供了对异步请求处理的配置选项。当<mvc:annotation-driven>有<async-support>子元素时,WebMvcConfigurer会有configureAsyncSupport方法。
这能允许你配置异步请求默认的超时值(如果你没有在下属的Servlet容器配置过时,比如在Tomcat中,默认是10秒),你同样能配置一个AsyncTaskExecutor来处理从控制器方法返回的Callable实例。这个配置是强烈推荐的,因为Spring MVC是默认使用SimpleAsyncTaskExecutor的。MVC Java配置和MVC命名空间同样允许你注册CallableProcessingInterceptor和DeferredResultProcessingInterceptor实例。
如果你需要为特定DeferredResult覆写默认的超时值,你可以使用合适的类构造器。相似地,对于一个Callable,你能在WebAsyncTask中封装它,并且使用合适的类构造器来自定义超时值。这个WebAsyncTask的类构造器同样可以提供一个AsyncTaskExecutor。
21.3.5 测试控制器
spring-test模块提供了对注解控制器的测试,详见“14.6 Spring MVC测试框架”。