Set timeout value and task executor per async request

Methods returning DeferredResult can now specify a timeout value
through constructor arg while methods returning a Callable can wrap it
in an AsyncTask that also accepts a timeout and a specific task
executor.

Issue: SPR-9399
This commit is contained in:
Rossen Stoyanchev 2012-08-16 18:45:36 -04:00
parent 4407f6a4c0
commit cdab04a032
20 changed files with 624 additions and 359 deletions

View File

@ -34,7 +34,7 @@ import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -195,13 +195,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
participate = true;
}
else {
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) {
logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
asyncManager.registerAsyncThreadInitializer(key, initializer);
}
}
@ -240,10 +240,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
}
}
private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
final SessionHolder sessionHolder) {
return new AsyncThreadInitializer() {
return new WebAsyncThreadInitializer() {
public void initialize() {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
}

View File

@ -28,7 +28,7 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
/**
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@ -147,7 +147,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
String participateAttributeName = getParticipateAttributeName();
if (asyncManager.hasConcurrentResult()) {
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
if (asyncManager.initializeAsyncThread(participateAttributeName)) {
return;
}
}
@ -169,7 +169,7 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
}
else {
@ -261,8 +261,8 @@ public class OpenSessionInViewInterceptor extends HibernateAccessor implements A
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
}
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
return new AsyncThreadInitializer() {
private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
return new WebAsyncThreadInitializer() {
public void initialize() {
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}

View File

@ -34,7 +34,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@ -126,13 +126,13 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
participate = true;
}
else {
if (!isAsyncDispatch(request) || !asyncManager.applyAsyncThreadInitializer(key)) {
if (!isAsyncDispatch(request) || !asyncManager.initializeAsyncThread(key)) {
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = openSession(sessionFactory);
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
AsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
WebAsyncThreadInitializer initializer = createAsyncThreadInitializer(sessionFactory, sessionHolder);
asyncManager.registerAsyncThreadInitializer(key, initializer);
}
}
@ -153,10 +153,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
}
}
private AsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
private WebAsyncThreadInitializer createAsyncThreadInitializer(final SessionFactory sessionFactory,
final SessionHolder sessionHolder) {
return new AsyncThreadInitializer() {
return new WebAsyncThreadInitializer() {
public void initialize() {
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
}

View File

@ -32,7 +32,7 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncWebRequestInterceptor;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
/**
* Spring web request interceptor that binds a Hibernate <code>Session</code> to the
@ -109,7 +109,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
String participateAttributeName = getParticipateAttributeName();
if (asyncManager.hasConcurrentResult()) {
if (asyncManager.applyAsyncThreadInitializer(participateAttributeName)) {
if (asyncManager.initializeAsyncThread(participateAttributeName)) {
return;
}
}
@ -126,7 +126,7 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
AsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
WebAsyncThreadInitializer asyncThreadInitializer = createThreadInitializer(sessionHolder);
asyncManager.registerAsyncThreadInitializer(participateAttributeName, asyncThreadInitializer);
}
}
@ -200,8 +200,8 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
}
private AsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
return new AsyncThreadInitializer() {
private WebAsyncThreadInitializer createThreadInitializer(final SessionHolder sessionHolder) {
return new WebAsyncThreadInitializer() {
public void initialize() {
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.context.request.async;
import java.util.concurrent.Callable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.util.Assert;
/**
* Holder for a {@link Callable}, a timeout value, and a task executor.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class AsyncTask {
private final Callable<?> callable;
private final Long timeout;
private final String executorName;
private final AsyncTaskExecutor executor;
private BeanFactory beanFactory;
/**
* Create an AsyncTask with a timeout value and a Callable.
* @param timeout timeout value in milliseconds
* @param callable the callable for concurrent handling
*/
public AsyncTask(long timeout, Callable<?> callable) {
this(timeout, null, null, callable);
Assert.notNull(timeout, "Timeout must not be null");
}
/**
* Create an AsyncTask with a timeout value, an executor name, and a Callable.
* @param timeout timeout value in milliseconds; ignored if {@code null}
* @param callable the callable for concurrent handling
*/
public AsyncTask(Long timeout, String executorName, Callable<?> callable) {
this(timeout, null, executorName, callable);
Assert.notNull(executor, "Executor name must not be null");
}
/**
* Create an AsyncTask with a timeout value, an executor instance, and a Callable.
* @param timeout timeout value in milliseconds; ignored if {@code null}
* @param callable the callable for concurrent handling
*/
public AsyncTask(Long timeout, AsyncTaskExecutor executor, Callable<?> callable) {
this(timeout, executor, null, callable);
Assert.notNull(executor, "Executor must not be null");
}
private AsyncTask(Long timeout, AsyncTaskExecutor executor, String executorName, Callable<?> callable) {
Assert.notNull(callable, "Callable must not be null");
this.callable = callable;
this.timeout = timeout;
this.executor = executor;
this.executorName = executorName;
}
/**
* Return the {@link Callable} to use for concurrent handling, never {@code null}.
*/
public Callable<?> getCallable() {
return this.callable;
}
/**
* Return the timeout value in milliseconds or {@code null} if not value is set.
*/
public Long getTimeout() {
return this.timeout;
}
/**
* Return the AsyncTaskExecutor to use for concurrent handling, or {@code null}.
*/
public AsyncTaskExecutor getExecutor() {
if (this.executor != null) {
return this.executor;
}
else if (this.executorName != null) {
Assert.state(this.beanFactory != null, "A BeanFactory is required to look up an task executor bean");
return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);
}
else {
return null;
}
}
/**
* 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
* Spring MVC controller.
*/
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}

View File

@ -29,20 +29,18 @@ public interface AsyncWebRequest extends NativeWebRequest {
/**
* Set the time required for concurrent handling to complete.
* @param timeout amount of time in milliseconds
* This property should not be set when concurrent handling is in progress,
* i.e. when {@link #isAsyncStarted()} is {@code true}.
* @param timeout amount of time in milliseconds; {@code null} means no
* timeout, i.e. rely on the default timeout of the container.
*/
void setTimeout(Long timeout);
/**
* Provide a Runnable to invoke on timeout.
* Set a handler to be invoked if concurrent processing times out.
*/
void setTimeoutHandler(Runnable runnable);
/**
* Provide a Runnable to invoke at the end of asynchronous request processing.
*/
void addCompletionHandler(Runnable runnable);
/**
* Mark the start of asynchronous request processing so that when the main
* processing thread exits, the response remains open for further processing
@ -70,6 +68,11 @@ public interface AsyncWebRequest extends NativeWebRequest {
*/
boolean isDispatched();
/**
* Add a Runnable to be invoked when request processing completes.
*/
void addCompletionHandler(Runnable runnable);
/**
* Whether asynchronous processing has completed.
*/

View File

@ -15,8 +15,14 @@
*/
package org.springframework.web.context.request.async;
import javax.servlet.ServletRequest;
import java.lang.reflect.Constructor;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
@ -57,4 +63,30 @@ public abstract class AsyncWebUtils {
return asyncManager;
}
/**
* Create an AsyncWebRequest instance.
* <p>By default an instance of {@link StandardServletAsyncWebRequest} is created
* if running in Servlet 3.0 (or higher) environment or as a fallback option an
* instance of {@link NoSupportAsyncWebRequest} is returned.
* @param request the current request
* @param response the current response
* @return an AsyncWebRequest instance, never {@code null}
*/
public static AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ?
createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response);
}
private static AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
try {
String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest";
Class<?> clazz = ClassUtils.forName(className, AsyncWebUtils.class.getClassLoader());
Constructor<?> constructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class);
return (AsyncWebRequest) BeanUtils.instantiateClass(constructor, request, response);
}
catch (Throwable t) {
throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t);
}
}
}

