Spring MVC Support for Servlet 3 Asynchronous Requests
Spring MVC supports Servlet3 asynchronous requests in two ways, one is to return Callable and the other is Deferred Result through the processor method. According to the specification of Servlet 3, it is necessary to configure the corresponding Servlet and Filter to support asynchronous requests when supporting asynchronous requests. In order to enable Spring MVC to support asynchronous requests processing, it is necessary to configure Dispatcher Servlet to support asynchronous requests when defining Dispatcher Servlet. Filter defined before Dispatcher Servlet also needs to configure to support asynchronous requests.
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <!-- Enabling Asynchronous Support --> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
Return Callable
When the return method of the processor is Callable type, the asynchronous request is initiated by default, and a TaskExecutor is used to call the returned Callable. The subsequent processing is the same as the normal Spring MVC request. Callable's return result is the same as the normal request for Spring MVC. It can return Model, Model AndView, String, Object, etc. or it can be used in conjunction with @ResponseBody, referring specifically to handleReturnValue() of Callable MethodReturnValueHandler.
@RequestMapping("/callable") public Callable<String> forCallable(Model model) throws Exception { return () -> { TimeUnit.SECONDS.sleep(1);//Sleep for 1 second, mimic some business operations model.addAttribute("a", "aaaaaaa"); return "async_request_callable"; }; }
If you need to specify a timeout for a single Callable request, we can wrap the Callable in a WebAsyncTask. You can then specify timeout callbacks and callbacks completed by normal processing.
@RequestMapping("/callable/timeout") public WebAsyncTask<String> forCallableWithTimeout(Model model) throws Exception { long timeout = 5 * 1000L; WebAsyncTask<String> asyncTask = new WebAsyncTask<>(timeout, () -> { TimeUnit.MILLISECONDS.sleep(timeout + 10); model.addAttribute("a", "aaaaaaa"); return "async_request_callable"; }); asyncTask.onTimeout(() -> { System.out.println("Response timeout callback"); return "async_request_callable_timeout"; }); asyncTask.onCompletion(() -> { System.out.println("response callable Callback completed"); }); return asyncTask; }
Return Deferred Result
Programming using DeferredResult to return results usually creates a DeferredResult instance in the processor method, saves it and returns it, such as saving it in a queue, and then retrieves the corresponding DeferredResult object from the queue in another thread for corresponding business processing, and sets the corresponding return value in DeferredResult. . After returning to Deferred Result, Spring MVC will create a Deferred Result Handler to listen for Deferred Result. Once the return value is set in Deferred Result, Deferred Result Handler will process the return value. For the process of Deferred Result, see handleReturnValue() of Deferred Result MethodReturnValueHandler.
@RequestMapping("/deferredresult") public DeferredResult<String> forDeferredResult() throws Exception { DeferredResult<String> result = new DeferredResult<>(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } result.setResult("async_request_deferredresult"); }).start(); return result; }
DeferredResult can also specify the timeout time and the callback after the timeout separately. Its timeout time can be passed directly through the constructor in milliseconds.
@RequestMapping("/deferredresult/timeout") public DeferredResult<String> forDeferredResultWithTimeout() throws Exception { DeferredResult<String> result = new DeferredResult<>(10 * 1000); new Thread(() -> { try { TimeUnit.SECONDS.sleep(31); } catch (InterruptedException e) { e.printStackTrace(); } result.setResult("async_request_deferredresult"); }).start(); result.onTimeout(() -> { System.out.println("Response timeout callback function"); }); result.onCompletion(() -> { System.out.println("Callback function of response completion"); }); return result; }
To configure
The default timeout time and TaskExecutor required to process asynchronous requests can be defined by <mvc: annotation-driven/> child element <mvc: async-support/>. If no default timeout is specified, the container's asynchronous request timeout is used by default, and if no TaskExecutor is specified, a SimpleAsyncTaskExecutor is used by default. In the following configuration, we configure the default timeout of 15 seconds, and the TaskExecutor that handles asynchronous requests is the TaskExecutor named asyncTaskExecutor in the bean container.
<mvc:annotation-driven> <mvc:async-support default-timeout="15000" task-executor="asyncTaskExecutor"/> </mvc:annotation-driven>
Interceptor
Requests returning to Callable type can be intercepted by implementing the Callable Processing Interceptor interface to customize an interceptor, or by inheriting the Callable Processing Interceptor Adapter abstract class to define an interceptor, so that only the method of interest is needed to implement. The Callable Processing Interceptor interface is defined as follows:
public interface CallableProcessingInterceptor { static final Object RESULT_NONE = new Object(); static final Object RESPONSE_HANDLED = new Object(); /** * Invoked <em>before</em> the start of concurrent handling in the original * thread in which the {@code Callable} is submitted for concurrent handling. * * <p> * This is useful for capturing the state of the current thread just prior to * invoking the {@link Callable}. Once the state is captured, it can then be * transferred to the new {@link Thread} in * {@link #preProcess(NativeWebRequest, Callable)}. Capturing the state of * Spring Security's SecurityContextHolder and migrating it to the new Thread * is a concrete example of where this is useful. * </p> * * @param request the current request * @param task the task for the current async request * @throws Exception in case of errors */ <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception; /** * Invoked <em>after</em> the start of concurrent handling in the async * thread in which the {@code Callable} is executed and <em>before</em> the * actual invocation of the {@code Callable}. * * @param request the current request * @param task the task for the current async request * @throws Exception in case of errors */ <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception; /** * Invoked <em>after</em> the {@code Callable} has produced a result in the * async thread in which the {@code Callable} is executed. This method may * be invoked later than {@code afterTimeout} or {@code afterCompletion} * depending on when the {@code Callable} finishes processing. * * @param request the current request * @param task the task for the current async request * @param concurrentResult the result of concurrent processing, which could * be a {@link Throwable} if the {@code Callable} raised an exception * @throws Exception in case of errors */ <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception; /** * Invoked from a container thread when the async request times out before * the {@code Callable} task completes. Implementations may return a value, * including an {@link Exception}, to use instead of the value the * {@link Callable} did not return in time. * * @param request the current request * @param task the task for the current async request * @return a concurrent result value; if the value is anything other than * {@link #RESULT_NONE} or {@link #RESPONSE_HANDLED}, concurrent processing * is resumed and subsequent interceptors are not invoked * @throws Exception in case of errors */ <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception; /** * Invoked from a container thread when async processing completes for any * reason including timeout or network error. * * @param request the current request * @param task the task for the current async request * @throws Exception in case of errors */ <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception; }
It is configured through <mvc: callable-interceptors/>.
<mvc:annotation-driven> <mvc:async-support default-timeout="15000" task-executor="asyncTaskExecutor"> <mvc:callable-interceptors> <bean class="YourCallableProcessingInterceptor"/> </mvc:callable-interceptors> </mvc:async-support> </mvc:annotation-driven>
Returning to Deferred Result can also be intercepted, which requires us to implement the Deferred Result Processing Interceptor interface or inherit from the Deferred Result Processing Interceptor Adapter. Deferred ResultProcessing Interceptor interface is defined as follows:
public interface DeferredResultProcessingInterceptor { /** * Invoked immediately before the start of concurrent handling, in the same * thread that started it. This method may be used to capture state just prior * to the start of concurrent processing with the given {@code DeferredResult}. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @throws Exception in case of errors */ <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; /** * Invoked immediately after the start of concurrent handling, in the same * thread that started it. This method may be used to detect the start of * concurrent processing with the given {@code DeferredResult}. * * <p>The {@code DeferredResult} may have already been set, for example at * the time of its creation or by another thread. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @throws Exception in case of errors */ <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; /** * Invoked after a {@code DeferredResult} has been set, via * {@link DeferredResult#setResult(Object)} or * {@link DeferredResult#setErrorResult(Object)}, and is also ready to * handle the concurrent result. * * <p>This method may also be invoked after a timeout when the * {@code DeferredResult} was created with a constructor accepting a default * timeout result. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @param concurrentResult the result to which the {@code DeferredResult} * @throws Exception in case of errors */ <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception; /** * Invoked from a container thread when an async request times out before * the {@code DeferredResult} has been set. Implementations may invoke * {@link DeferredResult#setResult(Object) setResult} or * {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume processing. * * @param request the current request * @param deferredResult the DeferredResult for the current request; if the * {@code DeferredResult} is set, then concurrent processing is resumed and * subsequent interceptors are not invoked * @return {@code true} if processing should continue, or {@code false} if * other interceptors should not be invoked * @throws Exception in case of errors */ <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; /** * Invoked from a container thread when an async request completed for any * reason including timeout and network error. This method is useful for * detecting that a {@code DeferredResult} instance is no longer usable. * * @param request the current request * @param deferredResult the DeferredResult for the current request * @throws Exception in case of errors */ <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception; }
The custom Deferred Result Processing Interceptor is configured through <mvc: deferred-result-interceptors>.
<mvc:annotation-driven> <mvc:async-support default-timeout="15000" task-executor="asyncTaskExecutor"> <mvc:deferred-result-interceptors> <bean class="YourDeferredResultProcessingInterceptor"/> </mvc:deferred-result-interceptors> </mvc:async-support> </mvc:annotation-driven>
When an asynchronous request is initiated, the postHandle() and afterCompletion() of Spring MVC's traditional Handler Interceptor will not execute, but they will execute after the asynchronous request is completed. If you need to do something after asynchronous processing is completed, you can also choose to implement afterConcurrent Handling Started () of the AsyncHandler Interceptor interface, which inherits Handler Interceptor.
(Note: This article is based on Spring 4.1.0)