Add beforeConcurrentHandling support

Previously CallableProcessingInterceptor and
DeferredResultProcessingInterceptor did not have support for capturing
the state of the original Thread just prior to processing. This made it
difficult to transfer the state of one Thread (i.e. ThreadLocal) to the
Thread used to process the Callable.

This commit adds a new method to CallableProcessingInterceptor and
DeferredResultProcessingInterceptor named beforeConcurrentHandling
which will be invoked on the original Thread used to submit the Callable
or DeferredResult. This means the state of the original Thread can be
captured in beforeConcurrentHandling and transfered to the new Thread
in preProcess.

Issue: SPR-10052
This commit is contained in:
Rob Winch
2012-11-29 11:55:53 -06:00
committed by Rossen Stoyanchev
parent 0d73d199ec
commit 1e62ad8665
9 changed files with 143 additions and 7 deletions

View File

@@ -26,6 +26,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* Assists with the invocation of {@link CallableProcessingInterceptor}'s.
*
* @author Rossen Stoyanchev
* @author Rob Winch
* @since 3.2
*/
class CallableInterceptorChain {
@@ -41,6 +42,12 @@ class CallableInterceptorChain {
this.interceptors = interceptors;
}
public void applyBeforeConcurrentHandling(NativeWebRequest request, Callable<?> task) throws Exception {
for (CallableProcessingInterceptor interceptor : this.interceptors) {
interceptor.beforeConcurrentHandling(request, task);
}
}
public void applyPreProcess(NativeWebRequest request, Callable<?> task) throws Exception {
for (CallableProcessingInterceptor interceptor : this.interceptors) {
interceptor.preProcess(request, task);

View File

@@ -39,6 +39,7 @@ import org.springframework.web.context.request.NativeWebRequest;
* can select a value to be used to resume processing.
*
* @author Rossen Stoyanchev
* @author Rob Winch
* @since 3.2
*/
public interface CallableProcessingInterceptor {
@@ -47,6 +48,24 @@ public interface CallableProcessingInterceptor {
static final Object RESPONSE_HANDLED = new Object();
/**
* Invoked <em>before</em> the start of concurrent handling in the original
* thread in which the {@code Callable} is submitted for concurrent handling.
*
* <p>
* This is useful for capturing the state of the current thread just prior to
* invoking the {@link Callable}. Once the state is captured, it can then be
* transfered to the new {@link Thread} in
* {@link #preProcess(NativeWebRequest, Callable)}. Capturing the state of
* Spring Security's SecurityContextHolder and migrating it to the new Thread
* is a concrete example of where this is useful.
* </p>
*
* @param request the current request
* @param task the task for the current async request
* @throws Exception in case of errors
*/
<T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception;
/**
* Invoked <em>after</em> the start of concurrent handling in the async

View File

@@ -24,10 +24,17 @@ import org.springframework.web.context.request.NativeWebRequest;
* for simplified implementation of individual methods.
*
* @author Rossen Stoyanchev
* @author Rob Winch
* @since 3.2
*/
public abstract class CallableProcessingInterceptorAdapter implements CallableProcessingInterceptor {
/**
* This implementation is empty.
*/
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
}
/**
* This implementation is empty.
*/

View File

@@ -40,6 +40,12 @@ class DeferredResultInterceptorChain {
this.interceptors = interceptors;
}
public void applyBeforeConcurrentHandling(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
interceptor.beforeConcurrentHandling(request, deferredResult);
}
}
public void applyPreProcess(NativeWebRequest request, DeferredResult<?> deferredResult) throws Exception {
for (DeferredResultProcessingInterceptor interceptor : this.interceptors) {
interceptor.preProcess(request, deferredResult);

View File

@@ -36,10 +36,22 @@ import org.springframework.web.context.request.NativeWebRequest;
* method can set the {@code DeferredResult} in order to resume processing.
*
* @author Rossen Stoyanchev
* @author Rob Winch
* @since 3.2
*/
public interface DeferredResultProcessingInterceptor {
/**
* Invoked immediately before the start of concurrent handling, in the same
* thread that started it. This method may be used to capture state just prior
* to the start of concurrent processing with the given {@code DeferredResult}.
*
* @param request the current request
* @param deferredResult the DeferredResult for the current request
* @throws Exception in case of errors
*/
<T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception;
/**
* Invoked immediately after the start of concurrent handling, in the same
* thread that started it. This method may be used to detect the start of

View File

@@ -22,10 +22,17 @@ import org.springframework.web.context.request.NativeWebRequest;
* interface for simplified implementation of individual methods.
*
* @author Rossen Stoyanchev
* @author Rob Winch
* @since 3.2
*/
public abstract class DeferredResultProcessingInterceptorAdapter implements DeferredResultProcessingInterceptor {
/**
* This implementation is empty.
*/
public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
}
/**
* This implementation is empty.
*/

View File

@@ -249,12 +249,13 @@ 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()}
* @throws Exception If concurrent processing failed to start
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) {
public void startCallableProcessing(final Callable<?> callable, Object... processingContext) throws Exception {
Assert.notNull(callable, "Callable must not be null");
startCallableProcessing(new WebAsyncTask(callable), processingContext);
}
@@ -267,8 +268,9 @@ public final class WebAsyncManager {
* @param webAsyncTask an WebAsyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
* @throws Exception If concurrent processing failed to start
*/
public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext) {
public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext) throws Exception {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
@@ -306,6 +308,8 @@ public final class WebAsyncManager {
}
});
interceptorChain.applyBeforeConcurrentHandling(asyncWebRequest, callable);
startAsyncProcessing(processingContext);
this.taskExecutor.submit(new Runnable() {
@@ -356,12 +360,13 @@ public final class WebAsyncManager {
* @param deferredResult the DeferredResult instance to initialize
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
* @throws Exception If concurrent processing failed to start
*
* @see #getConcurrentResult()
* @see #getConcurrentResultContext()
*/
public void startDeferredResultProcessing(
final DeferredResult<?> deferredResult, Object... processingContext) {
final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {
Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
@@ -395,6 +400,8 @@ public final class WebAsyncManager {
}
});
interceptorChain.applyBeforeConcurrentHandling(asyncWebRequest, deferredResult);
startAsyncProcessing(processingContext);
try {