1. Asynchronous request
Spring MVC has extensive integration with Servlet asynchronous request handling:
- DeferredResult and Callable return values in controller methods provide basic support for a single asynchronous return value.
- The controller can stream multiple values, including SSE and raw data.
- A controller can use a reactive client and return a reactive type to handle the response.
1. DeferredResult
Once asynchronous request handling is enabled in the servlet container, the controller method can wrap DeferredResult
the return value of any supported controller method, as shown in the following example:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<>();
// Save the deferredResult somewhere..
return deferredResult;
}
// From some other thread...
deferredResult.setResult(result);
Controllers can produce return values asynchronously from different threads—for example, in response to external events (JMS messages), scheduled tasks, or other events.
2. Callable
A controller can java.util.concurrent.Callable
be used to wrap any supported return value, as the following example shows:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return () -> "someView";
}
The return value can then be obtained by running the given task through the configured TaskExecutor.
3. Processing
Here is a very succinct overview of Servlet asynchronous request handling:
-
ServletRequest
request.startAsync()
Asynchronous mode can be entered by calling . The main effect of this is that the servlet (and any filters) can exit, but the response is still open for processing to complete later. -
The right
request.startAsync()
call returnsAsyncContext
, which you can use to further control the asynchronous processing. For example, it providesdispatch
methods that are similar to forward in the Servlet API, except that it allows the application to resume request processing on the Servlet container thread. -
ServletRequest
ProvidesDispatcherType
access to the current, which you can use to differentiate between processing the initial request, asynchronous dispatch, forwarding, and other dispatcher types.
DeferredResult
The processing works as follows:
-
The controller returns
DeferredResult
, and saves it in some in-memory queue or list that can be accessed. -
Spring MVC calls
request.startAsync()
. -
At the same time,
DispatcherServlet
the request processing thread exits with all configured filters, but the response remains open. -
The application is set up from some thread
DeferredResult
, and Spring MVC dispatches the request back to the Servlet container. -
DispatcherServlet
is called again, and processing resumes with the asynchronously produced return value.
Callable
Processing works like this:
-
The controller returns one
Callable
. -
Spring MVC calls
request.startAsync()
, and willCallable
submit toTaskExecutor
, handle them in a separate thread. -
At the same time,
DispatcherServlet
all filters exit the Servlet container thread, but the response is still open. -
Eventually,
Callable
a result is produced and Spring MVC dispatches the request back to the Servlet container to complete processing. -
DispatcherServlet
is called again, andCallable
processing continues with the asynchronously produced return value from .
exception handling
When you use DeferredResult
, you can choose whether to call setResult
or setErrorResult
not with an exception. In both cases, Spring MVC dispatches the request back to the Servlet container to complete processing. It is then handled as if the controller method returned the given value or raised the given exception. The exception then goes through the normal exception handling mechanisms (e.g. calling @ExceptionHandler
a method).
Callable
Similar processing logic occurs when you use , the main difference is whether the result is Callable
returned, or it raises an exception
to intercept
HandlerInterceptor instances can be of type AsyncHandlerInterceptor to receive the afterConcurrentHandlingStarted callback (instead of postHandle and afterCompletion ) on the initial request to start asynchronous processing.
Implementations of HandlerInterceptor can also register a CallableProcessingInterceptor or DeferredResultProcessingInterceptor to integrate more deeply with the life cycle of an asynchronous request (for example, to handle a timeout event) DeferredResult
provides onTimeout(Runnable) and onCompletion(Runnable) callbacks
Asynchronous Spring MVC vs. WebFlux
The Servlet API was originally built for a single pass in the Filter-Servlet chain. Asynchronous request processing lets the application exit the Filter-Servlet chain, but leaves the response for further processing. Spring MVC's asynchronous support is built around this mechanism. When the controller returns a DeferredResult, the Filter-Servlet chain is exited and the Servlet container thread is released. Later, when the DeferredResult is set, an ASYNC dispatch (to the same URL), during which the controller is mapped again, but instead of calling it, the DeferredResult value is used (as if the controller returned it), to resume processing.
In contrast, Spring WebFlux is neither built on the Servlet API, nor does it require such asynchronous request handling capabilities, since it is asynchronous by design. Asynchronous processing is built into all framework contracts and is inherently supported through all phases of request processing.
From a programming model perspective, both Spring MVC and Spring WebFlux support asynchronous and reactive (Reactive) types as return values of controller methods. Spring MVC even supports streams, including reactive backpressure. However, individual writes to the response are still blocking (and executed on a separate thread), unlike WebFlux, which relies on non-blocking I/O and does not require additional threads for each write.
Another fundamental difference is that Spring MVC does not support asynchronous or reactive types in controller method parameters (for example, @RequestBody, @RequestPart, etc.), nor does it explicitly support asynchronous and reactive types as model attributes. Spring WebFlux does support all of them.
Finally, from a configuration standpoint, asynchronous request handling must be enabled at the Servlet container level.
二、 HTTP Streaming
You can use DeferredResult and Callable to implement a single asynchronous return value. What if you want to generate multiple asynchronous values and have them written to the response? This section describes how to do this.
1.Objects
You can use the return value of ResponseBodyEmitter to generate a stream of objects, each of which is serialized by HttpMessageConverter and written to the response, as shown in the following example:
@GetMapping("/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();
You can also use ResponseBodyEmitter as the body in ResponseEntity, allowing you to customize the status and header of the response.
When the emitter throws an IOException (for example, if the remote client disappears), the application is not responsible for cleaning up the connection, nor should it call emitter.complete or emitter.completeWithError. Instead, the servlet container automatically initiates an AsyncListener error notification in which Spring MVC makes a completeWithError call. This call in turn performs the final ASYNC dispatch to the application, during which Spring MVC invokes the configured exception resolver and completes the request.
2.SSE
SseEmitter (a subclass of ResponseBodyEmitter) provides Server-Sent Events support, and events sent from the server are formatted according to the W3C SSE specification. To generate an SSE stream from a controller, return SseEmitter, as shown in the following example:
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter();
// 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();
While SSE streams are the primary choice for browsers, please note that Internet Explorer does not support Server-Sent Events. Consider using Spring's WebSocket messaging with SockJS fallback transports (including SSE), which targets a wide range of browsers.
3. Raw Data
Sometimes OutputStream
it is useful to bypass message conversion and flow directly to the response (for example, for file downloads). You can StreamingResponseBody
do this using return types, as the following example shows:
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
You can ResponseEntity
use StreamingResponseBody
it as the body to customize the status and header information of the response.