Add reference documentation for async web requests

Issue: SPR-9400
This commit is contained in:
Rossen Stoyanchev 2012-12-11 23:06:26 -05:00
parent 5b2cd763cf
commit e09bdb31cc
2 changed files with 238 additions and 4 deletions

View File

@ -1567,6 +1567,18 @@ public String processSubmit(<emphasis role="bold">@ModelAttribute("pet") Pet pet
linkend="mvc-ann-httpentity" />.</para>
</listitem>
<listitem>
<para>A <interfacename>Callable&lt;?&gt;</interfacename> can
be returned when the application wants to produce the return
value asynchronously in a thread managed by Spring MVC.</para>
</listitem>
<listitem>
<para>A <classname>DeferredResult&lt;?&gt;</classname> can
be returned when the application wants to produce the return
value from a thread of its own choosing.</para>
</listitem>
<listitem>
<para>Any other return type is considered to be a single model
attribute to be exposed to the view, using the attribute name
@ -1576,6 +1588,7 @@ public String processSubmit(<emphasis role="bold">@ModelAttribute("pet") Pet pet
objects and the results of <literal>@ModelAttribute</literal>
annotated reference data accessor methods.</para>
</listitem>
</itemizedlist></para>
</section>
@ -2359,6 +2372,228 @@ public String myHandleMethod(WebRequest webRequest, Model model) {
</section>
</section>
<section xml:id="mvc-ann-async">
<title>Asynchronous Request Processing</title>
<para>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 <interfacename>java.util.concurrent.Callable</interfacename>
and produce the return value from a separate thread. Meanwhile the main Servlet
container thread is released and allowed to process other requests.
Spring MVC invokes the <interfacename>Callable</interfacename> in a
separate thread with the help of a <interfacename>TaskExecutor</interfacename>
and when the <interfacename>Callable</interfacename> returns, the
request is dispatched back to the Servlet container to resume
processing with the value returned by the
<interfacename>Callable</interfacename>.
Here is an example controller method:</para>
<programlisting language="java">
@RequestMapping(method=RequestMethod.POST)
public Callable&lt;String&gt; processUpload(final MultipartFile file) {
return new Callable&lt;String&gt;() {
public Object call() throws Exception {
// ...
return "someView";
}
};
}</programlisting>
<para>A second option is for the controller to return an instance of
<classname>DeferredResult</classname>. 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:</para>
<programlisting language="java">
@RequestMapping("/quotes")
@ResponseBody
public DeferredResult&lt;String&gt; quotes() {
DeferredResult&lt;String&gt; deferredResult = new DeferredResult&lt;String&gt;();
// Save the deferredResult in in-memory queue ...
return deferredResult;
}
// In some other thread...
deferredResult.setResult(data);
</programlisting>
<para>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:</para>
<itemizedlist>
<listitem>
<para>A <interfacename>ServletRequest</interfacename>
can be put in asynchronous mode by calling
<code>request.startAsync()</code>. 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.
</para>
</listitem>
<listitem>
<para>The call to <code>request.startAsync()</code> returns an
<interfacename>AsyncContext</interfacename>, which can be used for
further control over async processing. For example it provides
the method <code>dispatch</code>, 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.</para>
</listitem>
<listitem>
<para><interfacename>ServletRequest</interfacename> provides access
to the current <interfacename>DispatcherType</interfacename>, which
can be used to distinguish if a <interfacename>Servlet</interfacename> or
a <interfacename>Filter</interfacename> is processing on
the initial request processing thread and when it is processing in
an async dispatch.</para>
</listitem>
</itemizedlist>
<para>With the above in mind, the following is the sequence
of events for async request processing with a <interfacename>Callable</interfacename>:
(1) Controller returns a
<interfacename>Callable</interfacename>, (2) Spring MVC starts async processing
and submits the <interfacename>Callable</interfacename>
to a <interfacename>TaskExecutor</interfacename>
for processing in a separate thread, (3) the <classname>DispatcherServlet</classname>
and all Filter's exit the request processing thread but the response
remains open, (4) the <interfacename>Callable</interfacename> produces a result
and Spring MVC dispatches the request back to the Servlet container,
(5) the <classname>DispatcherServlet</classname> is invoked again and processing
resumes with the asynchronously produced result from the
<interfacename>Callable</interfacename>. The exact sequencing of (2),
(3), and (4) may vary depending on the speed of execution of the
concurrent threads.
</para>
<para>The sequence of events for async request processing with a
<classname>DeferredResult</classname> is the same in principal except
it's up to the application to produce the asynchronous result from some thread:
(1) Controller returns a <classname>DeferredResult</classname> and saves it
in some in-memory queue or list where it can be accessed,
(2) Spring MVC starts async processing, (3) the <classname>DispatcherServlet</classname>
and all configured Filter's exit the request processing thread but the response
remains open, (4) the application sets the <classname>DeferredResult</classname>
from some thread and Spring MVC dispatches the request back to the Servlet container,
(5) the <classname>DispatcherServlet</classname> is invoked again and processing
resumes with the asynchronously produced result.
</para>
<para>Explaining the motivation for async request processing, when or why to use it
is beyond the scope of this document. For further information you may wish to read
<link xl:href="http://http://blog.springsource.org/2012/05/06/spring-mvc-3-2-preview-introducing-servlet-3-async-support/"> this blog post series</link>.
</para>
<section xml:id="mvc-ann-async-exceptions">
<title>Async Request Processing and Exception Handling</title>
<para>What happens if a <interfacename>Callable</interfacename> 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
<interfacename>@ExceptionHandler</interfacename> method in the same
controller or by one of the configured
<interfacename>HandlerExceptionResolver</interfacename> instances.</para>
<note>
<para>Under the covers, when a <interfacename>Callable</interfacename>
raises an Exception, Spring MVC still dispatches to the Servlet
container to resume processing. The only difference is that the
result of executing the <interfacename>Callable</interfacename>
is an <classname>Exception</classname> that must be processed
with the configured
<interfacename>HandlerExceptionResolver</interfacename> instances.</para>
</note>
<para>When using a <classname>DeferredResult</classname>, you have
a choice of calling its <code>setErrorResult(Object)</code> method
and provide an <classname>Exception</classname> or any other Object
you'd like to use as the result. If the result is an
<classname>Exception</classname>, it will be processed with a
matching <interfacename>@ExceptionHandler</interfacename> method in the
same controller or with any configured
<interfacename>HandlerExceptionResolver</interfacename> instance.</para>
</section>
<section xml:id="mvc-ann-async-interception">
<title>Intercepting Async Requests</title>
<para>An existing <interfacename>HandlerInterceptor</interfacename> can
implement <interfacename>AsyncHandlerInterceptor</interfacename>, which
provides one additional method <code>afterConcurrentHandlingStarted</code>.
It is invoked after async processing starts and when the initial
request processing thread is being exited. See the Javadoc of
<interfacename>AsyncHandlerInterceptor</interfacename> for more details
on that.</para>
<para>Further options for async request lifecycle callbacks are
provided directly on <classname>DeferredResult</classname>,
which has the methods <code>onTimeout(Runnable)</code> and
<code>onCompletion(Runnable)</code>. 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
<classname>DeferredResult</classname> to some value.
The completion callback however is final and the result can no
longer be set.</para>
<para>Similar callbacks are also available with a
<interfacename>Callable</interfacename>. However, you will need to wrap
the <interfacename>Callable</interfacename> in an instance of
<classname>WebAsyncTask</classname> and then use that to register
the timeout and completion callbacks. Just like with
<classname>DeferredResult</classname>, the timeout event can be
handled and a value can be returned while the completion event is final.</para>
<para>You can also register a
<interfacename>CallableProcessingInterceptor</interfacename> or a
<interfacename>DeferredResultProcessingInterceptor</interfacename>
globally through the MVC Java config or the MVC namespace.
Those interceptors provide a full set of callbacks and apply every
time a <interfacename>Callable</interfacename> or a
<classname>DeferredResult</classname> is used.</para>
</section>
<section xml:id="mvc-ann-async-configuration">
<title>Async Request Configuration</title>
<para>The MVC Java config and the MVC namespace provide options for
configuring async request processing.
<interfacename>WebMvcConfigurer</interfacename> has the method
<code>configureAsyncSupport</code> while &lt;mvc:annotation-driven&gt;
has an &lt;async-support&gt; sub-element.</para>
<para>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 can also configure an
<interfacename>AsyncTaskExecutor</interfacename> to use for executing
<interfacename>Callable</interfacename> instances returned from
controller methods. It is highly recommended to configure this property
since by default Spring MVC uses
<classname>SimpleAsyncTaskExecutor</classname>. The MVC Java config
and the MVC namespace also allow you to register
<interfacename>CallableProcessingInterceptor</interfacename> and
<interfacename>DeferredResultProcessingInterceptor</interfacename>
instances.</para>
<para>If you need to override the default timeout value for a
specific <classname>DeferredResult</classname>, you can do so by using
the appropriate class constructor. Similarly, for a
<interfacename>Callable</interfacename>, you can wrap it in a
<classname>WebAsyncTask</classname> and use the appropriate class
constructor to customize the timeout value. The class constructor of
<classname>WebAsyncTask</classname> also allows providing
an <interfacename>AsyncTaskExecutor</interfacename>.</para>
</section>
</section>
<section xml:id="mvc-ann-tests">
<title>Testing Controllers</title>

View File

@ -37,10 +37,8 @@
</listitem>
</itemizedlist>
<para>See <link
xl:href="http://blog.springsource.org/2012/05/06/spring-mvc-3-2-preview-introducing-servlet-3-async-support/">
Introducing Servlet 3 Async Support</link> (SpringSource team
blog).</para>
<para>See <xref linkend="mvc-ann-async"/>.</para>
</section>
<section xml:id="new-in-3.2-spring-mvc-test">
@ -258,5 +256,6 @@
linkend="testcontext-ctx-management-initializers">ApplicationContextInitializers</link></para>
</listitem>
</itemizedlist>
</section>
</chapter>