Last updated on November 5th, 2012 (Spring MVC 3.2 RC1)
In previous posts I introduced the Servlet 3 based async capability in Spring MVC 3.2 and discussed techniques for real-time updates. In this post I'll go into more technical details and discuss how asynchronous processing fits into the Spring MVC request lifecycle.
As a quick reminder, you can make any existing controller method asynchronous by changing it to return a Callable. For example a controller method that returns a view name, can return
Callable<String> instead. An
@ResponseBody that returns an object called
Person can return
Callable<Person> instead. And the same is true for any other controller return value type.
A central idea is that all of what you already know about how a controller method works remains unchanged as much as possible except that the remaining processing will occur in another thread. When it comes to asynchronous execution it's important to keep things simple. As you'll see even with this seemingly simple programming model change, there is quite a bit to consider.
The spring-mvc-showcase has been updated for Spring MVC 3.2. Have a look at CallableController. Method annotations like
@ResponseStatus apply to the return value from the
Callable as well, as you might expect. Exceptions raised from a
Callable are handled as if they were raised by the controller, in this case with an
@ExceptionHandler method. And so on.
If you execute one of the
CallableController methods through the "Async Requests" tab in the browser, you should see output similar to the one below:
08:25:15 [http-bio-8080-exec-10] DispatcherServlet - DispatcherServlet with name 'appServlet' processing GET request for [...] 08:25:15 [http-bio-8080-exec-10] RequestMappingHandlerMapping - Looking up handler method for path /async/callable/view 08:25:15 [http-bio-8080-exec-10] RequestMappingHandlerMapping - Returning handler method [...] 08:25:15 [http-bio-8080-exec-10] WebAsyncManager - Concurrent handling starting for GET [...] 08:25:15 [http-bio-8080-exec-10] DispatcherServlet - Leaving response open for concurrent processing 08:25:17 [MvcAsync1] WebAsyncManager - Concurrent result value [views/html] 08:25:17 [MvcAsync1] WebAsyncManager - Dispatching request to resume processing 08:25:17 [http-bio-8080-exec-6] DispatcherServlet - DispatcherServlet with name 'appServlet' resumed processing GET request for [...] 08:25:17 [http-bio-8080-exec-6] RequestMappingHandlerMapping - Looking up handler method for path /async/callable/view 08:25:17 [http-bio-8080-exec-6] RequestMappingHandlerMapping - Returning handler method [...] 08:25:17 [http-bio-8080-exec-6] RequestMappingHandlerAdapter - Found concurrent result value [views/html] 08:25:17 [http-bio-8080-exec-6] DispatcherServlet - Rendering view [...] in DispatcherServlet with name 'appServlet' 08:25:17 [http-bio-8080-exec-6] JstlView - Added model object 'fruit' of type [java.lang.String] 08:25:17 [http-bio-8080-exec-6] JstlView - Added model object 'foo' of type [java.lang.String] 08:25:17 [http-bio-8080-exec-6] JstlView - Forwarding to resource [/WEB-INF/views/views/html.jsp] 08:25:17 [http-bio-8080-exec-6] DispatcherServlet - Successfully completed request
Notice how the initial Servlet container thread exits quickly after logging a message that concurrent handling has started. That's because the controller method returned a
Callable. A second thread -- managed by Spring MVC through an
AsyncTaskExecutor -- invokes the
Callable to produce a value, in this case a String-based view name, and then the request is dispatched back to the Servlet container. Finally, in a third Servlet container thread (the dispatch), processing is completed by rendering the selected view. If you look at the timestamps you'll notice a 2 second, simulated delay between when the initial thread exits and the
Callable is ready.
Note: if you are not familiar with the Servlet 3 async API, an async dispatch is similar to forwarding except a forward occurs in the same thread while a dispatch is used from an application thread to resume processing in a servlet container thread.
By default Spring MVC uses a SimpleAsyncTaskExecutor to execute
Callable instances returned by controller methods. For production you must replace it with an
AsyncTaskExecutor implementation configured appropriately for your environment. The MVC Java config and the MVC namespace both provide options to configure an
AsyncTaskExecutor and async request processing in general. You can also configure the
If an async request does not complete processing within a certain amount of time, the Servlet container raises a timeout event and if not handled, the response is completed. You can configure the timeout value through the MVC Java config and the MVC namespace, or directly on the
RequestMappingHandlerAdapter. If not configured, the timeout value will depend on the underlying Servlet container. On Tomcat it is 10 seconds and it starts after the initial Servlet container thread exits all the way out.
What if you want to customize the timeout value or the task executor for a specific controller method? For such occasions, you can wrap the Callable in an instance of MvcAsyncTask. The constructor of
MvcAsyncTask accepts a timeout value and a task executor. Furthermore, it provides
onCompletion methods that allow you to register for "timeout" and "completion" callbacks. Like "finally" in a try-catch block, "completion" always takes place when an async request completes. The "timeout" callback occurs prior to "completion" and can select an alternative value to use to complete processing as well as notify the
Callable to stop processing.
The following is the sequence of events in a timeout scenario:
- Controller method returns a
Callablewrapped in an
- Spring MVC begins execution of the
Callablein a separate thread
- The Servlet container thread exits (and the timeout period begins)
MvcAsyncTaskis notified of a callback
- The callback code selects an alternative value and notifies the
Callableto cancel processing
- The request is dispatched back to the container to complete processing with the alternate value
To fully understand the above scenario consider the threads involved -- the initial Servlet container thread where request processing begins, the Spring MVC managed thread where the
Callable executes, the Servlet container thread in which the timeout event is raised, and the Servlet container thread processing the final async dispatch.
When an Exception is raised by a
Callable, it is handled through the
HandlerExceptionResolver mechanism just like exceptions raised by any other controller method. The more detailed explanation is that the exception is caught and saved, and the request is dispatched to the Servlet container where processing resumes and the
HandlerExceptionResolver chain invoked. This also means that
@ExceptionHandler methods will be invoked as usual.
preHandle method of a
HandlerInterceptor is invoked as usual from the initial Servlet container thread. If the controller returns a
Callable and async processing starts, there is neither a result nor is the request complete. Therefore
afterCompletion are not invoked in the initial Servlet container thread. Instead interceptors can implement
AsyncHandlerInterceptor, a sub-interface, and the
afterConcurrentHandlingStarted method. After the
Callable is done and the request dispatched to the Servlet container, all methods of the
HandlerInterceptor are invoked in the dispatched thread.
All Spring Framework Servlet filter implementations have been modified as necessary to work in asynchronous request processing. As for any other filters, some will work -- typically those that do pre-processing, and others will need to be modified -- typically those that do post-processing at the end of a request. Such filters will need to recognize when the initial Servlet container thread is being exited, making way for another thread to continue processing, and when they are invoked as part of an asynchronous dispatch to complete processing.
OpenEntityManagerInViewFilter have been updated to work transparently over the span the entire async request. However, if using
@Transactional directly on a controller method, the transaction will complete as soon as the controller method returns and will not extend to the execution of the
Callable. If the
Callable needs to do transactional work it should delegate to a bean with
The next post explores the use of
DeferredResult for async processing by modifying an existing sample from the Spring AMQP project that reacts to AMQP messages and sends updates to the browser.