16
16
17
17
package org .springframework .web .context .request .async ;
18
18
19
- import java .util .ArrayList ;
20
- import java .util .List ;
19
+ import java .util .ArrayDeque ;
20
+ import java .util .Deque ;
21
21
import java .util .concurrent .Callable ;
22
22
23
23
import javax .servlet .ServletRequest ;
31
31
32
32
/**
33
33
* 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.
35
35
*
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.
46
48
*
47
49
* @author Rossen Stoyanchev
48
50
* @since 3.2
@@ -51,13 +53,13 @@ public final class AsyncExecutionChain {
51
53
52
54
public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain .class .getName () + ".CALLABLE_CHAIN" ;
53
55
54
- private final List <AbstractDelegatingCallable > delegatingCallables = new ArrayList <AbstractDelegatingCallable >();
56
+ private final Deque <AbstractDelegatingCallable > callables = new ArrayDeque <AbstractDelegatingCallable >();
55
57
56
- private Callable <Object > callable ;
58
+ private Callable <Object > lastCallable ;
57
59
58
60
private AsyncWebRequest asyncWebRequest ;
59
61
60
- private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor ("AsyncExecutionChain " );
62
+ private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor ("MvcAsync " );
61
63
62
64
/**
63
65
* Private constructor
@@ -68,7 +70,7 @@ private AsyncExecutionChain() {
68
70
69
71
/**
70
72
* 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.
72
74
*/
73
75
public static AsyncExecutionChain getForCurrentRequest (ServletRequest request ) {
74
76
AsyncExecutionChain chain = (AsyncExecutionChain ) request .getAttribute (CALLABLE_CHAIN_ATTRIBUTE );
@@ -81,7 +83,7 @@ public static AsyncExecutionChain getForCurrentRequest(ServletRequest request) {
81
83
82
84
/**
83
85
* 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.
85
87
*/
86
88
public static AsyncExecutionChain getForCurrentRequest (WebRequest request ) {
87
89
int scope = RequestAttributes .SCOPE_REQUEST ;
@@ -94,105 +96,106 @@ public static AsyncExecutionChain getForCurrentRequest(WebRequest request) {
94
96
}
95
97
96
98
/**
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.
99
100
*/
100
101
public void setAsyncWebRequest (AsyncWebRequest asyncRequest ) {
102
+ Assert .state (!isAsyncStarted (), "Cannot set AsyncWebRequest after the start of async processing." );
101
103
this .asyncWebRequest = asyncRequest ;
102
104
}
103
105
104
106
/**
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
109
111
*/
110
112
public void setTaskExecutor (AsyncTaskExecutor taskExecutor ) {
111
113
this .taskExecutor = taskExecutor ;
112
114
}
113
115
114
116
/**
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.
120
120
*/
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 );
123
124
}
124
125
125
126
/**
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).
128
132
*/
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
+ }
132
141
}
133
142
134
143
/**
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.
138
145
*/
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 ());
143
148
}
144
149
145
150
/**
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.
150
152
*/
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 ;
154
157
}
155
158
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" );
158
165
this .asyncWebRequest .startAsync ();
166
+ this .taskExecutor .execute (new AsyncExecutionChainRunnable (this .asyncWebRequest , buildChain ()));
159
167
}
160
168
161
169
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 ;
169
176
}
170
- return result ;
177
+ return head ;
171
178
}
172
179
173
180
/**
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.
183
183
*/
184
184
public void startDeferredResultProcessing (final DeferredResult <?> deferredResult ) {
185
185
Assert .notNull (deferredResult , "DeferredResult is required" );
186
- startAsync ();
186
+ Assert .state (this .asyncWebRequest != null , "AsyncWebRequest was not set" );
187
+ this .asyncWebRequest .startAsync ();
188
+
187
189
deferredResult .init (new DeferredResultHandler () {
188
190
public void handle (Object result ) {
189
191
if (asyncWebRequest .isAsyncCompleted ()) {
190
- throw new StaleAsyncWebRequestException ("Async request processing already completed" );
192
+ throw new StaleAsyncWebRequestException ("Too late to set DeferredResult: " + result );
191
193
}
192
- setCallable (new PassThroughCallable (result ));
193
- new AsyncExecutionChainRunnable (asyncWebRequest , buildChain ()). run ( );
194
+ setLastCallable (new PassThroughCallable (result ));
195
+ taskExecutor . execute ( new AsyncExecutionChainRunnable (asyncWebRequest , buildChain ()));
194
196
}
195
197
});
198
+
196
199
this .asyncWebRequest .setTimeoutHandler (deferredResult .getTimeoutHandler ());
197
200
}
198
201
0 commit comments