Add onTimeout/onCompletion callbacks to DeferredResult

Issue: SPR-9914
This commit is contained in:
Rossen Stoyanchev 2012-10-30 21:55:38 -04:00
parent 3a09644843
commit d701464517
21 changed files with 309 additions and 158 deletions

View File

@ -178,7 +178,7 @@ public class OpenSessionInViewTests {
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject()); asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
replay(asyncWebRequest); replay(asyncWebRequest);
@ -494,7 +494,7 @@ public class OpenSessionInViewTests {
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject()); asyncWebRequest.addTimeoutHandler(EasyMock.<Runnable>anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes(); expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();

View File

@ -20,7 +20,6 @@ import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.verify;
@ -49,8 +48,8 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.AsyncWebRequest; import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext;
/** /**
@ -155,7 +154,7 @@ public class OpenEntityManagerInViewTests extends TestCase {
AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createStrictMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject()); asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
replay(asyncWebRequest); replay(asyncWebRequest);
@ -346,7 +345,7 @@ public class OpenEntityManagerInViewTests extends TestCase {
AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class); AsyncWebRequest asyncWebRequest = createMock(AsyncWebRequest.class);
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.setTimeoutHandler((Runnable) anyObject()); asyncWebRequest.addTimeoutHandler((Runnable) anyObject());
asyncWebRequest.addCompletionHandler((Runnable) anyObject()); asyncWebRequest.addCompletionHandler((Runnable) anyObject());
asyncWebRequest.startAsync(); asyncWebRequest.startAsync();
expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes(); expect(asyncWebRequest.isAsyncStarted()).andReturn(true).anyTimes();

View File

@ -48,7 +48,7 @@ import org.springframework.web.servlet.ModelAndView;
@SuppressWarnings("serial") @SuppressWarnings("serial")
final class TestDispatcherServlet extends DispatcherServlet { final class TestDispatcherServlet extends DispatcherServlet {
private static final String KEY = TestDispatcherServlet.class.getName() + "-interceptor"; private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
/** /**
* Create a new instance with the given web application context. * Create a new instance with the given web application context.

View File

@ -28,7 +28,7 @@ import org.hamcrest.Matchers;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.web.context.request.async.AsyncTask; import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
/** /**
@ -97,7 +97,7 @@ public class RequestResultMatchers {
/** /**
* Assert the result from asynchronous processing. * Assert the result from asynchronous processing.
* This method can be used when a controller method returns {@link Callable} * This method can be used when a controller method returns {@link Callable}
* or {@link AsyncTask}. The value matched is the value returned from the * or {@link MvcAsyncTask}. The value matched is the value returned from the
* {@code Callable} or the exception raised. * {@code Callable} or the exception raised.
*/ */
public <T> ResultMatcher asyncResult(Object expectedResult) { public <T> ResultMatcher asyncResult(Object expectedResult) {

View File

@ -37,12 +37,12 @@ public interface AsyncWebRequest extends NativeWebRequest {
void setTimeout(Long timeout); void setTimeout(Long timeout);
/** /**
* Set the handler to use when concurrent handling has timed out. * Add a handler to invoke when concurrent handling has timed out.
*/ */
void setTimeoutHandler(Runnable runnable); void addTimeoutHandler(Runnable runnable);
/** /**
* Add a Runnable to be invoked when request processing completes. * Add a handle to invoke when request processing completes.
*/ */
void addCompletionHandler(Runnable runnable); void addCompletionHandler(Runnable runnable);

View File

@ -39,8 +39,8 @@ class CallableInterceptorChain {
private int preProcessIndex = -1; private int preProcessIndex = -1;
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) { public CallableInterceptorChain(List<CallableProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<CallableProcessingInterceptor>(interceptors); this.interceptors = interceptors;
} }
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception { public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {

View File

@ -20,6 +20,7 @@ import java.util.concurrent.Callable;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
/** /**
* {@code DeferredResult} provides an alternative to using a {@link Callable} * {@code DeferredResult} provides an alternative to using a {@link Callable}
@ -41,6 +42,10 @@ public final class DeferredResult<T> {
private final Object timeoutResult; private final Object timeoutResult;
private Runnable timeoutCallback;
private Runnable completionCallback;
private DeferredResultHandler resultHandler; private DeferredResultHandler resultHandler;
private Object result = RESULT_NONE; private Object result = RESULT_NONE;
@ -56,7 +61,7 @@ public final class DeferredResult<T> {
} }
/** /**
* Create a DeferredResult with a timeout. * Create a DeferredResult with a timeout value.
* @param timeout timeout value in milliseconds * @param timeout timeout value in milliseconds
*/ */
public DeferredResult(long timeout) { public DeferredResult(long timeout) {
@ -64,7 +69,8 @@ public final class DeferredResult<T> {
} }
/** /**
* Create a DeferredResult with a timeout and a default result to use on timeout. * Create a DeferredResult with a timeout value and a default result to use
* in case of timeout.
* @param timeout timeout value in milliseconds; ignored if {@code null} * @param timeout timeout value in milliseconds; ignored if {@code null}
* @param timeoutResult the result to use * @param timeoutResult the result to use
*/ */
@ -73,13 +79,48 @@ public final class DeferredResult<T> {
this.timeout = timeout; this.timeout = timeout;
} }
/**
* Return {@code true} if this DeferredResult is no longer usable either
* because it was previously set or because the underlying request expired.
* <p>
* The result may have been set with a call to {@link #setResult(Object)},
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a
* timeout result was provided to the constructor. The request may also
* expire due to a timeout or network error.
*/
public boolean isSetOrExpired() {
return ((this.result != RESULT_NONE) || this.expired);
}
/** /**
* Return the configured timeout value in milliseconds. * Return the configured timeout value in milliseconds.
*/ */
public Long getTimeoutMilliseconds() { Long getTimeoutValue() {
return this.timeout; return this.timeout;
} }
/**
* Register code to invoke when the async request times out. This method is
* called from a container thread when an async request times out before the
* {@code DeferredResult} has been set. It may invoke
* {@link DeferredResult#setResult(Object) setResult} or
* {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume
* processing.
*/
public void onTimeout(Runnable callback) {
this.timeoutCallback = callback;
}
/**
* Register code to invoke when the async request completes. This method is
* called 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.
*/
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
}
/** /**
* Provide a handler to use to handle the result value. * Provide a handler to use to handle the result value.
* @param resultHandler the handler * @param resultHandler the handler
@ -138,33 +179,29 @@ public final class DeferredResult<T> {
return setResultInternal(result); return setResultInternal(result);
} }
/** DeferredResultProcessingInterceptor getInterceptor() {
* Return {@code true} if this DeferredResult is no longer usable either return new DeferredResultProcessingInterceptorAdapter() {
* because it was previously set or because the underlying request expired.
* <p> @Override
* The result may have been set with a call to {@link #setResult(Object)}, public <S> void afterTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) {
* or {@link #setErrorResult(Object)}, or as a result of a timeout, if a if (timeoutCallback != null) {
* timeout result was provided to the constructor. The request may also timeoutCallback.run();
* expire due to a timeout or network error. }
*/ if (DeferredResult.this.timeoutResult != RESULT_NONE) {
public boolean isSetOrExpired() { setResultInternal(timeoutResult);
return ((this.result != RESULT_NONE) || this.expired); }
} }
/** @Override
* Mark this instance expired so it may no longer be used. public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) {
* @return the previous value of the expiration flag
*/
boolean expire() {
synchronized (this) { synchronized (this) {
boolean previous = this.expired; expired = true;
this.expired = true; }
return previous; if (completionCallback != null) {
completionCallback.run();
} }
} }
};
boolean applyTimeoutResult() {
return (this.timeoutResult != RESULT_NONE) ? setResultInternal(this.timeoutResult) : false;
} }

View File

@ -15,8 +15,6 @@
*/ */
package org.springframework.web.context.request.async; package org.springframework.web.context.request.async;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -38,8 +36,8 @@ class DeferredResultInterceptorChain {
private int preProcessingIndex = -1; private int preProcessingIndex = -1;
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) { public DeferredResultInterceptorChain(List<DeferredResultProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors); this.interceptors = interceptors;
} }
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception { public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {

View File

@ -75,7 +75,7 @@ public interface DeferredResultProcessingInterceptor {
* Invoked from a container thread when an async request times out before * Invoked from a container thread when an async request times out before
* the {@code DeferredResult} has been set. Implementations may invoke * the {@code DeferredResult} has been set. Implementations may invoke
* {@link DeferredResult#setResult(Object) setResult} or * {@link DeferredResult#setResult(Object) setResult} or
* {@link DeferredResult#setErrorResult(Object) to resume processing. * {@link DeferredResult#setErrorResult(Object) setErrorResult} to resume processing.
* *
* @param request the current request * @param request the current request
* @param deferredResult the DeferredResult for the current request; if the * @param deferredResult the DeferredResult for the current request; if the

View File

@ -20,6 +20,7 @@ import java.util.concurrent.Callable;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
/** /**
* Holder for a {@link Callable}, a timeout value, and a task executor. * Holder for a {@link Callable}, a timeout value, and a task executor.
@ -27,7 +28,7 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
*/ */
public class AsyncTask<V> { public class MvcAsyncTask<V> {
private final Callable<V> callable; private final Callable<V> callable;
@ -37,40 +38,51 @@ public class AsyncTask<V> {
private final AsyncTaskExecutor executor; private final AsyncTaskExecutor executor;
private Callable<V> timeoutCallback;
private Runnable completionCallback;
private BeanFactory beanFactory; private BeanFactory beanFactory;
/** /**
* Create an AsyncTask with a timeout value and a Callable. * Create an {@code MvcAsyncTask} wrapping the given {@link Callable}.
* @param timeout timeout value in milliseconds
* @param callable the callable for concurrent handling * @param callable the callable for concurrent handling
*/ */
public AsyncTask(long timeout, Callable<V> callable) { public MvcAsyncTask(Callable<V> callable) {
this(timeout, null, null, callable); this(null, null, null, callable);
Assert.notNull(timeout, "Timeout must not be null");
} }
/** /**
* Create an AsyncTask with a timeout value, an executor name, and a Callable. * Create an {@code MvcAsyncTask} with a timeout value and a {@link Callable}.
* @param timeout timeout value in milliseconds
* @param callable the callable for concurrent handling
*/
public MvcAsyncTask(long timeout, Callable<V> callable) {
this(timeout, null, null, callable);
}
/**
* Create an {@code MvcAsyncTask} with a timeout value, an executor name, and a {@link Callable}.
* @param timeout timeout value in milliseconds; ignored if {@code null} * @param timeout timeout value in milliseconds; ignored if {@code null}
* @param callable the callable for concurrent handling * @param callable the callable for concurrent handling
*/ */
public AsyncTask(Long timeout, String executorName, Callable<V> callable) { public MvcAsyncTask(Long timeout, String executorName, Callable<V> callable) {
this(timeout, null, executorName, callable); this(timeout, null, executorName, callable);
Assert.notNull(executor, "Executor name must not be null"); Assert.notNull(executor, "Executor name must not be null");
} }
/** /**
* Create an AsyncTask with a timeout value, an executor instance, and a Callable. * Create an {@code MvcAsyncTask} with a timeout value, an executor instance, and a Callable.
* @param timeout timeout value in milliseconds; ignored if {@code null} * @param timeout timeout value in milliseconds; ignored if {@code null}
* @param callable the callable for concurrent handling * @param callable the callable for concurrent handling
*/ */
public AsyncTask(Long timeout, AsyncTaskExecutor executor, Callable<V> callable) { public MvcAsyncTask(Long timeout, AsyncTaskExecutor executor, Callable<V> callable) {
this(timeout, executor, null, callable); this(timeout, executor, null, callable);
Assert.notNull(executor, "Executor must not be null"); Assert.notNull(executor, "Executor must not be null");
} }
private AsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable<V> callable) { private MvcAsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable<V> callable) {
Assert.notNull(callable, "Callable must not be null"); Assert.notNull(callable, "Callable must not be null");
this.callable = callable; this.callable = callable;
this.timeout = timeout; this.timeout = timeout;
@ -111,11 +123,50 @@ public class AsyncTask<V> {
/** /**
* A {@link BeanFactory} to use to resolve an executor name. Applications are * A {@link BeanFactory} to use to resolve an executor name. Applications are
* not expected to have to set this property when AsyncTask is used in a * not expected to have to set this property when {@code MvcAsyncTask} is used in a
* Spring MVC controller. * Spring MVC controller.
*/ */
public void setBeanFactory(BeanFactory beanFactory) { public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory; this.beanFactory = beanFactory;
} }
/**
* Register code to invoke when the async request times out. This method is
* called from a container thread when an async request times out before the
* {@code Callable} has completed. The callback is executed in the same
* thread and therefore should return without blocking. It may return an
* alternative value to use, including an {@link Exception} or return
* {@link CallableProcessingInterceptor#RESULT_NONE RESULT_NONE}.
*/
public void onTimeout(Callable<V> callback) {
this.timeoutCallback = callback;
}
/**
* Register code to invoke when the async request completes. This method is
* called from a container thread when an async request completed for any
* reason including timeout and network error.
*/
public void onCompletion(Runnable callback) {
this.completionCallback = callback;
}
CallableProcessingInterceptor getInterceptor() {
return new CallableProcessingInterceptorAdapter() {
@Override
public <T> Object afterTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
return (timeoutCallback != null) ? timeoutCallback.call() : CallableProcessingInterceptor.RESULT_NONE;
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
if (completionCallback != null) {
completionCallback.run();
}
}
};
}
} }

View File

@ -41,7 +41,7 @@ public class NoSupportAsyncWebRequest extends ServletWebRequest implements Async
// ignored // ignored
} }
public void setTimeoutHandler(Runnable runnable) { public void addTimeoutHandler(Runnable runnable) {
// ignored // ignored
} }

View File

@ -49,7 +49,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
private AtomicBoolean asyncCompleted = new AtomicBoolean(false); private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
private Runnable timeoutHandler; private final List<Runnable> timeoutHandlers = new ArrayList<Runnable>();
private final List<Runnable> completionHandlers = new ArrayList<Runnable>(); private final List<Runnable> completionHandlers = new ArrayList<Runnable>();
@ -73,8 +73,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
this.timeout = timeout; this.timeout = timeout;
} }
public void setTimeoutHandler(Runnable timeoutHandler) { public void addTimeoutHandler(Runnable timeoutHandler) {
this.timeoutHandler = timeoutHandler; this.timeoutHandlers.add(timeoutHandler);
} }
public void addCompletionHandler(Runnable runnable) { public void addCompletionHandler(Runnable runnable) {
@ -127,8 +127,8 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
} }
public void onTimeout(AsyncEvent event) throws IOException { public void onTimeout(AsyncEvent event) throws IOException {
if (this.timeoutHandler != null) { for (Runnable handler : this.timeoutHandlers) {
this.timeoutHandler.run(); handler.run();
} }
} }

View File

@ -15,7 +15,9 @@
*/ */
package org.springframework.web.context.request.async; package org.springframework.web.context.request.async;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -155,19 +157,27 @@ public final class WebAsyncManager {
return this.concurrentResultContext; return this.concurrentResultContext;
} }
/**
* Get the {@link CallableProcessingInterceptor} registered under the given key.
* @param key the key
* @return the interceptor registered under that key or {@code null}
*/
public CallableProcessingInterceptor getCallableInterceptor(Object key) { public CallableProcessingInterceptor getCallableInterceptor(Object key) {
return this.callableInterceptors.get(key); return this.callableInterceptors.get(key);
} }
/**
* Get the {@link DeferredResultProcessingInterceptor} registered under the given key.
* @param key the key
* @return the interceptor registered under that key or {@code null}
*/
public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) { public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) {
return this.deferredResultInterceptors.get(key); return this.deferredResultInterceptors.get(key);
} }
/** /**
* Register a {@link CallableProcessingInterceptor} that will be applied * Register a {@link CallableProcessingInterceptor} under the given key.
* when concurrent request handling with a {@link Callable} starts. * @param key the key
*
* @param key a unique the key under which to register the interceptor
* @param interceptor the interceptor to register * @param interceptor the interceptor to register
*/ */
public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) { public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) {
@ -181,11 +191,8 @@ public final class WebAsyncManager {
} }
/** /**
* Register a {@link DeferredResultProcessingInterceptor} that will be * Register a {@link DeferredResultProcessingInterceptor} under the given key.
* applied when concurrent request handling with a {@link DeferredResult} * @param key the key
* starts.
*
* @param key a unique the key under which to register the interceptor
* @param interceptor the interceptor to register * @param interceptor the interceptor to register
*/ */
public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) { public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) {
@ -221,16 +228,47 @@ public final class WebAsyncManager {
* @see #getConcurrentResult() * @see #getConcurrentResult()
* @see #getConcurrentResultContext() * @see #getConcurrentResultContext()
*/ */
@SuppressWarnings({ "rawtypes", "unchecked" })
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) { public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
Assert.notNull(callable, "Callable must not be null"); Assert.notNull(callable, "Callable must not be null");
startCallableProcessing(new MvcAsyncTask(callable), processingContext);
}
/**
* Use the given {@link MvcAsyncTask} to configure the task executor as well as
* the timeout value of the {@code AsyncWebRequest} before delegating to
* {@link #startCallableProcessing(Callable, Object...)}.
*
* @param mvcAsyncTask an MvcAsyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
*/
public void startCallableProcessing(final MvcAsyncTask<?> mvcAsyncTask, Object... processingContext) {
Assert.notNull(mvcAsyncTask, "MvcAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null"); Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
final CallableInterceptorChain chain = new CallableInterceptorChain(this.callableInterceptors.values()); final Callable<?> callable = mvcAsyncTask.getCallable();
this.asyncWebRequest.setTimeoutHandler(new Runnable() { Long timeout = mvcAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
AsyncTaskExecutor executor = mvcAsyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
List<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>();
interceptors.add(mvcAsyncTask.getInterceptor());
interceptors.addAll(this.callableInterceptors.values());
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(new Runnable() {
public void run() { public void run() {
logger.debug("Processing timeout"); logger.debug("Processing timeout");
Object result = chain.triggerAfterTimeout(asyncWebRequest, callable); Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) { if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result); setConcurrentResultAndDispatch(result);
} }
@ -239,7 +277,7 @@ public final class WebAsyncManager {
this.asyncWebRequest.addCompletionHandler(new Runnable() { this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() { public void run() {
chain.triggerAfterCompletion(asyncWebRequest, callable); interceptorChain.triggerAfterCompletion(asyncWebRequest, callable);
} }
}); });
@ -249,14 +287,14 @@ public final class WebAsyncManager {
public void run() { public void run() {
Object result = null; Object result = null;
try { try {
chain.applyPreProcess(asyncWebRequest, callable); interceptorChain.applyPreProcess(asyncWebRequest, callable);
result = callable.call(); result = callable.call();
} }
catch (Throwable t) { catch (Throwable t) {
result = t; result = t;
} }
finally { finally {
result = chain.applyPostProcess(asyncWebRequest, callable, result); result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
} }
setConcurrentResultAndDispatch(result); setConcurrentResultAndDispatch(result);
} }
@ -282,32 +320,6 @@ public final class WebAsyncManager {
asyncWebRequest.dispatch(); asyncWebRequest.dispatch();
} }
/**
* Use the given {@link AsyncTask} to configure the task executor as well as
* the timeout value of the {@code AsyncWebRequest} before delegating to
* {@link #startCallableProcessing(Callable, Object...)}.
*
* @param asyncTask an asyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
*/
public void startCallableProcessing(AsyncTask<?> asyncTask, Object... processingContext) {
Assert.notNull(asyncTask, "AsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = asyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
AsyncTaskExecutor executor = asyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
startCallableProcessing(asyncTask.getCallable(), processingContext);
}
/** /**
* Start concurrent request processing and initialize the given * Start concurrent request processing and initialize the given
* {@link DeferredResult} with a {@link DeferredResultHandler} that saves * {@link DeferredResult} with a {@link DeferredResultHandler} that saves
@ -329,41 +341,41 @@ public final class WebAsyncManager {
Assert.notNull(deferredResult, "DeferredResult must not be null"); Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null"); Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
Long timeout = deferredResult.getTimeoutMilliseconds(); Long timeout = deferredResult.getTimeoutValue();
if (timeout != null) { if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout); this.asyncWebRequest.setTimeout(timeout);
} }
final DeferredResultInterceptorChain chain = List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>();
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values()); interceptors.add(deferredResult.getInterceptor());
interceptors.addAll(this.deferredResultInterceptors.values());
this.asyncWebRequest.setTimeoutHandler(new Runnable() { final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);
this.asyncWebRequest.addTimeoutHandler(new Runnable() {
public void run() { public void run() {
if (!deferredResult.applyTimeoutResult()) {
try { try {
chain.triggerAfterTimeout(asyncWebRequest, deferredResult); interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult);
} }
catch (Throwable t) { catch (Throwable t) {
setConcurrentResultAndDispatch(t); setConcurrentResultAndDispatch(t);
} }
} }
}
}); });
this.asyncWebRequest.addCompletionHandler(new Runnable() { this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() { public void run() {
deferredResult.expire(); interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult);
chain.triggerAfterCompletion(asyncWebRequest, deferredResult);
} }
}); });
startAsyncProcessing(processingContext); startAsyncProcessing(processingContext);
try { try {
chain.applyPreProcess(this.asyncWebRequest, deferredResult); interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
deferredResult.setResultHandler(new DeferredResultHandler() { deferredResult.setResultHandler(new DeferredResultHandler() {
public void handleResult(Object result) { public void handleResult(Object result) {
result = chain.applyPostProcess(asyncWebRequest, deferredResult, result); result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
setConcurrentResultAndDispatch(result); setConcurrentResultAndDispatch(result);
} }
}); });

View File

@ -19,6 +19,7 @@ package org.springframework.web.context.request.async;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -80,27 +81,42 @@ public class DeferredResultTests {
} }
@Test @Test
public void setExpired() { public void onCompletion() throws Exception {
DeferredResult<String> result = new DeferredResult<String>(); final StringBuilder sb = new StringBuilder();
assertFalse(result.isSetOrExpired());
DeferredResult<String> result = new DeferredResult<String>();
result.onCompletion(new Runnable() {
public void run() {
sb.append("completion event");
}
});
result.getInterceptor().afterCompletion(null, null);
result.expire();
assertTrue(result.isSetOrExpired()); assertTrue(result.isSetOrExpired());
assertFalse(result.setResult("hello")); assertEquals("completion event", sb.toString());
} }
@Test @Test
public void applyTimeoutResult() { public void onTimeout() throws Exception {
final StringBuilder sb = new StringBuilder();
DeferredResultHandler handler = createMock(DeferredResultHandler.class); DeferredResultHandler handler = createMock(DeferredResultHandler.class);
handler.handleResult("timed out"); handler.handleResult("timeout result");
replay(handler); replay(handler);
DeferredResult<String> result = new DeferredResult<String>(null, "timed out"); DeferredResult<String> result = new DeferredResult<String>(null, "timeout result");
result.setResultHandler(handler); result.setResultHandler(handler);
result.onTimeout(new Runnable() {
public void run() {
sb.append("timeout event");
}
});
assertTrue(result.applyTimeoutResult()); result.getInterceptor().afterTimeout(null, null);
assertFalse("Shouldn't be able to set result after timeout", result.setResult("hello"));
assertEquals("timeout event", sb.toString());
assertFalse("Should not be able to set result a second time", result.setResult("hello"));
verify(handler); verify(handler);
} }

View File

@ -128,7 +128,7 @@ public class StandardServletAsyncWebRequestTests {
timeoutHandler.run(); timeoutHandler.run();
replay(timeoutHandler); replay(timeoutHandler);
this.asyncRequest.setTimeoutHandler(timeoutHandler); this.asyncRequest.addTimeoutHandler(timeoutHandler);
this.asyncRequest.onTimeout(new AsyncEvent(null)); this.asyncRequest.onTimeout(new AsyncEvent(null));
verify(timeoutHandler); verify(timeoutHandler);

View File

@ -208,13 +208,13 @@ public class WebAsyncManagerTests {
replay(executor); replay(executor);
this.asyncWebRequest.setTimeout(1000L); this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.setTimeoutHandler(EasyMock.<Runnable>anyObject()); this.asyncWebRequest.addTimeoutHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.addCompletionHandler(EasyMock.<Runnable>anyObject()); this.asyncWebRequest.addCompletionHandler(EasyMock.<Runnable>anyObject());
this.asyncWebRequest.startAsync(); this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest); replay(this.asyncWebRequest);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
AsyncTask<Object> asyncTask = new AsyncTask<Object>(1000L, executor, createMock(Callable.class)); MvcAsyncTask<Object> asyncTask = new MvcAsyncTask<Object>(1000L, executor, createMock(Callable.class));
this.asyncManager.startCallableProcessing(asyncTask); this.asyncManager.startCallableProcessing(asyncTask);
verify(executor, this.asyncWebRequest); verify(executor, this.asyncWebRequest);
@ -311,7 +311,7 @@ public class WebAsyncManagerTests {
} }
private void setupDefaultAsyncScenario() { private void setupDefaultAsyncScenario() {
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull()); this.asyncWebRequest.addTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.addCompletionHandler((Runnable) notNull()); this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
this.asyncWebRequest.startAsync(); this.asyncWebRequest.startAsync();
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false); expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);

View File

@ -16,7 +16,6 @@
package org.springframework.web.context.request.async; package org.springframework.web.context.request.async;
import static org.springframework.web.context.request.async.CallableProcessingInterceptor.RESULT_NONE;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expect;
@ -25,6 +24,8 @@ import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.context.request.async.CallableProcessingInterceptor.RESULT_NONE;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -94,7 +95,27 @@ public class WebAsyncManagerTimeoutTests {
} }
@Test @Test
public void startCallableProcessingTimeoutAndResume() throws Exception { public void startCallableProcessingTimeoutAndResumeThroughCallback() throws Exception {
StubCallable callable = new StubCallable();
MvcAsyncTask<Object> mvcAsyncTask = new MvcAsyncTask<Object>(callable);
mvcAsyncTask.onTimeout(new Callable<Object>() {
public Object call() throws Exception {
return 7;
}
});
this.asyncManager.startCallableProcessing(mvcAsyncTask);
this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(7, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}
@Test
public void startCallableProcessingTimeoutAndResumeThroughInterceptor() throws Exception {
StubCallable callable = new StubCallable(); StubCallable callable = new StubCallable();
@ -107,6 +128,7 @@ public class WebAsyncManagerTimeoutTests {
this.asyncWebRequest.onTimeout(ASYNC_EVENT); this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(22, this.asyncManager.getConcurrentResult()); assertEquals(22, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath()); assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
@ -128,6 +150,7 @@ public class WebAsyncManagerTimeoutTests {
this.asyncWebRequest.onTimeout(ASYNC_EVENT); this.asyncWebRequest.onTimeout(ASYNC_EVENT);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(exception, this.asyncManager.getConcurrentResult()); assertEquals(exception, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath()); assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
@ -161,25 +184,38 @@ public class WebAsyncManagerTimeoutTests {
public void startDeferredResultProcessingTimeoutAndResumeWithDefaultResult() throws Exception { public void startDeferredResultProcessingTimeoutAndResumeWithDefaultResult() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(null, 23); DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(null, 23);
DeferredResultProcessingInterceptor interceptor = new DeferredResultProcessingInterceptorAdapter() {
public <T> void afterTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
result.setErrorResult("should not get here");
}
};
this.asyncManager.registerDeferredResultInterceptor("interceptor", interceptor);
this.asyncManager.startDeferredResultProcessing(deferredResult); this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null; AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event); this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(23, this.asyncManager.getConcurrentResult()); assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath()); assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
} }
@Test @Test
public void startDeferredResultProcessingTimeoutAndResumeWithInterceptor() throws Exception { public void startDeferredResultProcessingTimeoutAndResumeThroughCallback() throws Exception {
final DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
deferredResult.onTimeout(new Runnable() {
public void run() {
deferredResult.setResult(23);
}
});
this.asyncManager.startDeferredResultProcessing(deferredResult);
AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
}
@Test
public void startDeferredResultProcessingTimeoutAndResumeThroughInterceptor() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(); DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
@ -195,6 +231,7 @@ public class WebAsyncManagerTimeoutTests {
AsyncEvent event = null; AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event); this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(23, this.asyncManager.getConcurrentResult()); assertEquals(23, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath()); assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
} }
@ -217,6 +254,7 @@ public class WebAsyncManagerTimeoutTests {
AsyncEvent event = null; AsyncEvent event = null;
this.asyncWebRequest.onTimeout(event); this.asyncWebRequest.onTimeout(event);
assertTrue(this.asyncManager.hasConcurrentResult());
assertEquals(exception, this.asyncManager.getConcurrentResult()); assertEquals(exception, this.asyncManager.getConcurrentResult());
assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath()); assertEquals("/test", ((MockAsyncContext) this.servletRequest.getAsyncContext()).getDispatchedPath());
} }

View File

@ -905,7 +905,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
initContextHolders(request, localeContext, requestAttributes); initContextHolders(request, localeContext, requestAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(this.getClass().getName(), createRequestBindingInterceptor(request)); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), getRequestBindingInterceptor(request));
try { try {
doService(request, response); doService(request, response);
@ -988,7 +988,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
} }
} }
private CallableProcessingInterceptor createRequestBindingInterceptor(final HttpServletRequest request) { private CallableProcessingInterceptor getRequestBindingInterceptor(final HttpServletRequest request) {
return new CallableProcessingInterceptorAdapter() { return new CallableProcessingInterceptorAdapter() {
@Override @Override
public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) { public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {

View File

@ -23,7 +23,7 @@ import java.util.concurrent.Callable;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.context.request.async.AsyncTask; import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.CallableProcessingInterceptor; import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
@ -50,7 +50,7 @@ public class AsyncSupportConfigurer {
/** /**
* Set the default {@link AsyncTaskExecutor} to use when a controller method * Set the default {@link AsyncTaskExecutor} to use when a controller method
* returns a {@link Callable}. Controller methods can override this default on * returns a {@link Callable}. Controller methods can override this default on
* a per-request basis by returning an {@link AsyncTask}. * a per-request basis by returning an {@link MvcAsyncTask}.
* *
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used and it's * <p>By default a {@link SimpleAsyncTaskExecutor} instance is used and it's
* highly recommended to change that default in production since the simple * highly recommended to change that default in production since the simple

View File

@ -19,13 +19,13 @@ package org.springframework.web.servlet.mvc.method.annotation;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.AsyncTask; import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.method.support.ModelAndViewContainer;
/** /**
* Handles return values of type {@link AsyncTask}. * Handles return values of type {@link MvcAsyncTask}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
@ -41,7 +41,7 @@ public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnVal
public boolean supportsReturnType(MethodParameter returnType) { public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType(); Class<?> paramType = returnType.getParameterType();
return AsyncTask.class.isAssignableFrom(paramType); return MvcAsyncTask.class.isAssignableFrom(paramType);
} }
public void handleReturnValue(Object returnValue, public void handleReturnValue(Object returnValue,
@ -52,9 +52,9 @@ public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnVal
return; return;
} }
AsyncTask<?> asyncTask = (AsyncTask<?>) returnValue; MvcAsyncTask<?> mvcAsyncTask = (MvcAsyncTask<?>) returnValue;
asyncTask.setBeanFactory(this.beanFactory); mvcAsyncTask.setBeanFactory(this.beanFactory);
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(asyncTask, mavContainer); WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(mvcAsyncTask, mavContainer);
} }
} }

View File

@ -63,7 +63,7 @@ import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncTask; import org.springframework.web.context.request.async.MvcAsyncTask;
import org.springframework.web.context.request.async.AsyncWebRequest; import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor; import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
@ -350,7 +350,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
/** /**
* Set the default {@link AsyncTaskExecutor} to use when a controller method * Set the default {@link AsyncTaskExecutor} to use when a controller method
* return a {@link Callable}. Controller methods can override this default on * return a {@link Callable}. Controller methods can override this default on
* a per-request basis by returning an {@link AsyncTask}. * a per-request basis by returning an {@link MvcAsyncTask}.
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used. * <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
* It's recommended to change that default in production as the simple executor * It's recommended to change that default in production as the simple executor
* does not re-use threads. * does not re-use threads.