提交 209e8de1 编写于 作者: R Rossen Stoyanchev

Document async request processing enhancements

Generally update chapter and add documentation for 4.2 including
the return value types ResponseBodyEmitter, SseEmitter, and
StreamingResponseBody.

Issue: SPR-12672
上级 5b09723b
......@@ -2054,12 +2054,12 @@ customized through `jsonpParameterNames` property.
=== Asynchronous Request 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 separate thread.
Meanwhile the main Servlet container thread is released and allowed to process other
`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 with the value returned by the `Callable`. Here
is an example controller method:
Servlet container to resume processing using the value returned by the `Callable`. Here
is an example of such a controller method:
[source,java,indent=0]
[subs="verbatim,quotes"]
......@@ -2077,11 +2077,11 @@ is an example controller method:
}
----
A second option is for the controller to return an instance of `DeferredResult`. In this
case the return value will also be produced from a separate thread. However, that thread
is not known to Spring MVC. For example the result may be produced in response to some
external event such as a JMS message, a scheduled task, etc. Here is an example
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:
[source,java,indent=0]
[subs="verbatim,quotes"]
......@@ -2090,7 +2090,7 @@ controller method:
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult in in-memory queue ...
// Save the deferredResult somewhere..
return deferredResult;
}
......@@ -2098,102 +2098,187 @@ controller method:
deferredResult.setResult(data);
----
This may be difficult to understand without any knowledge of the Servlet 3 async
processing feature. It would certainly help to read up on it. At a very minimum consider
the following basic facts:
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 calling `request.startAsync()`.
The main effect of doing so is that the Servlet, as well as any Filters, can exit but
the response will remain open allowing some other thread to complete processing.
* The call to `request.startAsync()` returns an `AsyncContext`, which can be used for
the response will remain open to allow processing to complete later.
* The call to `request.startAsync()` returns `AsyncContext` which can be used for
further control over async processing. For example it provides the method `dispatch`,
which can be called from an application thread in order to "dispatch" the request back
to the Servlet container. An async dispatch is similar to a forward except it is made
from one (application) thread to another (Servlet container) thread whereas a forward
occurs synchronously in the same (Servlet container) thread.
* `ServletRequest` provides access to the current `DispatcherType`, which can be used to
distinguish if a `Servlet` or a `Filter` is processing on the initial request
processing thread and when it is processing in an async dispatch.
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 current `DispatcherType` 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`: (1) Controller returns a `Callable`, (2) Spring MVC starts
async processing and submits the `Callable` to a `TaskExecutor` for processing in a
separate thread, (3) the `DispatcherServlet` and all Filter's exit the request
processing thread but the response remains open, (4) the `Callable` produces a result
and Spring MVC dispatches the request back to the Servlet container, (5) the
`DispatcherServlet` is invoked again and processing resumes with the asynchronously
produced result from the `Callable`. The exact sequencing of (2), (3), and (4) may vary
depending on the speed of execution of the concurrent threads.
The sequence of events for async request processing with a `DeferredResult` is the same
in principal except it's up to the application to produce the asynchronous result from
some thread: (1) Controller returns a `DeferredResult` and saves it in some in-memory
queue or list where it can be accessed, (2) Spring MVC starts async processing, (3) the
`DispatcherServlet` and all configured Filter's exit the request processing thread but
the response remains open, (4) the application sets the `DeferredResult` from some
thread and Spring MVC dispatches the request back to the Servlet container, (5) the
`DispatcherServlet` is invoked again and processing resumes with the asynchronously
produced result.
Explaining the motivation for async request processing and when or why to use it are
beyond the scope of this document. For further information you may wish to read
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:
* 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
https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support[this
blog post series].
[[mvc-ann-async-exceptions]]
==== Exception Handling for Async Requests
What happens if a `Callable` returned from a controller method raises an Exception while
being executed? The effect is similar to what happens when any controller method raises
an exception. It is handled by a matching `@ExceptionHandler` method in the same
controller or by one of the configured `HandlerExceptionResolver` instances.
[NOTE]
====
Under the covers, when a `Callable` raises an Exception, Spring MVC still dispatches to
the Servlet container to resume processing. The only difference is that the result of
executing the `Callable` is an `Exception` that must be processed with the configured
`HandlerExceptionResolver` instances.
====
When using a `DeferredResult`, you have a choice of calling its `setErrorResult(Object)`
method and provide an `Exception` or any other Object you'd like to use as the result.
If the result is an `Exception`, it will be processed with a matching
`@ExceptionHandler` method in the same controller or with any configured
`HandlerExceptionResolver` instance.
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.
[[mvc-ann-async-interception]]
==== Intercepting Async Requests
An existing `HandlerInterceptor` can implement `AsyncHandlerInterceptor`, which provides
one additional method `afterConcurrentHandlingStarted`. It is invoked after async
processing starts and when the initial request processing thread is being exited. See
the `AsyncHandlerInterceptor` javadocs for more details on that.
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.
[[mvc-ann-async-http-streaming]]
==== 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
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.
Further options for async request lifecycle callbacks are provided directly on
`DeferredResult`, which has the methods `onTimeout(Runnable)` and
`onCompletion(Runnable)`. Those are called when the async request is about to time out
or has completed respectively. The timeout event can be handled by setting the
`DeferredResult` to some value. The completion callback however is final and the result
can no longer be set.
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:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@RequestMapping("/events")
public ResponseBodyEmitter<String> handle() {
ResponseBodyEmitter<String> emitter = new ResponseBodyEmitter<String>();
// 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();
----
Similar callbacks are also available with a `Callable`. However, you will need to wrap
the `Callable` in an instance of `WebAsyncTask` and then use that to register the
timeout and completion callbacks. Just like with `DeferredResult`, the timeout event can
be handled and a value can be returned while the completion event is final.
Note that `ResponseBodyEmitter` can also be used as the body in a
`ResponseEntity` in order to customize the status and headers of
the response.
You can also register a `CallableProcessingInterceptor` or a
`DeferredResultProcessingInterceptor` globally through the MVC Java config or the MVC
namespace. Those interceptors provide a full set of callbacks and apply every time a
`Callable` or a `DeferredResult` is used.
[[mvc-ann-async-sse]]
==== Server-Sent Events
`SseEmitter` is a sub-class 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 Servet-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
http://blog.pivotal.io/pivotal/products/websocket-architecture-in-spring-4-0[the following blog post].
[[mvc-ann-async-output-stream]]
==== 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:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@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.
[[mvc-ann-async-configuration]]
==== Configuration for Async Request Processing
==== Configuring Asynchronous Request Processing
[[mvc-ann-async-configuration-servlet3]]
===== Servlet 3 Async Config
To use Servlet 3 async request processing, you need to update `web.xml` to version 3.0:
===== Servlet Container Configuration
For applications configured with a `web.xml` be sure to update to version 3.0:
[source,xml,indent=0]
[subs="verbatim,quotes"]
......@@ -2209,22 +2294,15 @@ To use Servlet 3 async request processing, you need to update `web.xml` to versi
</web-app>
----
The `DispatcherServlet` and any `Filter` configuration need to have the
`<async-supported>true</async-supported>` sub-element. Additionally, any `Filter` that
also needs to get involved in async dispatches should also be configured to support the
ASYNC dispatcher type. Note that it is safe to enable the ASYNC dispatcher type for all
filters provided with the Spring Framework since they will not get involved in async
dispatches unless needed.
Asynchronous support must be enabled on the `DispatcherServlet` through the
`<async-supported>true</async-supported>` web.xml sub-element. Additionally
any `Filter` that participates in asyncrequest processing must be configured
to support the ASYNC dispatcher type. It should be safe to enable the ASYNC
dispatcher type for all filters provided with the Spring Framework since they
usually extend `OncePerRequestFilter` and that has runtime checks for whether
the filter needs to be involved in async dispatches or not.
[WARNING]
====
Note that for some Filters it is absolutely critical to ensure they are mapped to
be invoked during asynchronous dispatches. For example if a filter such as the
`OpenEntityManagerInViewFilter` is responsible for releasing database connection
resources and must be invoked at the end of an async request.
Below is an example of a propertly configured filter:
====
Below is some example web.xml configuration:
[source,xml,indent=0]
[subs="verbatim,quotes"]
......@@ -2253,18 +2331,20 @@ Below is an example of a propertly configured filter:
----
If using Servlet 3, Java based configuration, e.g. via `WebApplicationInitializer`,
If using Servlet 3, Java based configuration for example via `WebApplicationInitializer`,
you'll also need to set the "asyncSupported" flag as well as the ASYNC dispatcher type
just like with `web.xml`. To simplify all this configuration, consider extending
`AbstractDispatcherServletInitializer` or
`AbstractAnnotationConfigDispatcherServletInitializer`, which automatically set those
options and make it very easy to register `Filter` instances.
`AbstractAnnotationConfigDispatcherServletInitializer` which automatically
set those options and make it very easy to register `Filter` instances.
[[mvc-ann-async-configuration-spring-mvc]]
===== Spring MVC Async Config
The MVC Java config and the MVC namespace both provide options for configuring async
request processing. `WebMvcConfigurer` has the method `configureAsyncSupport` while
<mvc:annotation-driven> has an <async-support> sub-element.
===== Spring MVC Configuration
The MVC Java config and the MVC namespace provide options for configuring
asynchronous request processing. `WebMvcConfigurer` has the method
`configureAsyncSupport` while `<mvc:annotation-driven>` has an
`<async-support>` sub-element.
Those allow you to configure the default timeout value to use for async requests, which
if not set depends on the underlying Servlet container (e.g. 10 seconds on Tomcat). You
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册