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:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Support for asynchronous request processing.
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
package org.springframework.web.context.request.async;
|
||||
|
||||
Reference in New Issue
Block a user