提交 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. ...@@ -2054,12 +2054,12 @@ customized through `jsonpParameterNames` property.
=== Asynchronous Request Processing === Asynchronous Request Processing
Spring MVC 3.2 introduced Servlet 3 based asynchronous request processing. Instead of 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 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. `java.util.concurrent.Callable` and produce the return value from a Spring MVC managed thread.
Meanwhile the main Servlet container thread is released and allowed to process other 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 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 `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 Servlet container to resume processing using the value returned by the `Callable`. Here
is an example controller method: is an example of such a controller method:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
...@@ -2077,11 +2077,11 @@ is an example controller method: ...@@ -2077,11 +2077,11 @@ is an example controller method:
} }
---- ----
A second option is for the controller to return an instance of `DeferredResult`. In this Another option is for the controller method to return an instance of `DeferredResult`. In this
case the return value will also be produced from a separate thread. However, that thread case the return value will also be produced from any thread, i.e. one that
is not known to Spring MVC. For example the result may be produced in response to some 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, etc. Here is an example external event such as a JMS message, a scheduled task, and so on. Here is an example
controller method: of such a controller method:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
...@@ -2090,7 +2090,7 @@ controller method: ...@@ -2090,7 +2090,7 @@ controller method:
@ResponseBody @ResponseBody
public DeferredResult<String> quotes() { public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>(); DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult in in-memory queue ... // Save the deferredResult somewhere..
return deferredResult; return deferredResult;
} }
...@@ -2098,102 +2098,187 @@ controller method: ...@@ -2098,102 +2098,187 @@ controller method:
deferredResult.setResult(data); deferredResult.setResult(data);
---- ----
This may be difficult to understand without any knowledge of the Servlet 3 async This may be difficult to understand without any knowledge of the Servlet 3.0
processing feature. It would certainly help to read up on it. At a very minimum consider asynchronous request processing features. It would certainly help to read up
the following basic facts: on that. Here are a few basic facts about the underlying mechanism:
* A `ServletRequest` can be put in asynchronous mode by calling `request.startAsync()`. * 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 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 response will remain open to allow processing to complete later.
* The call to `request.startAsync()` returns an `AsyncContext`, which can be used for * The call to `request.startAsync()` returns `AsyncContext` which can be used for
further control over async processing. For example it provides the method `dispatch`, 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 that is similar to a forward from the Servlet API except it allows an
to the Servlet container. An async dispatch is similar to a forward except it is made application to resume request processing on a Servlet container thread.
from one (application) thread to another (Servlet container) thread whereas a forward * The `ServletRequest` provides access to the current `DispatcherType` that can
occurs synchronously in the same (Servlet container) thread. be used to distinguish between processing the initial request, an async
* `ServletRequest` provides access to the current `DispatcherType`, which can be used to dispatch, a forward, and other dispatcher types.
distinguish if a `Servlet` or a `Filter` is processing on the initial request
processing thread and when it is processing in an async dispatch.
With the above in mind, the following is the sequence of events for async request 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 processing with a `Callable`:
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 * Controller returns a `Callable`.
processing thread but the response remains open, (4) the `Callable` produces a result * Spring MVC starts asynchronous processing and submits the `Callable` to
and Spring MVC dispatches the request back to the Servlet container, (5) the a `TaskExecutor` for processing in a separate thread.
`DispatcherServlet` is invoked again and processing resumes with the asynchronously * The `DispatcherServlet` and all Filter's exit the Servlet container thread
produced result from the `Callable`. The exact sequencing of (2), (3), and (4) may vary but the response remains open.
depending on the speed of execution of the concurrent threads. * The `Callable` produces a result and Spring MVC dispatches the request back
to the Servlet container to resume processing.
The sequence of events for async request processing with a `DeferredResult` is the same * The `DispatcherServlet` is invoked again and processing resumes with the
in principal except it's up to the application to produce the asynchronous result from asynchronously produced result from the `Callable`.
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 The sequence for `DeferredResult` is very similar except it's up to the
`DispatcherServlet` and all configured Filter's exit the request processing thread but application to produce the asynchronous result from any thread:
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 * Controller returns a `DeferredResult` and saves it in some in-memory
`DispatcherServlet` is invoked again and processing resumes with the asynchronously queue or list where it can be accessed.
produced result. * Spring MVC starts async processing.
* The `DispatcherServlet` and all configured Filter's exit the request
Explaining the motivation for async request processing and when or why to use it are processing thread but the response remains open.
beyond the scope of this document. For further information you may wish to read * 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 https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support[this
blog post series]. blog post series].
[[mvc-ann-async-exceptions]] [[mvc-ann-async-exceptions]]
==== Exception Handling for Async Requests ==== Exception Handling for Async Requests
What happens if a `Callable` returned from a controller method raises an Exception while What happens if a `Callable` returned from a controller method raises an
being executed? The effect is similar to what happens when any controller method raises Exception while being executed? The short answer is the same as what happens
an exception. It is handled by a matching `@ExceptionHandler` method in the same when a controller method raises an exception. It goes through the regular
controller or by one of the configured `HandlerExceptionResolver` instances. exception handling mechanism. The longer explanation is that when a `Callable`
raises an Exception Spring MVC dispatches to the Servlet container with
[NOTE] the `Exception` as the result and that leads to resume request processing
==== with the `Exception` instead of a controller method return value.
Under the covers, when a `Callable` raises an Exception, Spring MVC still dispatches to When using a `DeferredResult` you have a choice whether to call
the Servlet container to resume processing. The only difference is that the result of `setResult` or `setErrorResult` with an `Exception` instance.
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.
[[mvc-ann-async-interception]] [[mvc-ann-async-interception]]
==== Intercepting Async Requests ==== Intercepting Async Requests
An existing `HandlerInterceptor` can implement `AsyncHandlerInterceptor`, which provides A `HandlerInterceptor` can also implement `AsyncHandlerInterceptor` in order
one additional method `afterConcurrentHandlingStarted`. It is invoked after async to implement the `afterConcurrentHandlingStarted` callback, which is called
processing starts and when the initial request processing thread is being exited. See instead of `postHandle` and `afterCompletion` when asynchronous processing
the `AsyncHandlerInterceptor` javadocs for more details on that. 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 What if you wanted to push multiple events on a single HTTP response?
`DeferredResult`, which has the methods `onTimeout(Runnable)` and This is a technique related to "Long Polling" that is known as "HTTP Streaming".
`onCompletion(Runnable)`. Those are called when the async request is about to time out Spring MVC makes this possible through the `ResponseBodyEmitter` return value
or has completed respectively. The timeout event can be handled by setting the type which can be used to send multiple Objects, instead of one as is normally
`DeferredResult` to some value. The completion callback however is final and the result the case with `@ResponseBody`, where each Object sent is written to the
can no longer be set. 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 Note that `ResponseBodyEmitter` can also be used as the body in a
the `Callable` in an instance of `WebAsyncTask` and then use that to register the `ResponseEntity` in order to customize the status and headers of
timeout and completion callbacks. Just like with `DeferredResult`, the timeout event can the response.
be handled and a value can be returned while the completion event is final.
You can also register a `CallableProcessingInterceptor` or a [[mvc-ann-async-sse]]
`DeferredResultProcessingInterceptor` globally through the MVC Java config or the MVC ==== Server-Sent Events
namespace. Those interceptors provide a full set of callbacks and apply every time a
`Callable` or a `DeferredResult` is used. `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]] [[mvc-ann-async-configuration]]
==== Configuration for Async Request Processing ==== Configuring Asynchronous Request Processing
[[mvc-ann-async-configuration-servlet3]] [[mvc-ann-async-configuration-servlet3]]
===== Servlet 3 Async Config ===== Servlet Container Configuration
To use Servlet 3 async request processing, you need to update `web.xml` to version 3.0: For applications configured with a `web.xml` be sure to update to version 3.0:
[source,xml,indent=0] [source,xml,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
...@@ -2209,22 +2294,15 @@ To use Servlet 3 async request processing, you need to update `web.xml` to versi ...@@ -2209,22 +2294,15 @@ To use Servlet 3 async request processing, you need to update `web.xml` to versi
</web-app> </web-app>
---- ----
The `DispatcherServlet` and any `Filter` configuration need to have the Asynchronous support must be enabled on the `DispatcherServlet` through the
`<async-supported>true</async-supported>` sub-element. Additionally, any `Filter` that `<async-supported>true</async-supported>` web.xml sub-element. Additionally
also needs to get involved in async dispatches should also be configured to support the any `Filter` that participates in asyncrequest processing must be configured
ASYNC dispatcher type. Note that it is safe to enable the ASYNC dispatcher type for all to support the ASYNC dispatcher type. It should be safe to enable the ASYNC
filters provided with the Spring Framework since they will not get involved in async dispatcher type for all filters provided with the Spring Framework since they
dispatches unless needed. usually extend `OncePerRequestFilter` and that has runtime checks for whether
the filter needs to be involved in async dispatches or not.
[WARNING] Below is some example web.xml configuration:
====
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:
====
[source,xml,indent=0] [source,xml,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
...@@ -2253,18 +2331,20 @@ Below is an example of a propertly configured filter: ...@@ -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 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 just like with `web.xml`. To simplify all this configuration, consider extending
`AbstractDispatcherServletInitializer` or `AbstractDispatcherServletInitializer` or
`AbstractAnnotationConfigDispatcherServletInitializer`, which automatically set those `AbstractAnnotationConfigDispatcherServletInitializer` which automatically
options and make it very easy to register `Filter` instances. set those options and make it very easy to register `Filter` instances.
[[mvc-ann-async-configuration-spring-mvc]] [[mvc-ann-async-configuration-spring-mvc]]
===== Spring MVC Async Config ===== Spring MVC Configuration
The MVC Java config and the MVC namespace both provide options for configuring async
request processing. `WebMvcConfigurer` has the method `configureAsyncSupport` while The MVC Java config and the MVC namespace provide options for configuring
<mvc:annotation-driven> has an <async-support> sub-element. 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 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 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.
先完成此消息的编辑!
想要评论请 注册