1616
1717package org .springframework .web .context .request .async ;
1818
19- import java .util .ArrayList ;
20- import java .util .List ;
19+ import java .util .ArrayDeque ;
20+ import java .util .Deque ;
2121import java .util .concurrent .Callable ;
2222
2323import javax .servlet .ServletRequest ;
3131
3232/**
3333 * The central class for managing async request processing, mainly intended as
34- * an SPI and typically not by non-framework classes.
34+ * an SPI and not typically used directly by application classes.
3535 *
36- * <p>An async execution chain consists of a sequence of Callable instances and
37- * represents the work required to complete request processing in a separate
38- * thread. To construct the chain, each layer in the call stack of a normal
39- * request (e.g. filter, servlet) may contribute an
40- * {@link AbstractDelegatingCallable} when a request is being processed.
41- * For example the DispatcherServlet might contribute a Callable that
42- * performs view resolution while a HandlerAdapter might contribute a Callable
43- * that returns the ModelAndView, etc. The last Callable is the one that
44- * actually produces an application-specific value, for example the Callable
45- * returned by an {@code @RequestMapping} method.
36+ * <p>An async execution chain consists of a sequence of Callable instances that
37+ * represent the work required to complete request processing in a separate thread.
38+ * To construct the chain, each level of the call stack pushes an
39+ * {@link AbstractDelegatingCallable} during the course of a normal request and
40+ * pops (removes) it on the way out. If async processing has not started, the pop
41+ * operation succeeds and the processing continues as normal, or otherwise if async
42+ * processing has begun, the main processing thread must be exited.
43+ *
44+ * <p>For example the DispatcherServlet might contribute a Callable that completes
45+ * view resolution or the HandlerAdapter might contribute a Callable that prepares a
46+ * ModelAndView while the last Callable in the chain is usually associated with the
47+ * application, e.g. the return value of an {@code @RequestMapping} method.
4648 *
4749 * @author Rossen Stoyanchev
4850 * @since 3.2
@@ -51,13 +53,13 @@ public final class AsyncExecutionChain {
5153
5254 public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain .class .getName () + ".CALLABLE_CHAIN" ;
5355
54- private final List <AbstractDelegatingCallable > delegatingCallables = new ArrayList <AbstractDelegatingCallable >();
56+ private final Deque <AbstractDelegatingCallable > callables = new ArrayDeque <AbstractDelegatingCallable >();
5557
56- private Callable <Object > callable ;
58+ private Callable <Object > lastCallable ;
5759
5860 private AsyncWebRequest asyncWebRequest ;
5961
60- private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor ("AsyncExecutionChain " );
62+ private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor ("MvcAsync " );
6163
6264 /**
6365 * Private constructor
@@ -68,7 +70,7 @@ private AsyncExecutionChain() {
6870
6971 /**
7072 * Obtain the AsyncExecutionChain for the current request.
71- * Or if not found, create an instance and associate it with the request.
73+ * Or if not found, create it and associate it with the request.
7274 */
7375 public static AsyncExecutionChain getForCurrentRequest (ServletRequest request ) {
7476 AsyncExecutionChain chain = (AsyncExecutionChain ) request .getAttribute (CALLABLE_CHAIN_ATTRIBUTE );
@@ -81,7 +83,7 @@ public static AsyncExecutionChain getForCurrentRequest(ServletRequest request) {
8183
8284 /**
8385 * Obtain the AsyncExecutionChain for the current request.
84- * Or if not found, create an instance and associate it with the request.
86+ * Or if not found, create it and associate it with the request.
8587 */
8688 public static AsyncExecutionChain getForCurrentRequest (WebRequest request ) {
8789 int scope = RequestAttributes .SCOPE_REQUEST ;
@@ -94,105 +96,106 @@ public static AsyncExecutionChain getForCurrentRequest(WebRequest request) {
9496 }
9597
9698 /**
97- * Provide an instance of an AsyncWebRequest.
98- * This property must be set before async request processing can begin.
99+ * Provide an instance of an AsyncWebRequest -- required for async processing.
99100 */
100101 public void setAsyncWebRequest (AsyncWebRequest asyncRequest ) {
102+ Assert .state (!isAsyncStarted (), "Cannot set AsyncWebRequest after the start of async processing." );
101103 this .asyncWebRequest = asyncRequest ;
102104 }
103105
104106 /**
105- * Provide an AsyncTaskExecutor to use when
106- * {@link #startCallableChainProcessing()} is invoked, for example when a
107- * controller method returns a Callable .
108- * <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
107+ * Provide an AsyncTaskExecutor for use with {@link #startCallableProcessing()}.
108+ * <p>By default a {@link SimpleAsyncTaskExecutor} instance is used. Applications are
109+ * advised to provide a TaskExecutor configured for production use .
110+ * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setAsyncTaskExecutor
109111 */
110112 public void setTaskExecutor (AsyncTaskExecutor taskExecutor ) {
111113 this .taskExecutor = taskExecutor ;
112114 }
113115
114116 /**
115- * Whether async request processing has started through one of:
116- * <ul>
117- * <li>{@link #startCallableChainProcessing()}
118- * <li>{@link #startDeferredResultProcessing(DeferredResult)}
119- * </ul>
117+ * Push an async Callable for the current stack level. This method should be
118+ * invoked before delegating to the next level of the stack where async
119+ * processing may start.
120120 */
121- public boolean isAsyncStarted () {
122- return ((this .asyncWebRequest != null ) && this .asyncWebRequest .isAsyncStarted ());
121+ public void push (AbstractDelegatingCallable callable ) {
122+ Assert .notNull (callable , "Async Callable is required" );
123+ this .callables .addFirst (callable );
123124 }
124125
125126 /**
126- * Add a Callable with logic required to complete request processing in a
127- * separate thread. See {@link AbstractDelegatingCallable} for details.
127+ * Pop the Callable of the current stack level. Ensure this method is invoked
128+ * after delegation to the next level of the stack where async processing may
129+ * start. The pop operation succeeds if async processing did not start.
130+ * @return {@code true} if the Callable was removed, or {@code false}
131+ * otherwise (i.e. async started).
128132 */
129- public void addDelegatingCallable (AbstractDelegatingCallable callable ) {
130- Assert .notNull (callable , "Callable required" );
131- this .delegatingCallables .add (callable );
133+ public boolean pop () {
134+ if (isAsyncStarted ()) {
135+ return false ;
136+ }
137+ else {
138+ this .callables .removeFirst ();
139+ return true ;
140+ }
132141 }
133142
134143 /**
135- * Add the last Callable, for example the one returned by the controller.
136- * This property must be set prior to invoking
137- * {@link #startCallableChainProcessing()}.
144+ * Whether async request processing has started.
138145 */
139- public AsyncExecutionChain setCallable (Callable <Object > callable ) {
140- Assert .notNull (callable , "Callable required" );
141- this .callable = callable ;
142- return this ;
146+ public boolean isAsyncStarted () {
147+ return ((this .asyncWebRequest != null ) && this .asyncWebRequest .isAsyncStarted ());
143148 }
144149
145150 /**
146- * Start the async execution chain by submitting an
147- * {@link AsyncExecutionChainRunnable} instance to the TaskExecutor provided via
148- * {@link #setTaskExecutor(AsyncTaskExecutor)} and returning immediately.
149- * @see AsyncExecutionChainRunnable
151+ * Set the last Callable, e.g. the one returned by the controller.
150152 */
151- public void startCallableChainProcessing () {
152- startAsync ();
153- this .taskExecutor .execute (new AsyncExecutionChainRunnable (this .asyncWebRequest , buildChain ()));
153+ public AsyncExecutionChain setLastCallable (Callable <Object > callable ) {
154+ Assert .notNull (callable , "Callable required" );
155+ this .lastCallable = callable ;
156+ return this ;
154157 }
155158
156- private void startAsync () {
157- Assert .state (this .asyncWebRequest != null , "An AsyncWebRequest is required to start async processing" );
159+ /**
160+ * Start async processing and execute the async chain with an AsyncTaskExecutor.
161+ * This method returns immediately.
162+ */
163+ public void startCallableProcessing () {
164+ Assert .state (this .asyncWebRequest != null , "AsyncWebRequest was not set" );
158165 this .asyncWebRequest .startAsync ();
166+ this .taskExecutor .execute (new AsyncExecutionChainRunnable (this .asyncWebRequest , buildChain ()));
159167 }
160168
161169 private Callable <Object > buildChain () {
162- Assert .state (this .callable != null , "The last callable is required to build the async chain" );
163- this .delegatingCallables .add (new StaleAsyncRequestCheckingCallable (asyncWebRequest ));
164- Callable <Object > result = this .callable ;
165- for (int i = this .delegatingCallables .size () - 1 ; i >= 0 ; i --) {
166- AbstractDelegatingCallable callable = this .delegatingCallables .get (i );
167- callable .setNextCallable (result );
168- result = callable ;
170+ Assert .state (this .lastCallable != null , "The last Callable was not set" );
171+ AbstractDelegatingCallable head = new StaleAsyncRequestCheckingCallable (this .asyncWebRequest );
172+ head .setNext (this .lastCallable );
173+ for (AbstractDelegatingCallable callable : this .callables ) {
174+ callable .setNext (head );
175+ head = callable ;
169176 }
170- return result ;
177+ return head ;
171178 }
172179
173180 /**
174- * Mark the start of async request processing accepting the provided
175- * DeferredResult and initializing it such that if
176- * {@link DeferredResult#set(Object)} is called (from another thread),
177- * the set Object value will be processed with the execution chain by
178- * invoking {@link AsyncExecutionChainRunnable}.
179- * <p>The resulting processing from this method is identical to
180- * {@link #startCallableChainProcessing()}. The main difference is in
181- * the threading model, i.e. whether a TaskExecutor is used.
182- * @see DeferredResult
181+ * Start async processing and initialize the given DeferredResult so when
182+ * its value is set, the async chain is executed with an AsyncTaskExecutor.
183183 */
184184 public void startDeferredResultProcessing (final DeferredResult <?> deferredResult ) {
185185 Assert .notNull (deferredResult , "DeferredResult is required" );
186- startAsync ();
186+ Assert .state (this .asyncWebRequest != null , "AsyncWebRequest was not set" );
187+ this .asyncWebRequest .startAsync ();
188+
187189 deferredResult .init (new DeferredResultHandler () {
188190 public void handle (Object result ) {
189191 if (asyncWebRequest .isAsyncCompleted ()) {
190- throw new StaleAsyncWebRequestException ("Async request processing already completed" );
192+ throw new StaleAsyncWebRequestException ("Too late to set DeferredResult: " + result );
191193 }
192- setCallable (new PassThroughCallable (result ));
193- new AsyncExecutionChainRunnable (asyncWebRequest , buildChain ()). run ( );
194+ setLastCallable (new PassThroughCallable (result ));
195+ taskExecutor . execute ( new AsyncExecutionChainRunnable (asyncWebRequest , buildChain ()));
194196 }
195197 });
198+
196199 this .asyncWebRequest .setTimeoutHandler (deferredResult .getTimeoutHandler ());
197200 }
198201
0 commit comments