Spring Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

514 lines
20 KiB

[[mvc-ann-async]]
= Asynchronous Requests
Spring MVC has an extensive integration with Servlet asynchronous request
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-processing[processing]:
* xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-deferredresult[`DeferredResult`] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-callable[`Callable`]
return values in controller methods provide basic support for a single asynchronous
return value.
* Controllers can xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-http-streaming[stream] multiple values, including
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SSE] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-output-stream[raw data].
* Controllers can use reactive clients and return
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types] for response handling.
For an overview of how this differs from Spring WebFlux, see the xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-vs-webflux[Async Spring MVC compared to WebFlux] section below.
[[mvc-ann-async-deferredresult]]
== `DeferredResult`
Once the asynchronous request processing feature is xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration[enabled]
in the Servlet container, controller methods can wrap any supported controller method
return value with `DeferredResult`, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@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);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
val deferredResult = DeferredResult<String>()
// Save the deferredResult somewhere..
return deferredResult
}
// From some other thread...
deferredResult.setResult(result)
----
======
The controller can produce the return value asynchronously, from a different thread -- for
example, in response to an external event (JMS message), a scheduled task, or other event.
[[mvc-ann-async-callable]]
== `Callable`
A controller can wrap any supported return value with `java.util.concurrent.Callable`,
as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return () -> "someView";
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
// ...
"someView"
}
----
======
The return value can then be obtained by running the given task through the
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `AsyncTaskExecutor`.
[[mvc-ann-async-processing]]
== Processing
Here is a very concise overview of Servlet asynchronous request processing:
* A `ServletRequest` can be put in asynchronous mode by calling `request.startAsync()`.
The main effect of doing so is that the Servlet (as well as any filters) can exit, but
the response remains open to let processing complete later.
* The call to `request.startAsync()` returns `AsyncContext`, which you can use for
further control over asynchronous processing. For example, it provides the `dispatch` method,
which is similar to a forward from the Servlet API, except that it lets an
application resume request processing on a Servlet container thread.
* The `ServletRequest` provides access to the current `DispatcherType`, which you can
use to distinguish between processing the initial request, an asynchronous
dispatch, a forward, and other dispatcher types.
`DeferredResult` processing works as follows:
* The controller returns a `DeferredResult` and saves it in some in-memory
queue or list where it can be accessed.
* Spring MVC calls `request.startAsync()`.
* Meanwhile, the `DispatcherServlet` and all configured filters 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 return value.
`Callable` processing works as follows:
* The controller returns a `Callable`.
* Spring MVC calls `request.startAsync()` and submits the `Callable` to
an `AsyncTaskExecutor` for processing in a separate thread.
* Meanwhile, the `DispatcherServlet` and all filters exit the Servlet container thread,
but the response remains open.
* Eventually the `Callable` produces a result, and Spring MVC dispatches the request back
to the Servlet container to complete processing.
* The `DispatcherServlet` is invoked again, and processing resumes with the
asynchronously produced return value from the `Callable`.
For further background and context, you can also read
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support[the
blog posts] that introduced asynchronous request processing support in Spring MVC 3.2.
[[mvc-ann-async-exceptions]]
=== Exception Handling
When you use a `DeferredResult`, you can choose whether to call `setResult` or
`setErrorResult` with an exception. In both cases, Spring MVC dispatches the request back
to the Servlet container to complete processing. It is then treated either as if the
controller method returned the given value or as if it produced the given exception.
The exception then goes through the regular exception handling mechanism (for example, invoking
`@ExceptionHandler` methods).
When you use `Callable`, similar processing logic occurs, the main difference being that
the result is returned from the `Callable` or an exception is raised by it.
[[mvc-ann-async-interception]]
=== Interception
`HandlerInterceptor` instances can be of type `AsyncHandlerInterceptor`, to receive the
`afterConcurrentHandlingStarted` callback on the initial request that starts asynchronous
processing (instead of `postHandle` and `afterCompletion`).
`HandlerInterceptor` implementations can also register a `CallableProcessingInterceptor`
or a `DeferredResultProcessingInterceptor`, to integrate more deeply with the
lifecycle of an asynchronous request (for example, to handle a timeout event). See
{api-spring-framework}/web/servlet/AsyncHandlerInterceptor.html[`AsyncHandlerInterceptor`]
for more details.
`DeferredResult` provides `onTimeout(Runnable)` and `onCompletion(Runnable)` callbacks.
See the {api-spring-framework}/web/context/request/async/DeferredResult.html[javadoc of `DeferredResult`]
for more details. `Callable` can be substituted for `WebAsyncTask` that exposes additional
methods for timeout and completion callbacks.
[[mvc-ann-async-vs-webflux]]
=== Async Spring MVC compared to WebFlux
The Servlet API was originally built for making a single pass through the Filter-Servlet
chain. Asynchronous request processing lets applications exit the Filter-Servlet chain
but leave the response open for further processing. The Spring MVC asynchronous support
is built around that mechanism. When a 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) is made, during which the
controller is mapped again but, rather than invoking it, the `DeferredResult` value is used
(as if the controller returned it) to resume processing.
By contrast, Spring WebFlux is neither built on the Servlet API, nor does it need such an
asynchronous request processing feature, because it is asynchronous by design. Asynchronous
handling is built into all framework contracts and is intrinsically supported through all
stages of request processing.
From a programming model perspective, both Spring MVC and Spring WebFlux support
asynchronous and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] as return values in controller methods.
Spring MVC even supports streaming, including reactive back pressure. However, individual
writes to the response remain blocking (and are performed on a separate thread), unlike WebFlux,
which relies on non-blocking I/O and does not need an extra thread for each write.
Another fundamental difference is that Spring MVC does not support asynchronous or reactive
types in controller method arguments (for example, `@RequestBody`, `@RequestPart`, and others),
nor does it have any explicit support for asynchronous and reactive types as model attributes.
Spring WebFlux does support all that.
Finally, from a configuration perspective the asynchronous request processing feature must be
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration[enabled at the Servlet container level].
[[mvc-ann-async-http-streaming]]
== HTTP Streaming
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]#
You can use `DeferredResult` and `Callable` for a single asynchronous return value.
What if you want to produce multiple asynchronous values and have those written to the
response? This section describes how to do so.
[[mvc-ann-async-objects]]
=== Objects
You can use the `ResponseBodyEmitter` return value to produce a stream of objects, where
each object is serialized with an
xref:integration/rest-clients.adoc#rest-message-conversion[`HttpMessageConverter`] and written to the
response, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@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();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@GetMapping("/events")
fun handle() = ResponseBodyEmitter().apply {
// Save the emitter somewhere..
}
// 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 a `ResponseEntity`, letting you
customize the status and headers of the response.
When an `emitter` throws an `IOException` (for example, if the remote client went away), applications
are not responsible for cleaning up the connection and should not invoke `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 one final `ASYNC` dispatch to the application, during which Spring MVC
invokes the configured exception resolvers and completes the request.
[[mvc-ann-async-sse]]
=== SSE
`SseEmitter` (a subclass of `ResponseBodyEmitter`) provides support for
https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from the server
are formatted according to the W3C SSE specification. To produce an SSE
stream from a controller, return `SseEmitter`, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@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();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun handle() = SseEmitter().apply {
// Save the emitter somewhere..
}
// 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 is the main option for streaming into browsers, note that Internet Explorer
does not support Server-Sent Events. Consider using Spring's
xref:web/websocket.adoc[WebSocket messaging] with
xref:web/websocket/fallback.adoc[SockJS fallback] transports (including SSE) that target
a wide range of browsers.
See also xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-objects[previous section] for notes on exception handling.
[[mvc-ann-async-output-stream]]
=== Raw Data
Sometimes, it is useful to bypass message conversion and stream directly to the response
`OutputStream` (for example, for a file download). You can use the `StreamingResponseBody`
return value type to do so, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@GetMapping("/download")
fun handle() = StreamingResponseBody {
// write...
}
----
======
You can use `StreamingResponseBody` as the body in a `ResponseEntity` to
customize the status and headers of the response.
[[mvc-ann-async-reactive-types]]
== Reactive Types
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]#
Spring MVC supports use of reactive client libraries in a controller (also read
xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries] in the WebFlux section).
This includes the `WebClient` from `spring-webflux` and others, such as Spring Data
reactive data repositories. In such scenarios, it is convenient to be able to return
reactive types from the controller method.
Reactive return values are handled as follows:
* A single-value promise is adapted to, similar to using `DeferredResult`. Examples
include `Mono` (Reactor) or `Single` (RxJava).
* A multi-value stream with a streaming media type (such as `application/x-ndjson`
or `text/event-stream`) is adapted to, similar to using `ResponseBodyEmitter` or
`SseEmitter`. Examples include `Flux` (Reactor) or `Observable` (RxJava).
Applications can also return `Flux<ServerSentEvent>` or `Observable<ServerSentEvent>`.
* A multi-value stream with any other media type (such as `application/json`) is adapted
to, similar to using `DeferredResult<List<?>>`.
TIP: Spring MVC supports Reactor and RxJava through the
{api-spring-framework}/core/ReactiveAdapterRegistry.html[`ReactiveAdapterRegistry`] from
`spring-core`, which lets it adapt from multiple reactive libraries.
For streaming to the response, reactive back pressure is supported, but writes to the
response are still blocking and are run on a separate thread through the
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured]
`AsyncTaskExecutor`, to avoid blocking the upstream source such as a `Flux` returned
from `WebClient`.
[[mvc-ann-async-context-propagation]]
== Context Propagation
It is common to propagate context via `java.lang.ThreadLocal`. This works transparently
for handling on the same thread, but requires additional work for asynchronous handling
across multiple threads. The Micrometer
https://github.com/micrometer-metrics/context-propagation#context-propagation-library[Context Propagation]
library simplifies context propagation across threads, and across context mechanisms such
as `ThreadLocal` values,
Reactor https://projectreactor.io/docs/core/release/reference/#context[context],
GraphQL Java https://www.graphql-java.com/documentation/concerns/#context-objects[context],
and others.
If Micrometer Context Propagation is present on the classpath, when a controller method
returns a xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type] such as `Flux` or `Mono`, all
`ThreadLocal` values, for which there is a registered `io.micrometer.ThreadLocalAccessor`,
are written to the Reactor `Context` as key-value pairs, using the key assigned by the
`ThreadLocalAccessor`.
For other asynchronous handling scenarios, you can use the Context Propagation library
directly. For example:
[source,java,indent=0,subs="verbatim,quotes"]
.Java
----
// Capture ThreadLocal values from the main thread ...
ContextSnapshot snapshot = ContextSnapshot.captureAll();
// On a different thread: restore ThreadLocal values
try (ContextSnapshot.Scope scope = snapshot.setThreadLocals()) {
// ...
}
----
For more details, see the
https://micrometer.io/docs/contextPropagation[documentation] of the Micrometer Context
Propagation library.
[[mvc-ann-async-disconnects]]
== Disconnects
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs-streaming[See equivalent in the Reactive stack]#
The Servlet API does not provide any notification when a remote client goes away.
Therefore, while streaming to the response, whether through xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-sse[SseEmitter]
or xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive types], it is important to send data periodically,
since the write fails if the client has disconnected. The send could take the form of an
empty (comment-only) SSE event or any other data that the other side would have to interpret
as a heartbeat and ignore.
Alternatively, consider using web messaging solutions (such as
xref:web/websocket/stomp.adoc[STOMP over WebSocket] or WebSocket with xref:web/websocket/fallback.adoc[SockJS])
that have a built-in heartbeat mechanism.
[[mvc-ann-async-configuration]]
== Configuration
The asynchronous request processing feature must be enabled at the Servlet container level.
The MVC configuration also exposes several options for asynchronous requests.
[[mvc-ann-async-configuration-servlet3]]
=== Servlet Container
Filter and Servlet declarations have an `asyncSupported` flag that needs to be set to `true`
to enable asynchronous request processing. In addition, Filter mappings should be
declared to handle the `ASYNC` `jakarta.servlet.DispatchType`.
In Java configuration, when you use `AbstractAnnotationConfigDispatcherServletInitializer`
to initialize the Servlet container, this is done automatically.
In `web.xml` configuration, you can add `<async-supported>true</async-supported>` to the
`DispatcherServlet` and to `Filter` declarations and add
`<dispatcher>ASYNC</dispatcher>` to filter mappings.
[[mvc-ann-async-configuration-spring-mvc]]
=== Spring MVC
The MVC configuration exposes the following options for asynchronous request processing:
* Java configuration: Use the `configureAsyncSupport` callback on `WebMvcConfigurer`.
* XML namespace: Use the `<async-support>` element under `<mvc:annotation-driven>`.
You can configure the following:
* Default timeout value for async requests, which if not set, depends
on the underlying Servlet container.
* `AsyncTaskExecutor` to use for blocking writes when streaming with
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] and for
executing `Callable` instances returned from controller methods.
The one used by default is not suitable for production under load.
* `DeferredResultProcessingInterceptor` implementations and `CallableProcessingInterceptor` implementations.
Note that you can also set the default timeout value on a `DeferredResult`,
a `ResponseBodyEmitter`, and an `SseEmitter`. For a `Callable`, you can use
`WebAsyncTask` to provide a timeout value.