Spring MVC 3.2開始引入了基于Servlet 3的異步請(qǐng)求處理。相比以前,控制器方法已經(jīng)不一定需要返回一個(gè)值,而是可以返回一個(gè)java.util.concurrent.Callable
的對(duì)象,并通過Spring MVC所管理的線程來產(chǎn)生返回值。與此同時(shí),Servlet容器的主線程則可以退出并釋放其資源了,同時(shí)也允許容器去處理其他的請(qǐng)求。通過一個(gè)TaskExecutor
,Spring MVC可以在另外的線程中調(diào)用Callable
。當(dāng)Callable
返回時(shí),請(qǐng)求再攜帶Callable
返回的值,再次被分配到Servlet容器中恢復(fù)處理流程。以下代碼給出了一個(gè)這樣的控制器方法作為例子:
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
另一個(gè)選擇,是讓控制器方法返回一個(gè)DeferredResult
的實(shí)例。這種場(chǎng)景下,返回值可以由任何一個(gè)線程產(chǎn)生,也包括那些不是由Spring MVC管理的線程。舉個(gè)例子,返回值可能是為了響應(yīng)某些外部事件所產(chǎn)生的,比如一條JMS的消息,一個(gè)計(jì)劃任務(wù),等等。以下代碼給出了一個(gè)這樣的控制器作為例子:
@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);
如果對(duì)Servlet 3.0的異步請(qǐng)求處理特性沒有了解,理解這個(gè)特性可能會(huì)有點(diǎn)困難。因此,閱讀一下前者的文檔將會(huì)很有幫助。以下給出了這個(gè)機(jī)制運(yùn)作背后的一些原理:
ServletRequest
可以通過調(diào)用request.startAsync()
方法而進(jìn)入異步模式。這樣做的主要結(jié)果就是該servlet以及所有的過濾器都可以結(jié)束,但其響應(yīng)(response)會(huì)留待異步處理結(jié)束后再返回request.startAsync()
方法會(huì)返回一個(gè)AsyncContext
對(duì)象,可用它對(duì)異步處理進(jìn)行進(jìn)一步的控制和操作。比如說它也提供了一個(gè)與轉(zhuǎn)向(forward)很相似的dispatch
方法,只不過它允許應(yīng)用恢復(fù)Servlet容器的請(qǐng)求處理進(jìn)程ServletRequest
提供了獲取當(dāng)前DispatherType
的方式,后者可以用來區(qū)別當(dāng)前處理的是原始請(qǐng)求、異步分發(fā)請(qǐng)求、轉(zhuǎn)向,或是其他類型的請(qǐng)求分發(fā)類型。有了上面的知識(shí),下面可以來看一下Callable
的異步請(qǐng)求被處理時(shí)所依次發(fā)生的事件:
Callable
對(duì)象Callable
對(duì)象提交給另一個(gè)獨(dú)立線程的執(zhí)行器TaskExecutor
處理DispatcherServlet
和所有過濾器都退出Servlet容器線程,但此時(shí)方法的響應(yīng)對(duì)象仍未返回Callable
對(duì)象最終產(chǎn)生一個(gè)返回結(jié)果,此時(shí)Spring MVC會(huì)重新把請(qǐng)求分派回Servlet容器,恢復(fù)處理DispatcherServlet
再次被調(diào)用,恢復(fù)對(duì)Callable
異步處理所返回結(jié)果的處理對(duì)DeferredResult
異步請(qǐng)求的處理順序也非常類似,區(qū)別僅在于應(yīng)用可以通過任何線程來計(jì)算返回一個(gè)結(jié)果:
DeferredResult
對(duì)象,并把它存取在內(nèi)存(隊(duì)列或列表等)中以便存取DispatcherServlet
和所有過濾器都退出Servlet容器線程,但此時(shí)方法的響應(yīng)對(duì)象仍未返回DeferredResult
進(jìn)行設(shè)值,然后Spring MVC會(huì)重新把請(qǐng)求分派回Servlet容器,恢復(fù)處理DispatcherServlet
再次被調(diào)用,恢復(fù)對(duì)該異步返回結(jié)果的處理關(guān)于引入異步請(qǐng)求處理的背景和原因,以及什么時(shí)候使用它、為什么使用異步請(qǐng)求處理等問題,你可以從這個(gè)系列的博客中了解更多信息。
若控制器返回的Callable
在執(zhí)行過程中拋出了異常,又會(huì)發(fā)生什么事情?簡(jiǎn)單來說,這與一般的控制器方法拋出異常是一樣的。它會(huì)被正常的異常處理流程捕獲處理。更具體地說呢,當(dāng)Callable
拋出異常時(shí),Spring MVC會(huì)把一個(gè)Exception
對(duì)象分派給Servlet容器進(jìn)行處理,而不是正常返回方法的返回值,然后容器恢復(fù)對(duì)此異步請(qǐng)求異常的處理。若方法返回的是一個(gè)DeferredResult
對(duì)象,你可以選擇調(diào)Exception
實(shí)例的setResult
方法還是setErrorResult
方法。
處理器攔截器HandlerInterceptor
可以實(shí)現(xiàn)AsyncHandlerInterceptor
接口攔截異步請(qǐng)求,因?yàn)樵诋惒秸?qǐng)求開始時(shí),被調(diào)用的回調(diào)方法是該接口的afterConcurrentHandlingStarted
方法,而非一般的postHandle
和afterCompletion
方法。
如果需要與異步請(qǐng)求處理的生命流程有更深入的集成,比如需要處理timeout的事件等,則HandlerInterceptor
需要注冊(cè)一個(gè)CallableProcessingInterceptor
或DeferredResultProcessingInterceptor
攔截器。具體的細(xì)節(jié)可以參考AsyncHandlerInterceptor
類的Java文檔。
DeferredResult
類還提供了onTimeout(Runnable)
和onCompletion(Runnable)
等方法,具體的細(xì)節(jié)可以參考DeferredResult
類的Java文檔。
Callable
需要請(qǐng)求過期(timeout)和完成后的攔截時(shí),可以把它包裝在一個(gè)WebAsyncTask
實(shí)例中,后者提供了相關(guān)的支持。
如前所述,控制器可以使用DeferredResult
或Callable
對(duì)象來異步地計(jì)算其返回值,這可以用于實(shí)現(xiàn)一些有用的技術(shù),比如 long polling技術(shù),讓服務(wù)器可以盡可能快地向客戶端推送事件。
如果你想在一個(gè)HTTP響應(yīng)中同時(shí)推送多個(gè)事件,怎么辦?這樣的技術(shù)已經(jīng)存在,與"Long Polling"相關(guān),叫"HTTP Streaming"。Spring MVC支持這項(xiàng)技術(shù),你可以通過讓方法返回一個(gè)ResponseBodyEmitter
類型對(duì)象來實(shí)現(xiàn),該對(duì)象可被用于發(fā)送多個(gè)對(duì)象。通常我們所使用的@ResponseBody
只能返回一個(gè)對(duì)象,它是通過HttpMessageConverter
寫到響應(yīng)體中的。
下面是一個(gè)實(shí)現(xiàn)該技術(shù)的例子:
@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
體里面使用,這可以對(duì)響應(yīng)狀態(tài)和響應(yīng)頭做一些定制。
Note that ResponseBodyEmitter
can also be used as the body in a ResponseEntity
in order to customize the status and headers of the response.
SseEmitter
是ResponseBodyEmitter
的一個(gè)子類,提供了對(duì)服務(wù)器端事件推送的技術(shù)的支持。服務(wù)器端事件推送其實(shí)只是一種HTTP Streaming的類似實(shí)現(xiàn),只不過它服務(wù)器端所推送的事件遵循了W3C Server-Sent Events規(guī)范中定義的事件格式。
“服務(wù)器端事件推送”技術(shù)正如其名,是用于由服務(wù)器端向客戶端進(jìn)行的事件推送。這在Spring MVC中很容易做到,只需要方法返回一個(gè)SseEmitter
類型的對(duì)象即可。
需要注意的是,Internet Explorer并不支持這項(xiàng)服務(wù)器端事件推送的技術(shù)。另外,對(duì)于更大型的web應(yīng)用及更精致的消息傳輸場(chǎng)景——比如在線游戲、在線協(xié)作、金融應(yīng)用等——來說,使用Spring的WebSocket(包含SockJS風(fēng)格的實(shí)時(shí)WebSocket)更成熟一些,因?yàn)樗С值臑g覽器范圍非常廣(包括IE),并且,對(duì)于一個(gè)以消息為中心的架構(gòu)中,它為服務(wù)器端-客戶端間的事件發(fā)布-訂閱模型的交互提供了更高層級(jí)的消息模式(messaging patterns)的支持。
ResponseBodyEmitter
也允許通過HttpMessageConverter
向響應(yīng)體中支持寫事件對(duì)象。這可能是最常見的情形,比如寫返回的JSON數(shù)據(jù)的時(shí)候。但有時(shí),跳過消息轉(zhuǎn)換的階段,直接把數(shù)據(jù)寫回響應(yīng)的輸出流OutputStream
可能更有效,比如文件下載這樣的場(chǎng)景。這可以通過返回一個(gè)StreamingResponseBody
類型的對(duì)象來實(shí)現(xiàn)。
以下是一個(gè)實(shí)現(xiàn)的例子:
@RequestMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
ResponseBodyEmitter
也可以被放到ResponseEntity
體里面使用,這可以對(duì)響應(yīng)狀態(tài)和響應(yīng)頭做一些定制。
對(duì)于那些使用web.xml
配置文件的應(yīng)用,請(qǐng)確保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>
異步請(qǐng)求必須在web.xml
將DispatcherServlet
下的子元素<async-supported>true</async-supported>
設(shè)置為true。此外,所有可能參與異步請(qǐng)求處理的過濾器Filter
都必須配置為支持ASYNC類型的請(qǐng)求分派。在Spring框架中為過濾器啟用支持ASYNC類型的請(qǐng)求分派應(yīng)是安全的,因?yàn)檫@些過濾器一般都繼承了基類OncePerRequestFilter
,后者在運(yùn)行時(shí)會(huì)檢查該過濾器是否需要參與到異步分派的請(qǐng)求處理中。
以下是一個(gè)例子,展示了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>
如果應(yīng)用使用的是Servlet 3規(guī)范基于Java編程的配置方式,比如通過WebApplicationInitializer
,那么你也需要設(shè)置"asyncSupported"標(biāo)志和ASYNC分派類型的支持,就像你在web.xml
中所配置的一樣。你可以考慮直接繼承AbstractDispatcherServletInitializer
或AbstractAnnotationConfigDispatcherServletInitializer
來簡(jiǎn)化配置,它們都自動(dòng)地為你設(shè)置了這些配置項(xiàng),并使得注冊(cè)Filter
過濾器實(shí)例變得非常簡(jiǎn)單。
MVC Java編程配置和MVC命名空間配置方式都提供了配置異步請(qǐng)求處理支持的選擇。WebMvcConfigurer
提供了configureAsyncSupport
方法,而<mvc:annotation-driven>
有一個(gè)子元素<async-support>
,它們都用以為此提供支持。
這些配置允許你覆寫異步請(qǐng)求默認(rèn)的超時(shí)時(shí)間,在未顯式設(shè)置時(shí),它們的值與所依賴的Servlet容器是相關(guān)的(比如,Tomcat設(shè)置的超時(shí)時(shí)間是10秒)。你也可以配置用于執(zhí)行控制器返回值Callable
的執(zhí)行器AsyncTaskExecutor
。Spring強(qiáng)烈推薦你配置這個(gè)選項(xiàng),因?yàn)镾pring MVC默認(rèn)使用的是普通的執(zhí)行器SimpleAsyncTaskExecutor
。MVC Java編程配置及MVC命名空間配置的方式都允許你注冊(cè)自己的CallableProcessingInterceptor
和DeferredResultProcessingInterceptor
攔截器實(shí)例。
若你需要為特定的DeferredResult
覆寫默認(rèn)的超時(shí)時(shí)間,你可以選用合適的構(gòu)造方法來實(shí)現(xiàn)。類似,對(duì)于Callable
返回,你可以把它包裝在一個(gè)WebAsyncTask
對(duì)象中,并使用合適的構(gòu)造方法定義超時(shí)時(shí)間。WebAsyncTask
類的構(gòu)造方法同時(shí)也能接受一個(gè)任務(wù)執(zhí)行器AsyncTaskExecutor
類型的參數(shù)。
更多建議: