Add interceptors for async processing

This change introduces two new interceptors with callback methods
for concurrent request handling. These interfaces are
CallableProcessingInterceptor and DeferredResultProcessingInterceptor.

Unlike a HandlerInterceptor, and its AsyncHandlerInterceptor sub-type,
which intercepts the invocation of a handler in he main request
processing thread, the two new interfaces are aimed at intercepting the
asynchronous execution of a Callable or a DeferredResult.

This allows for the registration of thread initialization logic in the
case of Callable executed with an AsyncTaskExecutor, or for centralized
tracking of the completion and/or expiration of a DeferredResult.
This commit is contained in:
Rossen Stoyanchev
2012-09-19 09:25:50 -04:00
parent 30bce7fa16
commit 57c36dd395
24 changed files with 735 additions and 267 deletions

View File

@@ -41,6 +41,11 @@ public interface AsyncWebRequest extends NativeWebRequest {
*/
void setTimeoutHandler(Runnable runnable);
/**
* Add a Runnable to be invoked when request processing completes.
*/
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
@@ -62,17 +67,6 @@ public interface AsyncWebRequest extends NativeWebRequest {
*/
void dispatch();
/**
* Whether the request was dispatched to the container in order to resume
* processing after concurrent execution in an application thread.
*/
boolean isDispatched();
/**
* Add a Runnable to be invoked when request processing completes.
*/
void addCompletionHandler(Runnable runnable);
/**
* Whether asynchronous processing has completed.
*/

View File

@@ -0,0 +1,64 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Assists with the invocation of {@link CallableProcessingInterceptor}'s.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
class CallableInterceptorChain {
private static Log logger = LogFactory.getLog(CallableInterceptorChain.class);
private final List<CallableProcessingInterceptor> interceptors;
private int interceptorIndex = -1;
public CallableInterceptorChain(Collection<CallableProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<CallableProcessingInterceptor>(interceptors);
}
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {
for (int i = 0; i < this.interceptors.size(); i++) {
this.interceptors.get(i).preProcess(request, task);
this.interceptorIndex = i;
}
}
public void applyPostProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) {
for (int i = this.interceptorIndex; i >= 0; i--) {
try {
this.interceptors.get(i).postProcess(request, task, concurrentResult);
}
catch (Exception ex) {
logger.error("CallableProcessingInterceptor.postProcess threw exception", ex);
}
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.core.task.AsyncTaskExecutor;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/**
* Intercepts concurrent request handling, where the concurrent result is
* obtained by executing a {@link Callable} on behalf of the application with an
* {@link AsyncTaskExecutor}.
* <p>
* A {@code CallableProcessingInterceptor} is invoked before and after the
* invocation of the {@code Callable} task in the asynchronous thread.
*
* <p>A {@code CallableProcessingInterceptor} may be registered as follows:
* <pre>
* CallableProcessingInterceptor interceptor = ... ;
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
* asyncManager.registerCallableInterceptor("key", interceptor);
* </pre>
*
* <p>To register an interceptor for every request, the above can be done through
* a {@link WebRequestInterceptor} during pre-handling.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface CallableProcessingInterceptor {
/**
* Invoked from the asynchronous thread in which the {@code Callable} is
* executed, before the {@code Callable} is invoked.
*
* @param request the current request
* @param task the task that will produce a result
*/
void preProcess(NativeWebRequest request, Callable<?> task) throws Exception;
/**
* Invoked from the asynchronous thread in which the {@code Callable} is
* executed, after the {@code Callable} returned a result.
*
* @param request the current request
* @param task the task that produced the result
* @param concurrentResult the result of concurrent processing, which could
* be a {@link Throwable} if the {@code Callable} raised an exception
*/
void postProcess(NativeWebRequest request, Callable<?> task, Object concurrentResult) throws Exception;
}

View File

@@ -25,9 +25,9 @@ import org.apache.commons.logging.LogFactory;
/**
* {@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.
* for asynchronous request processing. While a {@code Callable} is executed
* concurrently on behalf of the application, with a {@code DeferredResult} the
* application can produce the result from a thread of its choice.
*
* @author Rossen Stoyanchev
* @since 3.2
@@ -45,8 +45,6 @@ public final class DeferredResult<T> {
private DeferredResultHandler resultHandler;
private Object result = RESULT_NONE;
private final AtomicBoolean expired = new AtomicBoolean(false);
private final Object lock = new Object();
@@ -79,7 +77,6 @@ public final class DeferredResult<T> {
this.timeout = timeout;
}
/**
* Return the configured timeout value in milliseconds.
*/
@@ -88,8 +85,12 @@ public final class DeferredResult<T> {
}
/**
* Set a handler to handle the result when set. Normally applications do not
* use this method at runtime but may do so during testing.
* Set a handler to handle the result when set. There can be only handler
* for a {@code DeferredResult}. At runtime it will be set by the framework.
* However applications may set it when unit testing.
*
* <p>If you need to be called back when a {@code DeferredResult} is set or
* expires, register a {@link DeferredResultProcessingInterceptor} instead.
*/
public void setResultHandler(DeferredResultHandler resultHandler) {
this.resultHandler = resultHandler;
@@ -122,14 +123,14 @@ public final class DeferredResult<T> {
}
private boolean processResult(Object result) {
synchronized (this.lock) {
if (isSetOrExpired()) {
boolean wasExpired = getAndSetExpired();
if (wasExpired) {
return false;
}
this.result = result;
if (!awaitResultHandler()) {
throw new IllegalStateException("DeferredResultHandler not set");
}
@@ -156,15 +157,24 @@ public final class DeferredResult<T> {
}
/**
* Whether the DeferredResult can no longer be set either because the async
* request expired or because it was already set.
* Return {@code true} if this DeferredResult is no longer usable either
* because it was previously set or because the underlying request ended
* before it could be set.
* <p>
* The result may have been set with a call to {@link #setResult(Object)},
* or {@link #setErrorResult(Object)}, or following a timeout, assuming a
* timeout result was provided to the constructor. The request may before
* the result set due to a timeout or network error.
*/
public boolean isSetOrExpired() {
return (this.expired.get() || (this.result != RESULT_NONE));
return this.expired.get();
}
void setExpired() {
this.expired.set(true);
/**
* Atomically set the expired flag and return its previous value.
*/
boolean getAndSetExpired() {
return this.expired.getAndSet(true);
}
boolean hasTimeoutResult() {

View File

@@ -0,0 +1,71 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Assists with the invocation of {@link DeferredResultProcessingInterceptor}'s.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
class DeferredResultInterceptorChain {
private static Log logger = LogFactory.getLog(DeferredResultInterceptorChain.class);
private final List<DeferredResultProcessingInterceptor> interceptors;
public DeferredResultInterceptorChain(Collection<DeferredResultProcessingInterceptor> interceptors) {
this.interceptors = new ArrayList<DeferredResultProcessingInterceptor>(interceptors);
}
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> task) throws Exception {
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
interceptor.preProcess(request, task);
}
}
public void applyPostProcess(NativeWebRequest request, DeferredResult<?> task, Object concurrentResult) {
for (int i = this.interceptors.size()-1; i >= 0; i--) {
try {
this.interceptors.get(i).postProcess(request, task, concurrentResult);
}
catch (Exception ex) {
logger.error("DeferredResultProcessingInterceptor.postProcess threw exception", ex);
}
}
}
public void triggerAfterExpiration(NativeWebRequest request, DeferredResult<?> task) {
for (int i = this.interceptors.size()-1; i >= 0; i--) {
try {
this.interceptors.get(i).afterExpiration(request, task);
}
catch (Exception ex) {
logger.error("DeferredResultProcessingInterceptor.afterExpiration threw exception", ex);
}
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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 org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
/**
* Intercepts concurrent request handling, where the concurrent result is
* obtained by waiting for a {@link DeferredResult} to be set from a thread
* chosen by the application (e.g. in response to some external event).
*
* <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start of
* asynchronous processing and either when the {@code DeferredResult} is set or
* when when the underlying request ends, whichever comes fist.
*
* <p>A {@code DeferredResultProcessingInterceptor} may be registered as follows:
* <pre>
* DeferredResultProcessingInterceptor interceptor = ... ;
* WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
* asyncManager.registerDeferredResultInterceptor("key", interceptor);
* </pre>
*
* <p>To register an interceptor for every request, the above can be done through
* a {@link WebRequestInterceptor} during pre-handling.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface DeferredResultProcessingInterceptor {
/**
* Invoked before the start of concurrent handling using a
* {@link DeferredResult}. The invocation occurs in the thread that
* initiated concurrent handling.
*
* @param request the current request
* @param deferredResult the DeferredResult instance
*/
void preProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
/**
* Invoked when a {@link DeferredResult} is set either with a normal value
* or with a {@link DeferredResult#DeferredResult(Long, Object) timeout
* result}. The invocation occurs in the thread that set the result.
* <p>
* If the request ends before the {@code DeferredResult} is set, then
* {@link #afterExpiration(NativeWebRequest, DeferredResult)} is called.
*
* @param request the current request
* @param deferredResult the DeferredResult that has been set
* @param concurrentResult the result to which the {@code DeferredResult}
* was set
*/
void postProcess(NativeWebRequest request, DeferredResult<?> deferredResult,
Object concurrentResult) throws Exception;
/**
* Invoked when a {@link DeferredResult} expires before a result has been
* set possibly due to a timeout or a network error. This invocation occurs
* in the thread where the timeout or network error notification is
* processed.
*
* @param request the current request
* @param deferredResult the DeferredResult that has been set
*/
void afterExpiration(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception;
}

View File

@@ -49,10 +49,6 @@ public class NoSupportAsyncWebRequest extends ServletWebRequest implements Async
return false;
}
public boolean isDispatched() {
return false;
}
// Not supported
public void startAsync() {

View File

@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -94,10 +93,6 @@ public class StandardServletAsyncWebRequest extends ServletWebRequest implements
return ((this.asyncContext != null) && getRequest().isAsyncStarted());
}
public boolean isDispatched() {
return (DispatcherType.ASYNC.equals(getRequest().getDispatcherType()));
}
/**
* Whether async request processing has completed.
* <p>It is important to avoid use of request and response objects after async

View File

@@ -15,10 +15,7 @@
*/
package org.springframework.web.context.request.async;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -73,7 +70,11 @@ public final class WebAsyncManager {
private Object[] concurrentResultContext;
private final Map<Object, WebAsyncThreadInitializer> threadInitializers = new LinkedHashMap<Object, WebAsyncThreadInitializer>();
private final Map<Object, CallableProcessingInterceptor> callableInterceptors =
new LinkedHashMap<Object, CallableProcessingInterceptor>();
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
private static final UrlPathHelper urlPathHelper = new UrlPathHelper();
@@ -87,11 +88,13 @@ public final class WebAsyncManager {
}
/**
* 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}.
* 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) {
@@ -127,17 +130,18 @@ public final class WebAsyncManager {
}
/**
* Whether the request has been dispatched to process the result of
* concurrent handling.
* Whether a result value exists as a result of concurrent handling.
*/
public boolean hasConcurrentResult() {
return ((this.concurrentResult != RESULT_NONE) && this.asyncWebRequest.isDispatched());
return (this.concurrentResult != RESULT_NONE);
}
/**
* Provides access to the result from concurrent handling.
*
* @return an Object, possibly an {@code Exception} or {@code Throwable} if
* concurrent handling raised one.
* concurrent handling raised one.
* @see #clearConcurrentResult()
*/
public Object getConcurrentResult() {
return this.concurrentResult;
@@ -146,11 +150,46 @@ public final class WebAsyncManager {
/**
* Provides access to additional processing context saved at the start of
* concurrent handling.
*
* @see #clearConcurrentResult()
*/
public Object[] getConcurrentResultContext() {
return this.concurrentResultContext;
}
public CallableProcessingInterceptor getCallableInterceptor(Object key) {
return this.callableInterceptors.get(key);
}
public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) {
return this.deferredResultInterceptors.get(key);
}
/**
* Register a {@link CallableProcessingInterceptor} that will be applied
* when concurrent request handling with a {@link Callable} starts.
*
* @param key a unique the key under which to register the interceptor
* @param interceptor the interceptor to register
*/
public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) {
Assert.notNull(interceptor, "interceptor is required");
this.callableInterceptors.put(key, interceptor);
}
/**
* Register a {@link DeferredResultProcessingInterceptor} that will be
* applied when concurrent request handling with a {@link DeferredResult}
* starts.
*
* @param key a unique the key under which to register the interceptor
* @param interceptor the interceptor to register
*/
public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) {
Assert.notNull(interceptor, "interceptor is required");
this.deferredResultInterceptors.put(key, interceptor);
}
/**
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.
@@ -169,7 +208,7 @@ public final class WebAsyncManager {
*
* @param callable a unit of work to be executed asynchronously
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
* via {@link #getConcurrentResultContext()}
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
@@ -182,23 +221,19 @@ public final class WebAsyncManager {
this.taskExecutor.submit(new Runnable() {
public void run() {
List<WebAsyncThreadInitializer> initializers =
new ArrayList<WebAsyncThreadInitializer>(threadInitializers.values());
CallableInterceptorChain chain =
new CallableInterceptorChain(callableInterceptors.values());
try {
for (WebAsyncThreadInitializer initializer : initializers) {
initializer.initialize();
}
chain.applyPreProcess(asyncWebRequest, callable);
concurrentResult = callable.call();
}
catch (Throwable t) {
concurrentResult = t;
}
finally {
Collections.reverse(initializers);
for (WebAsyncThreadInitializer initializer : initializers) {
initializer.reset();
}
chain.applyPostProcess(asyncWebRequest, callable, concurrentResult);
}
if (logger.isDebugEnabled()) {
@@ -220,9 +255,10 @@ public final class WebAsyncManager {
* 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()}
* via {@link #getConcurrentResultContext()}
*/
public void startCallableProcessing(AsyncTask asyncTask, Object... processingContext) {
Assert.notNull(asyncTask, "AsyncTask must not be null");
@@ -241,21 +277,23 @@ public final class WebAsyncManager {
}
/**
* 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.
* 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 that can be accessed
* via {@link #getConcurrentResultContext()}
* via {@link #getConcurrentResultContext()}
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult, Object... processingContext) {
public void startDeferredResultProcessing(final DeferredResult<?> deferredResult,
Object... processingContext) throws Exception {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Long timeout = deferredResult.getTimeoutMilliseconds();
@@ -263,12 +301,6 @@ public final class WebAsyncManager {
this.asyncWebRequest.setTimeout(timeout);
}
this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() {
deferredResult.setExpired();
}
});
if (deferredResult.hasTimeoutResult()) {
this.asyncWebRequest.setTimeoutHandler(new Runnable() {
public void run() {
@@ -277,6 +309,19 @@ public final class WebAsyncManager {
});
}
final DeferredResultInterceptorChain chain =
new DeferredResultInterceptorChain(this.deferredResultInterceptors.values());
chain.applyPreProcess(this.asyncWebRequest, deferredResult);
this.asyncWebRequest.addCompletionHandler(new Runnable() {
public void run() {
if (!deferredResult.getAndSetExpired()) {
chain.triggerAfterExpiration(asyncWebRequest, deferredResult);
}
}
});
startAsyncProcessing(processingContext);
deferredResult.setResultHandler(new DeferredResultHandler() {
@@ -287,8 +332,7 @@ public final class WebAsyncManager {
logger.debug("Deferred result value [" + concurrentResult + "]");
}
Assert.state(!asyncWebRequest.isAsyncComplete(),
"Cannot handle DeferredResult [ " + deferredResult + " ] due to a timeout or network error");
chain.applyPostProcess(asyncWebRequest, deferredResult, result);
logger.debug("Dispatching request to complete processing");
asyncWebRequest.dispatch();
@@ -298,12 +342,12 @@ public final class WebAsyncManager {
private void startAsyncProcessing(Object[] processingContext) {
clearConcurrentResult();
this.concurrentResultContext = processingContext;
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
this.asyncWebRequest.startAsync();
this.concurrentResult = null;
this.concurrentResultContext = processingContext;
if (logger.isDebugEnabled()) {
HttpServletRequest request = asyncWebRequest.getNativeRequest(HttpServletRequest.class);
String requestUri = urlPathHelper.getRequestUri(request);
@@ -311,43 +355,4 @@ 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);
}
/**
* 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 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();
void reset();
}
}

View File

@@ -64,10 +64,11 @@ public abstract class WebAsyncUtils {
}
/**
* 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.
* Create an AsyncWebRequest instance. By default an instance of
* {@link StandardServletAsyncWebRequest} is created if running in Servlet
* 3.0 (or higher) environment or as a fallback, an instance of
* {@link NoSupportAsyncWebRequest} is returned.
*
* @param request the current request
* @param response the current response
* @return an AsyncWebRequest instance, never {@code null}