Spring MVC 3.2 introduced Servlet 3 based asynchronous request processing. Instead of returning a value, as usual, a controller method can now return ajava.util.concurrent.Callable
and produce the return value from a Spring MVC managed thread. Meanwhile the main Servlet container thread is exited and released and allowed to process other requests. Spring MVC invokes the Callable
in a separate thread with the help of a TaskExecutor
and when the Callable
returns, the request is dispatched back to the Servlet container to resume processing using the value returned by the Callable
. Here is an example of such a controller method:
@RequestMapping(method=RequestMethod.POST) public CallableprocessUpload(final MultipartFile file) { return new Callable () { public String call() throws Exception { // ... return "someView"; } }; }
Another option is for the controller method to return an instance of DeferredResult
. In this case the return value will also be produced from any thread, i.e. one that is not managed by Spring MVC. For example the result may be produced in response to some external event such as a JMS message, a scheduled task, and so on. Here is an example of such a controller method:
@RequestMapping("/quotes") @ResponseBody public DeferredResultquotes() { DeferredResult deferredResult = new DeferredResult (); // Save the deferredResult somewhere.. return deferredResult; } // In some other thread... deferredResult.setResult(data);
This may be difficult to understand without any knowledge of the Servlet 3.0 asynchronous request processing features. It would certainly help to read up on that. Here are a few basic facts about the underlying mechanism:
- A
ServletRequest
can be put in asynchronous mode by callingrequest.startAsync()
. The main effect of doing so is that the Servlet, as well as any Filters, can exit but the response will remain open to allow processing to complete later. - The call to
request.startAsync()
returnsAsyncContext
which can be used for further control over async processing. For example it provides the methoddispatch
, that is similar to a forward from the Servlet API except it allows an application to resume request processing on a Servlet container thread. - The
ServletRequest
provides access to the currentDispatcherType
that can be used to distinguish between processing the initial request, an async dispatch, a forward, and other dispatcher types.
With the above in mind, the following is the sequence of events for async request processing with a Callable
:
- Controller returns a
Callable
. - Spring MVC starts asynchronous processing and submits the
Callable
to aTaskExecutor
for processing in a separate thread. - The
DispatcherServlet
and all Filter’s exit the Servlet container thread but the response remains open. - The
Callable
produces a result and Spring MVC dispatches the request back to the Servlet container to resume processing. - The
DispatcherServlet
is invoked again and processing resumes with the asynchronously produced result from theCallable
.
The sequence for DeferredResult
is very similar except it’s up to the application to produce the asynchronous result from any thread:
- Controller returns a
DeferredResult
and saves it in some in-memory queue or list where it can be accessed. - Spring MVC starts async processing.
- The
DispatcherServlet
and all configured Filter’s exit the request processing thread but the response remains open. - The application sets the
DeferredResult
from some thread and Spring MVC dispatches the request back to the Servlet container. - The
DispatcherServlet
is invoked again and processing resumes with the asynchronously produced result.
For further background on the motivation for async request processing and when or why to use it please read this blog post series.
Exception Handling for Async Requests
What happens if a Callable
returned from a controller method raises an Exception while being executed? The short answer is the same as what happens when a controller method raises an exception. It goes through the regular exception handling mechanism. The longer explanation is that when a Callable
raises an Exception Spring MVC dispatches to the Servlet container with the Exception
as the result and that leads to resume request processing with the Exception
instead of a controller method return value. When using a DeferredResult
you have a choice whether to call setResult
or setErrorResult
with an Exception
instance.
Intercepting Async Requests
A HandlerInterceptor
can also implement AsyncHandlerInterceptor
in order to implement the afterConcurrentHandlingStarted
callback, which is called instead of postHandle
and afterCompletion
when asynchronous processing starts.
A HandlerInterceptor
can also register a CallableProcessingInterceptor
or a DeferredResultProcessingInterceptor
in order to integrate more deeply with the lifecycle of an asynchronous request and for example handle a timeout event. See the Javadoc of AsyncHandlerInterceptor
for more details.
The DeferredResult
type also provides methods such as onTimeout(Runnable)
and onCompletion(Runnable)
. See the Javadoc of DeferredResult
for more details.
When using a Callable
you can wrap it with an instance of WebAsyncTask
which also provides registration methods for timeout and completion.
HTTP Streaming
A controller method can use DeferredResult
and Callable
to produce its return value asynchronously and that can be used to implement techniques such as long polling where the server can push an event to the client as soon as possible.
What if you wanted to push multiple events on a single HTTP response? This is a technique related to "Long Polling" that is known as "HTTP Streaming". Spring MVC makes this possible through the ResponseBodyEmitter
return value type which can be used to send multiple Objects, instead of one as is normally the case with@ResponseBody
, where each Object sent is written to the response with an HttpMessageConverter
.
Here is an example of that:
@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();
Note that ResponseBodyEmitter
can also be used as the body in a ResponseEntity
in order to customize the status and headers of the response.
HTTP Streaming With Server-Sent Events
SseEmitter
is a sub-class of ResponseBodyEmitter
providing support for Server-Sent Events. Server-sent events is a just another variation on the same "HTTP Streaming" technique except events pushed from the server are formatted according to the W3C Server-Sent Events specification.
Server-Sent Events can be used for their intended purpose, that is to push events from the server to clients. It is quite easy to do in Spring MVC and requires simply returning a value of type SseEmitter
.
Note however that Internet Explorer does not support Server-Sent Events and that for more advanced web application messaging scenarios such as online games, collaboration, financial applicatinos, and others it’s better to consider Spring’s WebSocket support that includes SockJS-style WebSocket emulation falling back to a very wide range of browsers (including Internet Explorer) and also higher-level messaging patterns for interacting with clients through a publish-subscribe model within a more messaging-centric architecture. For further background on this see the following blog post.
HTTP Streaming Directly To The OutputStream
ResponseBodyEmitter
allows sending events by writing Objects to the response through an HttpMessageConverter
. This is probably the most common case, for example when writing JSON data. However sometimes it is useful to bypass message conversion and write directly to the response OutputStream
for example for a file download. This can be done with the help of the StreamingResponseBody
return value type.
Here is an example of that:
@RequestMapping("/download") public StreamingResponseBody handle() { return new StreamingResponseBody() { @Override public void writeTo(OutputStream outputStream) throws IOException { // write... } }; }
Note that StreamingResponseBody
can also be used as the body in a ResponseEntity
in order to customize the status and headers of the response.