@ -16,8 +16,8 @@
package org.springframework.web.context.request.async ;
package org.springframework.web.context.request.async ;
import java.util.ArrayList ;
import java.util.ArrayDeque ;
import java.util.List ;
import java.util.Deque ;
import java.util.concurrent.Callable ;
import java.util.concurrent.Callable ;
import javax.servlet.ServletRequest ;
import javax.servlet.ServletRequest ;
@ -31,18 +31,20 @@ import org.springframework.web.context.request.async.DeferredResult.DeferredResu
/ * *
/ * *
* The central class for managing async request processing , mainly intended as
* The central class for managing async request processing , mainly intended as
* an SPI and typically not by non - framework classes .
* an SPI and not typically used directly by application classes .
*
*
* < p > An async execution chain consists of a sequence of Callable instances and
* < p > An async execution chain consists of a sequence of Callable instances that
* represents the work required to complete request processing in a separate
* represent the work required to complete request processing in a separate thread .
* thread . To construct the chain , each layer in the call stack of a normal
* To construct the chain , each level of the call stack pushes an
* request ( e . g . filter , servlet ) may contribute an
* { @link AbstractDelegatingCallable } during the course of a normal request and
* { @link AbstractDelegatingCallable } when a request is being processed .
* pops ( removes ) it on the way out . If async processing has not started , the pop
* For example the DispatcherServlet might contribute a Callable that
* operation succeeds and the processing continues as normal , or otherwise if async
* performs view resolution while a HandlerAdapter might contribute a Callable
* processing has begun , the main processing thread must be exited .
* that returns the ModelAndView , etc . The last Callable is the one that
*
* actually produces an application - specific value , for example the Callable
* < p > For example the DispatcherServlet might contribute a Callable that completes
* returned by an { @code @RequestMapping } method .
* view resolution or the HandlerAdapter might contribute a Callable that prepares a
* ModelAndView while the last Callable in the chain is usually associated with the
* application , e . g . the return value of an { @code @RequestMapping } method .
*
*
* @author Rossen Stoyanchev
* @author Rossen Stoyanchev
* @since 3 . 2
* @since 3 . 2
@ -51,13 +53,13 @@ public final class AsyncExecutionChain {
public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain . class . getName ( ) + ".CALLABLE_CHAIN" ;
public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain . class . getName ( ) + ".CALLABLE_CHAIN" ;
private final List < AbstractDelegatingCallable > delegatingCallables = new ArrayList < AbstractDelegatingCallable > ( ) ;
private final Deque < AbstractDelegatingCallable > callables = new ArrayDeque < AbstractDelegatingCallable > ( ) ;
private Callable < Object > c allable;
private Callable < Object > lastC allable;
private AsyncWebRequest asyncWebRequest ;
private AsyncWebRequest asyncWebRequest ;
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor ( "AsyncExecutionChain " ) ;
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor ( "Mvc Async" ) ;
/ * *
/ * *
* Private constructor
* Private constructor
@ -68,7 +70,7 @@ public final class AsyncExecutionChain {
/ * *
/ * *
* Obtain the AsyncExecutionChain for the current request .
* Obtain the AsyncExecutionChain for the current request .
* Or if not found , create an ins tance and associate it with the request .
* Or if not found , create it and associate it with the request .
* /
* /
public static AsyncExecutionChain getForCurrentRequest ( ServletRequest request ) {
public static AsyncExecutionChain getForCurrentRequest ( ServletRequest request ) {
AsyncExecutionChain chain = ( AsyncExecutionChain ) request . getAttribute ( CALLABLE_CHAIN_ATTRIBUTE ) ;
AsyncExecutionChain chain = ( AsyncExecutionChain ) request . getAttribute ( CALLABLE_CHAIN_ATTRIBUTE ) ;
@ -81,7 +83,7 @@ public final class AsyncExecutionChain {
/ * *
/ * *
* Obtain the AsyncExecutionChain for the current request .
* Obtain the AsyncExecutionChain for the current request .
* Or if not found , create an ins tance and associate it with the request .
* Or if not found , create it and associate it with the request .
* /
* /
public static AsyncExecutionChain getForCurrentRequest ( WebRequest request ) {
public static AsyncExecutionChain getForCurrentRequest ( WebRequest request ) {
int scope = RequestAttributes . SCOPE_REQUEST ;
int scope = RequestAttributes . SCOPE_REQUEST ;
@ -94,105 +96,106 @@ public final class AsyncExecutionChain {
}
}
/ * *
/ * *
* Provide an instance of an AsyncWebRequest .
* Provide an instance of an AsyncWebRequest - - required for async processing .
* This property must be set before async request processing can begin .
* /
* /
public void setAsyncWebRequest ( AsyncWebRequest asyncRequest ) {
public void setAsyncWebRequest ( AsyncWebRequest asyncRequest ) {
Assert . state ( ! isAsyncStarted ( ) , "Cannot set AsyncWebRequest after the start of async processing." ) ;
this . asyncWebRequest = asyncRequest ;
this . asyncWebRequest = asyncRequest ;
}
}
/ * *
/ * *
* Provide an AsyncTaskExecutor to use when
* Provide an AsyncTaskExecutor for use wit h { @link # startCallableProcessing ( ) } .
* { @link # startCallableChainProcessing ( ) } is invoked , for example when a
* < p > By default a { @link SimpleAsyncTaskExecutor } instance is used . Applications are
* controller method returns a Callabl e.
* advised to provide a TaskExecutor configured for production us e.
* < p > By default a { @link SimpleAsyncTaskExecutor } instance is used .
* @see org . springframework . web . servlet . mvc . method . annotation . RequestMappingHandlerAdapter # setAsyncTaskExecutor
* /
* /
public void setTaskExecutor ( AsyncTaskExecutor taskExecutor ) {
public void setTaskExecutor ( AsyncTaskExecutor taskExecutor ) {
this . taskExecutor = taskExecutor ;
this . taskExecutor = taskExecutor ;
}
}
/ * *
/ * *
* Whether async request processing has started through one of :
* Push an async Callable for the current stack level . This method should be
* < ul >
* invoked before delegating to the next level of the stack where async
* < li > { @link # startCallableChainProcessing ( ) }
* processing may start .
* < li > { @link # startDeferredResultProcessing ( DeferredResult ) }
* < / ul >
* /
* /
public boolean isAsyncStarted ( ) {
public void push ( AbstractDelegatingCallable callable ) {
return ( ( this . asyncWebRequest ! = null ) & & this . asyncWebRequest . isAsyncStarted ( ) ) ;
Assert . notNull ( callable , "Async Callable is required" ) ;
this . callables . addFirst ( callable ) ;
}
}
/ * *
/ * *
* Add a Callable with logic required to complete request processing in a
* Pop the Callable of the current stack level . Ensure this method is invoked
* separate thread . See { @link AbstractDelegatingCallable } for details .
* after delegation to the next level of the stack where async processing may
* start . The pop operation succeeds if async processing did not start .
* @return { @code true } if the Callable was removed , or { @code false }
* otherwise ( i . e . async started ) .
* /
* /
public void addDelegatingCallable ( AbstractDelegatingCallable callable ) {
public boolean pop ( ) {
Assert . notNull ( callable , "Callable required" ) ;
if ( isAsyncStarted ( ) ) {
this . delegatingCallables . add ( callable ) ;
return false ;
}
else {
this . callables . removeFirst ( ) ;
return true ;
}
}
}
/ * *
/ * *
* Add the last Callable , for example the one returned by the controller .
* Whether async request processing has started .
* This property must be set prior to invoking
* { @link # startCallableChainProcessing ( ) } .
* /
* /
public AsyncExecutionChain setCallable ( Callable < Object > callable ) {
public boolean isAsyncStarted ( ) {
Assert . notNull ( callable , "Callable required" ) ;
return ( ( this . asyncWebRequest ! = null ) & & this . asyncWebRequest . isAsyncStarted ( ) ) ;
this . callable = callable ;
return this ;
}
}
/ * *
/ * *
* Start the async execution chain by submitting an
* Set the last Callable , e . g . the one returned by the controller .
* { @link AsyncExecutionChainRunnable } instance to the TaskExecutor provided via
* { @link # setTaskExecutor ( AsyncTaskExecutor ) } and returning immediately .
* @see AsyncExecutionChainRunnable
* /
* /
public void startCallableChainProcessing ( ) {
public AsyncExecutionChain setLastCallable ( Callable < Object > callable ) {
startAsync ( ) ;
Assert . notNull ( callable , "Callable required" ) ;
this . taskExecutor . execute ( new AsyncExecutionChainRunnable ( this . asyncWebRequest , buildChain ( ) ) ) ;
this . lastCallable = callable ;
return this ;
}
}
private void startAsync ( ) {
/ * *
Assert . state ( this . asyncWebRequest ! = null , "An AsyncWebRequest is required to start async processing" ) ;
* Start async processing and execute the async chain with an AsyncTaskExecutor .
* This method returns immediately .
* /
public void startCallableProcessing ( ) {
Assert . state ( this . asyncWebRequest ! = null , "AsyncWebRequest was not set" ) ;
this . asyncWebRequest . startAsync ( ) ;
this . asyncWebRequest . startAsync ( ) ;
this . taskExecutor . execute ( new AsyncExecutionChainRunnable ( this . asyncWebRequest , buildChain ( ) ) ) ;
}
}
private Callable < Object > buildChain ( ) {
private Callable < Object > buildChain ( ) {
Assert . state ( this . callable ! = null , "The last callable is required to build the async chain" ) ;
Assert . state ( this . lastCallable ! = null , "The last Callable was not set" ) ;
this . delegatingCallables . add ( new StaleAsyncRequestCheckingCallable ( asyncWebRequest ) ) ;
AbstractDelegatingCallable head = new StaleAsyncRequestCheckingCallable ( this . asyncWebRequest ) ;
Callable < Object > result = this . callable ;
head . setNext ( this . lastCallable ) ;
for ( int i = this . delegatingCallables . size ( ) - 1 ; i > = 0 ; i - - ) {
for ( AbstractDelegatingCallable callable : this . callables ) {
AbstractDelegatingCallable callable = this . delegatingCallables . get ( i ) ;
callable . setNext ( head ) ;
callable . setNextCallable ( result ) ;
head = callable ;
result = callable ;
}
}
return result ;
return head ;
}
}
/ * *
/ * *
* Mark the start of async request processing accepting the provided
* Start async processing and initialize the given DeferredResult so when
* DeferredResult and initializing it such that if
* its value is set , the async chain is executed with an AsyncTaskExecutor .
* { @link DeferredResult # set ( Object ) } is called ( from another thread ) ,
* the set Object value will be processed with the execution chain by
* invoking { @link AsyncExecutionChainRunnable } .
* < p > The resulting processing from this method is identical to
* { @link # startCallableChainProcessing ( ) } . The main difference is in
* the threading model , i . e . whether a TaskExecutor is used .
* @see DeferredResult
* /
* /
public void startDeferredResultProcessing ( final DeferredResult < ? > deferredResult ) {
public void startDeferredResultProcessing ( final DeferredResult < ? > deferredResult ) {
Assert . notNull ( deferredResult , "DeferredResult is required" ) ;
Assert . notNull ( deferredResult , "DeferredResult is required" ) ;
startAsync ( ) ;
Assert . state ( this . asyncWebRequest ! = null , "AsyncWebRequest was not set" ) ;
this . asyncWebRequest . startAsync ( ) ;
deferredResult . init ( new DeferredResultHandler ( ) {
deferredResult . init ( new DeferredResultHandler ( ) {
public void handle ( Object result ) {
public void handle ( Object result ) {
if ( asyncWebRequest . isAsyncCompleted ( ) ) {
if ( asyncWebRequest . isAsyncCompleted ( ) ) {
throw new StaleAsyncWebRequestException ( "Async request processing already completed" ) ;
throw new StaleAsyncWebRequestException ( "Too late to set DeferredResult: " + result ) ;
}
}
setCallable ( new PassThroughCallable ( result ) ) ;
setLast Callable ( new PassThroughCallable ( result ) ) ;
new AsyncExecutionChainRunnable ( asyncWebRequest , buildChain ( ) ) . run ( ) ;
taskExecutor . execute ( new AsyncExecutionChainRunnable ( asyncWebRequest , buildChain ( ) ) ) ;
}
}
} ) ;
} ) ;
this . asyncWebRequest . setTimeoutHandler ( deferredResult . getTimeoutHandler ( ) ) ;
this . asyncWebRequest . setTimeoutHandler ( deferredResult . getTimeoutHandler ( ) ) ;
}
}