View File

@ -24,10 +24,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* {@code DeferredResult} provides an alternative to returning a {@link Callable}
* for asynchronous request processing. While with a Callable, a thread is used
* to execute it on behalf of the application, with a DeferredResult the application
* sets the result whenever it needs to from a thread of its choice.
* {@code DeferredResult} provides an alternative to using a {@link Callable}
* for asynchronous request processing. While a Callable is executed concurrently
* on behalf of the application, with a DeferredResult the application can produce
* the result from a thread of its choice.
*
* @author Rossen Stoyanchev
* @since 3.2
@ -38,32 +38,53 @@ public final class DeferredResult<T> {
private static final Object RESULT_NONE = new Object();
private Object result = RESULT_NONE;
private final Object timeoutResult;
private final AtomicBoolean expired = new AtomicBoolean(false);
private final Long timeout;
private DeferredResultHandler resultHandler;
private Object result = RESULT_NONE;
private final AtomicBoolean expired = new AtomicBoolean(false);
private final Object lock = new Object();
private final CountDownLatch latch = new CountDownLatch(1);
/**
* Create a DeferredResult instance.
* Create a DeferredResult.
*/
public DeferredResult() {
this(RESULT_NONE);
this(null, RESULT_NONE);
}
/**
* Create a DeferredResult with a default result to use in case of a timeout.
* @param timeoutResult the result to use
* Create a DeferredResult with a timeout.
* @param timeout timeout value in milliseconds
*/
public DeferredResult(Object timeoutResult) {
public DeferredResult(long timeout) {
this(timeout, RESULT_NONE);
}
/**
* Create a DeferredResult with a timeout and a default result to use on timeout.
* @param timeout timeout value in milliseconds; ignored if {@code null}
* @param timeoutResult the result to use, possibly {@code null}
*/
public DeferredResult(Long timeout, Object timeoutResult) {
this.timeoutResult = timeoutResult;
this.timeout = timeout;
}
/**
* Return the configured timeout value in milliseconds.
*/
public Long getTimeoutMilliseconds() {
return this.timeout;
}
/**

View File

@ -70,6 +70,7 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
* <p>The timeout period begins when the main processing thread has exited.
*/
public void setTimeout(Long timeout) {
Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress");
this.timeout = timeout;
}
@ -145,6 +146,9 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
}
/**
* Sends a SERVICE_UNAVAILABLE (503).
*/
private class DefaultTimeoutHandler implements Runnable {
public void run() {

View File

@ -38,25 +38,16 @@ import org.springframework.web.util.UrlPathHelper;
* as an SPI and not typically used directly by application classes.
*
* <p>An async scenario starts with request processing as usual in a thread (T1).
* When a handler decides to handle the request concurrently, it calls
* Concurrent request handling can be innitiated by calling
* {@linkplain #startCallableProcessing(Callable, Object...) startCallableProcessing} or
* {@linkplain #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing}
* both of which will process in a separate thread (T2).
* After the start of concurrent handling {@link #isConcurrentHandlingStarted()}
* returns "true" and this can be used by classes involved in processing on the
* main thread (T1) quickly and with very minimal processing.
* both of which produce a result in a separate thread (T2). The result is saved
* and the request dispatched to the container, to resume processing with the saved
* result in a third thread (T3). Within the dispatched thread (T3), the saved
* result can be accessed via {@link #getConcurrentResult()} or its presence
* detected via {@link #hasConcurrentResult()}.
*
* <p>When the concurrent handling completes in a separate thread (T2), both
* {@code startCallableProcessing} and {@code startDeferredResultProcessing}
* save the results and dispatched to the container, essentially to the
* same request URI as the one that started concurrent handling. This allows for
* further processing of the concurrent results. Classes in the dispatched
* thread (T3), can access the results via {@link #getConcurrentResult()} or
* detect their presence via {@link #hasConcurrentResult()}. Also in the
* dispatched thread {@link #isConcurrentHandlingStarted()} will return "false"
* unless concurrent handling is started once again.
*
* TODO .. mention Servlet 3 configuration
* <p>TODO .. Servlet 3 config
*
* @author Rossen Stoyanchev
* @since 3.2
@ -73,42 +64,38 @@ public final class WebAsyncManager {
private static final Log logger = LogFactory.getLog(WebAsyncManager.class);
private AsyncWebRequest asyncWebRequest;
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());
private final Map<Object, AsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, AsyncThreadInitializer>();
private Object concurrentResult = RESULT_NONE;
private Object[] concurrentResultContext;
private final Map<Object, WebAsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, WebAsyncThreadInitializer>();
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* Package private constructor
* @see AsyncWebUtils
* Package private constructor.
* @see AsyncWebUtils#getAsyncManager(javax.servlet.ServletRequest)
* @see AsyncWebUtils#getAsyncManager(org.springframework.web.context.request.WebRequest)
*/
WebAsyncManager() {
}
/**
* Configure an AsyncTaskExecutor for use with {@link #startCallableProcessing(Callable)}.
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used. Applications
* are advised to provide a TaskExecutor configured for production use.
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setAsyncTaskExecutor
*/
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Provide an {@link AsyncWebRequest} to use to start and to dispatch request.
* This property must be set before the start of concurrent handling.
* @param asyncWebRequest the request to use
* Configure the {@link AsyncWebRequest} to use. This property may be
* set more than once during a single request to accurately reflect the
* current state of the request (e.g. following a forward, request/response
* wrapping, etc). However, it should not be set while concurrent handling is
* in progress, i.e. while {@link #isConcurrentHandlingStarted()} is {@code true}.
* @param asyncWebRequest the web request to use
*/
public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) {
Assert.notNull(asyncWebRequest, "Expected AsyncWebRequest");
Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null");
Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress");
this.asyncWebRequest = asyncWebRequest;
this.asyncWebRequest.addCompletionHandler(new Runnable() {
@ -119,18 +106,27 @@ public final class WebAsyncManager {
}
/**
* Whether the handler for the current request is executed concurrently.
* Once concurrent handling is done, the result is saved, and the request
* dispatched again to resume processing where the result of concurrent
* handling is available via {@link #getConcurrentResult()}.
* Configure an AsyncTaskExecutor for use with concurrent processing via
* {@link #startCallableProcessing(Callable, Object...)}.
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
*/
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Whether the target handler chose to handle the request asynchronously.
* A return value of "true" indicates concurrent handling is under way and the
* response will remain open. A return value of "false" will be returned again after concurrent
* handling produces a result and the request is dispatched to resume processing.
*/
public boolean isConcurrentHandlingStarted() {
return ((this.asyncWebRequest != null) && (this.asyncWebRequest.isAsyncStarted()));
}
/**
* Whether the current thread was dispatched to continue processing the result
* of concurrent handler execution.
* Whether the request was dispatched to resume processing the result of
* concurrent handling.
*/
public boolean hasConcurrentResult() {
@ -142,84 +138,58 @@ public final class WebAsyncManager {
}
/**
* Return the result of concurrent handler execution. This may be an Object
* value on successful return or an {@code Exception} or {@code Throwable}.
* Provides access to the result from concurrent handling.
* @return an Object, possibly an {@code Exception} or {@code Throwable} if
* concurrent handling raised one.
*/
public Object getConcurrentResult() {
return this.concurrentResult;
}
/**
* Return the processing context saved at the start of concurrent handling.
* Provides access to additional processing context saved at the start of
* concurrent handling.
*/
public Object[] getConcurrentResultContext() {
return this.concurrentResultContext;
}
/**
* Reset the {@linkplain #getConcurrentResult() concurrentResult} and the
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
*/
public void resetConcurrentResult() {
public void clearConcurrentResult() {
this.concurrentResult = RESULT_NONE;
this.concurrentResultContext = null;
}
/**
* Register an {@link AsyncThreadInitializer} with the WebAsyncManager instance
* for the current request. It may later be accessed and applied via
* {@link #applyAsyncThreadInitializer(String)} and will also be used to
* initialize and reset threads for concurrent handler execution.
* @param key a unique the key under which to keep the initializer
* @param initializer the initializer instance
*/
public void registerAsyncThreadInitializer(Object key, AsyncThreadInitializer initializer) {
Assert.notNull(initializer, "An AsyncThreadInitializer instance is required");
this.threadInitializers.put(key, initializer);
}
/**
* Invoke the {@linkplain AsyncThreadInitializer#initialize() initialize()}
* method of the named {@link AsyncThreadInitializer}.
* @param key the key under which the initializer was registered
* @return whether an initializer was found and applied
*/
public boolean applyAsyncThreadInitializer(Object key) {
AsyncThreadInitializer initializer = this.threadInitializers.get(key);
if (initializer != null) {
initializer.initialize();
return true;
}
return false;
}
/**
* Submit a request handling task for concurrent execution. Returns immediately
* and subsequent calls to {@link #isConcurrentHandlingStarted()} return "true".
* <p>When concurrent handling is done, the resulting value, which may be an
* Object or a raised {@code Exception} or {@code Throwable}, is saved and the
* request is dispatched for further processing of that result. In the dispatched
* thread, the result can be accessed via {@link #getConcurrentResult()} while
* {@link #hasConcurrentResult()} returns "true" and
* {@link #isConcurrentHandlingStarted()} is back to returning "false".
* Start concurrent request processing and execute the given task with an
* {@link #setTaskExecutor(AsyncTaskExecutor) AsyncTaskExecutor}. The result
* from the task execution is saved and the request dispatched in order to
* resume processing of that result. If the task raises an Exception then
* the saved result will be the raised Exception.
*
* @param callable a unit of work to be executed asynchronously
* @param processingContext additional context to save for later access via
* {@link #getConcurrentResultContext()}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
Assert.notNull(callable, "Callable is required");
Assert.notNull(callable, "Callable must not be null");
startAsyncProcessing(processingContext);
this.taskExecutor.submit(new Runnable() {
public void run() {
List<AsyncThreadInitializer> initializers =
new ArrayList<AsyncThreadInitializer>(threadInitializers.values());
List<WebAsyncThreadInitializer> initializers =
new ArrayList<WebAsyncThreadInitializer>(threadInitializers.values());
try {
for (AsyncThreadInitializer initializer : initializers) {
for (WebAsyncThreadInitializer initializer : initializers) {
initializer.initialize();
}
concurrentResult = callable.call();
@ -229,7 +199,7 @@ public final class WebAsyncManager {
}
finally {
Collections.reverse(initializers);
for (AsyncThreadInitializer initializer : initializers) {
for (WebAsyncThreadInitializer initializer : initializers) {
initializer.reset();
}
}
@ -250,24 +220,51 @@ public final class WebAsyncManager {
}
/**
* Initialize the given given {@link DeferredResult} so that whenever the
* DeferredResult is set, the resulting value, which may be an Object or a
* raised {@code Exception} or {@code Throwable}, is saved and the request
* is dispatched for further processing of the result. In the dispatch
* thread, the result value can be accessed via {@link #getConcurrentResult()}.
* <p>The method returns immediately and it's up to the caller to set the
* DeferredResult. Subsequent calls to {@link #isConcurrentHandlingStarted()}
* return "true" until after the dispatch when {@link #hasConcurrentResult()}
* returns "true" and {@link #isConcurrentHandlingStarted()} is back to "false".
* 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");
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 {@link DeferredResult}
* with a {@link DeferredResultHandler} that saves the result and dispatches
* the request to resume processing of that result.
* The {@code AsyncWebRequest} is also updated with a completion handler that
* expires the {@code DeferredResult} and a timeout handler assuming the
* {@code DeferredResult} has a default timeout result.
*
* @param deferredResult the DeferredResult instance to initialize
* @param processingContext additional context to save for later access via
* {@link #getConcurrentResultContext()}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult, Object... processingContext) {
Assert.notNull(deferredResult, "DeferredResult is required");
Assert.notNull(deferredResult, "DeferredResult must not be null");
startAsyncProcessing(processingContext);
Long timeout = deferredResult.getTimeoutMilliseconds();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() {
@ -283,6 +280,8 @@ public final class WebAsyncManager {
});
}
startAsyncProcessing(processingContext);
deferredResult.setResultHandler(new DeferredResultHandler() {
public void handleResult(Object result) {
@ -300,13 +299,13 @@ public final class WebAsyncManager {
});
}
private void startAsyncProcessing(Object... context) {
private void startAsyncProcessing(Object[] processingContext) {
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest was not set");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
this.asyncWebRequest.startAsync();
this.concurrentResult = null;
this.concurrentResultContext = context;
this.concurrentResultContext = processingContext;
if (logger.isDebugEnabled()) {
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
@ -315,11 +314,38 @@ public final class WebAsyncManager {
}
}
/**
* Register an {@link WebAsyncThreadInitializer} for the current request. It may
* later be accessed and applied via {@link #initializeAsyncThread(String)}
* and will also be used to initialize and reset threads for concurrent handler execution.
* @param key a unique the key under which to keep the initializer
* @param initializer the initializer instance
*/
public void registerAsyncThreadInitializer(Object key, WebAsyncThreadInitializer initializer) {
Assert.notNull(initializer, "WebAsyncThreadInitializer must not be null");
this.threadInitializers.put(key, initializer);
}
/**
* A contract for initializing and resetting a thread.
* Invoke the {@linkplain WebAsyncThreadInitializer#initialize() initialize()}
* method of the named {@link WebAsyncThreadInitializer}.
* @param key the key under which the initializer was registered
* @return whether an initializer was found and applied
*/
public interface AsyncThreadInitializer {
public boolean initializeAsyncThread(Object key) {
WebAsyncThreadInitializer initializer = this.threadInitializers.get(key);
if (initializer != null) {
initializer.initialize();
return true;
}
return false;
}
/**
* Initialize and reset thread-bound variables.
*/
public interface WebAsyncThreadInitializer {
void initialize();

View File

@ -0,0 +1,10 @@
/**
*
* Support for asynchronous request processing.
*
* @since 3.2
*
*/
package org.springframework.web.context.request.async;

View File

@ -108,7 +108,7 @@ public class DeferredResultTests {
@Test
public void hasTimeout() {
assertFalse(new DeferredResult<String>().hasTimeoutResult());
assertTrue(new DeferredResult<String>("timed out").hasTimeoutResult());
assertTrue(new DeferredResult<String>(null, "timed out").hasTimeoutResult());
}
@Test
@ -117,7 +117,7 @@ public class DeferredResultTests {
handler.handleResult("timed out");
replay(handler);
DeferredResult<String> result = new DeferredResult<String>("timed out");
DeferredResult<String> result = new DeferredResult<String>(null, "timed out");
result.setResultHandler(handler);
assertTrue(result.applyTimeoutResult());

View File

@ -135,4 +135,10 @@ public class StandardServletAsyncWebRequestTests {
verify(timeoutHandler);
}
@Test(expected=IllegalStateException.class)
public void setTimeoutDuringConcurrentHandling() {
this.asyncRequest.startAsync();
this.asyncRequest.setTimeout(25L);
}
}

View File

@ -16,26 +16,26 @@
package org.springframework.web.context.request.async;
import static org.hamcrest.Matchers.containsString;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.notNull;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
/**
@ -47,168 +47,140 @@ public class WebAsyncManagerTests {
private WebAsyncManager asyncManager;
private MockHttpServletRequest request;
private StubAsyncWebRequest stubAsyncWebRequest;
private AsyncWebRequest asyncWebRequest;
@Before
public void setUp() {
this.request = new MockHttpServletRequest();
this.stubAsyncWebRequest = new StubAsyncWebRequest(this.request, new MockHttpServletResponse());
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
this.asyncManager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest());
this.asyncManager.setTaskExecutor(new SyncTaskExecutor());
this.asyncManager.setAsyncWebRequest(this.stubAsyncWebRequest);
}
@Test
public void getForCurrentRequest() throws Exception {
assertNotNull(this.asyncManager);
assertSame(this.asyncManager, AsyncWebUtils.getAsyncManager(this.request));
assertSame(this.asyncManager, this.request.getAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE));
this.asyncWebRequest = createStrictMock(AsyncWebRequest.class);
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
replay(this.asyncWebRequest);
this.asyncManager.setAsyncWebRequest(this.asyncWebRequest);
verify(this.asyncWebRequest);
reset(this.asyncWebRequest);
}
@Test
public void isConcurrentHandlingStarted() {
expect(this.asyncWebRequest.isAsyncStarted()).andReturn(false);
replay(this.asyncWebRequest);
assertFalse(this.asyncManager.isConcurrentHandlingStarted());
this.stubAsyncWebRequest.startAsync();
verify(this.asyncWebRequest);
reset(this.asyncWebRequest);
expect(this.asyncWebRequest.isAsyncStarted()).andReturn(true);
replay(this.asyncWebRequest);
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
verify(this.asyncWebRequest);
}
@Test(expected=IllegalArgumentException.class)
public void setAsyncWebRequestAfterAsyncStarted() {
this.stubAsyncWebRequest.startAsync();
this.asyncWebRequest.startAsync();
this.asyncManager.setAsyncWebRequest(null);
}
@Test
public void startCallableChainProcessing() throws Exception {
public void startCallableProcessing() throws Exception {
WebAsyncThreadInitializer initializer = createStrictMock(WebAsyncThreadInitializer.class);
initializer.initialize();
initializer.reset();
replay(initializer);
this.asyncWebRequest.startAsync();
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest);
this.asyncManager.registerAsyncThreadInitializer("testInitializer", initializer);
this.asyncManager.startCallableProcessing(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
assertTrue(this.stubAsyncWebRequest.isDispatched());
verify(initializer, this.asyncWebRequest);
}
@Test
public void startCallableChainProcessingStaleRequest() {
this.stubAsyncWebRequest.setAsyncComplete(true);
this.asyncManager.startCallableProcessing(new Callable<Object>() {
public Object call() throws Exception {
return 1;
}
});
public void startCallableProcessingAsyncTask() {
assertFalse(this.stubAsyncWebRequest.isDispatched());
AsyncTaskExecutor executor = createMock(AsyncTaskExecutor.class);
expect(executor.submit((Runnable) notNull())).andReturn(null);
replay(executor);
this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest);
AsyncTask asyncTask = new AsyncTask(1000L, executor, createMock(Callable.class));
this.asyncManager.startCallableProcessing(asyncTask);
verify(executor, this.asyncWebRequest);
}
@Test
public void startCallableChainProcessingCallableRequired() {
public void startCallableProcessingNullCallable() {
try {
this.asyncManager.startCallableProcessing(null);
this.asyncManager.startCallableProcessing((Callable<?>) null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertEquals(ex.getMessage(), "Callable is required");
assertEquals(ex.getMessage(), "Callable must not be null");
}
}
@Test
public void startCallableChainProcessingAsyncWebRequestRequired() {
this.request.removeAttribute(AsyncWebUtils.WEB_ASYNC_MANAGER_ATTRIBUTE);
this.asyncManager = AsyncWebUtils.getAsyncManager(this.request);
public void startCallableProcessingNullRequest() {
WebAsyncManager manager = AsyncWebUtils.getAsyncManager(new MockHttpServletRequest());
try {
this.asyncManager.startCallableProcessing(new Callable<Object>() {
manager.startCallableProcessing(new Callable<Object>() {
public Object call() throws Exception {
return null;
return 1;
}
});
fail("Expected exception");
}
catch (IllegalStateException ex) {
assertEquals(ex.getMessage(), "AsyncWebRequest was not set");
assertEquals(ex.getMessage(), "AsyncWebRequest must not be null");
}
}
@Test
public void startDeferredResultProcessing() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.asyncWebRequest.setTimeout(1000L);
this.asyncWebRequest.addCompletionHandler((Runnable) notNull());
this.asyncWebRequest.setTimeoutHandler((Runnable) notNull());
this.asyncWebRequest.startAsync();
replay(this.asyncWebRequest);
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>(1000L, 10);
this.asyncManager.startDeferredResultProcessing(deferredResult);
assertTrue(this.asyncManager.isConcurrentHandlingStarted());
verify(this.asyncWebRequest);
reset(this.asyncWebRequest);
expect(this.asyncWebRequest.isAsyncComplete()).andReturn(false);
this.asyncWebRequest.dispatch();
replay(this.asyncWebRequest);
deferredResult.setResult(25);
assertEquals(25, this.asyncManager.getConcurrentResult());
verify(this.asyncWebRequest);
}
@Test
public void startDeferredResultProcessingStaleRequest() throws Exception {
DeferredResult<Integer> deferredResult = new DeferredResult<Integer>();
this.asyncManager.startDeferredResultProcessing(deferredResult);
this.stubAsyncWebRequest.setAsyncComplete(true);
assertFalse(deferredResult.setResult(1));
}
@Test
public void startDeferredResultProcessingDeferredResultRequired() {
try {
this.asyncManager.startDeferredResultProcessing(null);
fail("Expected exception");
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString("DeferredResult is required"));
}
}
private static class StubAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest {
private boolean asyncStarted;
private boolean dispatched;
private boolean asyncComplete;
public StubAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
public void setTimeout(Long timeout) { }
public void setTimeoutHandler(Runnable runnable) { }
public void startAsync() {
this.asyncStarted = true;
}
public boolean isAsyncStarted() {
return this.asyncStarted;
}
public void dispatch() {
this.dispatched = true;
}
public boolean isDispatched() {
return dispatched;
}
public void setAsyncComplete(boolean asyncComplete) {
this.asyncComplete = asyncComplete;
}
public boolean isAsyncComplete() {
return this.asyncComplete;
}
public void addCompletionHandler(Runnable runnable) {
}
}
@SuppressWarnings("serial")
private static class SyncTaskExecutor extends SimpleAsyncTaskExecutor {

View File

@ -49,7 +49,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.AsyncThreadInitializer;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
import org.springframework.web.context.support.ServletRequestHandledEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
@ -991,9 +991,9 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
}
private AsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) {
private WebAsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) {
return new AsyncThreadInitializer() {
return new WebAsyncThreadInitializer() {
public void initialize() {
initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request));
}

View File

@ -1,73 +0,0 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import javax.servlet.ServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Handles return values of type {@link Callable} and {@link DeferredResult}.
*
* <p>This handler does not have a defined behavior for {@code null} return
* values and will raise an {@link IllegalArgumentException}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class AsyncMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return Callable.class.isAssignableFrom(paramType) || DeferredResult.class.isAssignableFrom(paramType);
}
@SuppressWarnings("unchecked")
public void handleReturnValue(Object returnValue,
MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
Assert.notNull(returnValue, "A Callable or a DeferredValue is required");
Class<?> paramType = returnType.getParameterType();
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(servletRequest);
if (Callable.class.isAssignableFrom(paramType)) {
asyncManager.startCallableProcessing((Callable<Object>) returnValue, mavContainer);
}
else if (DeferredResult.class.isAssignableFrom(paramType)) {
asyncManager.startDeferredResultProcessing((DeferredResult<?>) returnValue, mavContainer);
}
else {
// should never happen..
Method method = returnType.getMethod();
throw new UnsupportedOperationException("Unknown return value: " + paramType + " in method: " + method);
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Handles return values of type {@link AsyncTask}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private final BeanFactory beanFactory;
public AsyncTaskMethodReturnValueHandler(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return AsyncTask.class.isAssignableFrom(paramType);
}
public void handleReturnValue(Object returnValue,
MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
AsyncTask asyncTask = (AsyncTask) returnValue;
asyncTask.setBeanFactory(this.beanFactory);
AsyncWebUtils.getAsyncManager(webRequest).startCallableProcessing(asyncTask.getCallable(), mavContainer);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.concurrent.Callable;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Handles return values of type {@link Callable}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return Callable.class.isAssignableFrom(paramType);
}
public void handleReturnValue(Object returnValue,
MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
Callable<?> callable = (Callable<?>) returnValue;
AsyncWebUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Handles return values of type {@link DeferredResult}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return DeferredResult.class.isAssignableFrom(paramType);
}
public void handleReturnValue(Object returnValue,
MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
DeferredResult<?> deferredResult = (DeferredResult<?>) returnValue;
AsyncWebUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
@ -27,13 +26,11 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.transform.Source;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
@ -50,7 +47,6 @@ import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.ui.ModelMap;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.accept.ContentNegotiationManager;
@ -68,7 +64,6 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.AsyncWebUtils;
import org.springframework.web.context.request.async.NoSupportAsyncWebRequest;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
@ -552,7 +547,9 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
handlers.add(new AsyncMethodReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
@ -690,7 +687,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = createAsyncWebRequest(request, response);
AsyncWebRequest asyncWebRequest = AsyncWebUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
final WebAsyncManager asyncManager = AsyncWebUtils.getAsyncManager(request);
@ -700,7 +697,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.resetConcurrentResult();
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
@ -802,23 +799,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
private AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ?
createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response);
}
private AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
try {
String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest";
Class<?> clazz = ClassUtils.forName(className, this.getClass().getClassLoader());
Constructor<?> constructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class);
return (AsyncWebRequest) BeanUtils.instantiateClass(constructor, request, response);
}
catch (Throwable t) {
throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t);
}
}
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {