提交 4b861aea 编写于 作者: R Rossen Stoyanchev

Reference docs: update async request content

Issue: SPR-16203
上级 aee8a9c9
......@@ -3153,19 +3153,47 @@ or in a JSP:
[[mvc-ann-async]]
== Async Requests
Spring MVC has an extensive integration with the Servlet 3.0 asynchronous request
processing. <<mvc-ann-async-deferredresult>> and <<mvc-ann-async-callable>>
provide basic support for producing return values asynchronously. Controllers can produce
<<mvc-ann-async-http-streaming,response streams>> including
<<mvc-ann-async-sse,SSE>> and <<mvc-ann-async-output-stream,raw data>>. Controllers can
use reactive clients and return <<mvc-ann-async-reactive-types,reactive return types>>
to Spring MVC for response handling.
[[mvc-ann-async-processing]]
=== Processing
Spring MVC 3.2 introduced Servlet 3 based asynchronous request processing. Instead of
returning a value, as usual, a controller method can now return a
`java.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:
[[mvc-ann-async-deferredresult]]
=== `DeferredResult`
Once the asynchronous request processing feature is
<<mvc-ann-async-configuration,enabled>> in the Servlet container, controller methods can
wrap any supported controller method return value with `DeferredResult`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}
// From some other thread...
deferredResult.setResult(data);
----
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.
[[mvc-ann-async-callable]]
=== `Callable`
A controller may also wrap any supported return value with `java.util.concurrent.Callable`:
[source,java,indent=0]
[subs="verbatim,quotes"]
......@@ -3183,30 +3211,15 @@ is an example of such a controller method:
}
----
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:
The return value will then be obtained by executing the the given task through the
<<mvc-ann-async-configuration-spring-mvc,configured>> `TaskExecutor`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@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);
----
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:
[[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
......@@ -3219,98 +3232,83 @@ on that. Here are a few basic facts about the underlying mechanism:
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
a `TaskExecutor` 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 the `Callable`.
The sequence for `DeferredResult` is very similar except it's up to the
application to produce the asynchronous result from any thread:
`DeferredResult` processing:
* 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
* Spring MVC calls `request.startAsync()`.
* Meanwhile 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.
asynchronously produced return value.
`Callable` processing:
* Controller returns a `Callable`.
* Spring MVC calls `request.startAsync()` and submits the `Callable` to
a `TaskExecutor` for processing in a separate thread.
* Meanwhile the `DispatcherServlet` and all Filter's 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 on the motivation for async request processing and
when or why to use it please read
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support[this
blog post series].
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
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.
When using 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, e.g. invoking
`@ExceptionHandler` methods.
When using `Callable`, similar processing logic follows. The main difference being that
the result is returned from the `Callable` or an exception is raised by it.
[[mvc-ann-async-interception]]
=== Async interceptors
=== Interception
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.
``HandlerInterceptor``'s can also be `AsyncHandlerInterceptor` in order to receive the
`afterConcurrentHandlingStarted` callback on the initial request that starts asynchronous
processing instead of `postHandle` and `afterCompletion`.
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`
``HandlerInterceptor``'s can also register a `CallableProcessingInterceptor`
or a `DeferredResultProcessingInterceptor` in order 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.
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.
`DeferredResult` provides `onTimeout(Runnable)` and `onCompletion(Runnable)` callbacks.
See the Javadoc of `DeferredResult` for more details. `Callable` can be substituted for
`WebAsyncTask` that exposes additional methods for timeout and completion callbacks.
[[mvc-ann-async-http-streaming]]
=== Streaming response
A controller method can use `DeferredResult` and `Callable` to produce its
return value asynchronously and that can be used to implement techniques such as
http://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/[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 <<integration.adoc#rest-message-conversion,HttpMessageConverter>>.
Here is an example of that:
What if you wanted to push multiple events on a single HTTP response? The
`ResponseBodyEmitter` return value can be used to stream multiple Objects, where each
Object sent is serialized with an
<<integration.adoc#rest-message-conversion,HttpMessageConverter>> and written to the
response. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@RequestMapping("/events")
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
......@@ -3327,53 +3325,58 @@ Here is an example of that:
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.
`ResponseBodyEmitter` can also be used as the body in a `ResponseEntity` allowing you to
customize the status and headers of the response.
[[mvc-ann-async-sse]]
=== Server-Sent Events
`SseEmitter` is a subclass of `ResponseBodyEmitter` providing support for
http://www.w3.org/TR/eventsource/[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.
`SseEmitter` is a sub-class of `ResponseBodyEmitter` that provides support for
http://www.w3.org/TR/eventsource/[Server-Sent Events] where events sent from the server
are formatted according to the W3C SSE specification. In order to produce an SSE
stream from a controller simply return `SseEmitter`:
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`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@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();
----
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
http://blog.pivotal.io/pivotal/products/websocket-architecture-in-spring-4-0[the following blog post].
While SSE is the main option for streaming into browsers, note that Internet Explorer
does not support Server-Sent Events. Consider using Spring's
<<web.adoc#websocket,WebSocket messaging>> with
<<web.adoc#websocket-fallback,SockJS fallback>> transports (including SSE) that target
a wide range of browsers.
[[mvc-ann-async-output-stream]]
=== Streaming raw data
`ResponseBodyEmitter` allows sending events by writing Objects to the
response through an <<integration.adoc#rest-message-conversion,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:
Sometimes it is useful to bypass message conversion and stream directly to the response
`OutputStream` for example for a file download. Use the of the `StreamingResponseBody`
return value type to do that:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@RequestMapping("/download")
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
......@@ -3384,61 +3387,44 @@ Here is an example of that:
}
----
Note that `StreamingResponseBody` can also be used as the body in a
`ResponseEntity` in order to customize the status and headers of
the response.
`StreamingResponseBody` can be used as the body in a `ResponseEntity` allowing you to
customize the status and headers of the response.
[[mvc-ann-async-reactive-types]]
=== Reactive return values
If using the reactive `WebClient` from `spring-webflux`, or another client, or
a data store with reactive support, you can return reactive types directly from
Spring MVC controller methods.
Spring MVC supports use of reactive client libraries in a controller. 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 .
Spring MVC adapts transparently to the reactive library in use with proper translation
of cardinality -- i.e. how many values are expected. This is done with the help of the
{api-spring-framework}/core/ReactiveAdapterRegistry.html[ReactiveAdapterRegistry] from
`spring-core` which provides pluggable support for reactive and async types. The registry
has built-in support for RxJava but others can be registered.
Return values are handled as follows:
* If the return type has single-value stream semantics such as Reactor `Mono` or
RxJava `Single` it is adapted and equivalent to using `DeferredResult`.
* If the return type has multi-value stream semantics such as Reactor `Flux` or
RxJava `Observable` / `Flowable` and if the media type indicates streaming, e.g.
"application/stream+json" or "text/event-stream", it is adapted and equivalent to
using `ResponseBodyEmitter` or `SseEmitter`. You can also return
`Flux<ServerSentEvent>` or `Observable<ServerSentEvent>`.
* If the return type has multi-value stream semantics but the media type does not
imply streaming, e.g. "application/json", it is adapted and equivalent to using
`DeferredResult<List<?>>`, e.g. JSON array.
Reactive libraries are detected and adapted to a Reactive Streams `Publisher`
through Spring's pluggable `ReactiveAdapterRegistry` which by default supports
Reactor 3, RxJava 2, and RxJava 1. Note that for RxJava 1 you will need to add
https://github.com/ReactiveX/RxJavaReactiveStreams["io.reactivex:rxjava-reactive-streams"]
to the classpath.
A common assumption with reactive libraries is to not block the processing thread.
The `WebClient` with Reactor Netty for example is based on event-loop style
handling using a small, fixed number of threads and those must not be blocked
when writing to the `ServletResponseOutputStream`. Reactive libraries have
operators for that but Spring MVC automatically writes asynchronously so you
don't need to use them. The underlying `TaskExecutor` for this must be configured
through the MVC Java config and the MVC namespace as described in the following
section which by default is a `SyncTaskExecutor` and hence not suitable for
production use.
Reactive return values are handled as follows:
[NOTE]
* A single-value promise is adapted to and similar to using `DeferredResult`. Examples
include `Mono` (Reactor) or `Single` (RxJava).
* A multi-value stream, with a streaming media type such as `"application/stream+json"`
or `"text/event-stream"`, is adapted to and 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 (e.g. "application/json"), is adapted
to and similar to using `DeferredResult<List<?>>`.
[TIP]
====
Unlike Spring MVC, Spring WebFlux is built on a non-blocking, reactive foundation
and uses the Servlet 3.1 non-blocking I/O that's also based on event loop style
processing and hence does not require a thread to absorb the effect of blocking.
Spring MVC supports Reactor and RxJava through the
{api-spring-framework}/core/ReactiveAdapterRegistry.html[ReactiveAdapterRegistry] from
`spring-core` which allows it to adapt from multiple reactive libraries.
====
When streaming to the response with a reactive type, Spring MVC performs (blocking)
writes to the response through the
through the <<mvc-ann-async-configuration-spring-mvc,configured>> MVC `TaskExecutor`.
By default this is a `SyncTaskExecutor` and not suitable for production.
https://jira.spring.io/browse/SPR-16203[SPR-16203] will provide better defaults.
In the mean time please configure the executor through the MVC config.
[[mvc-ann-async-configuration]]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册