Skip to content

Commit 3642b0f

Browse files
committed
Initial cut of Servlet 3.0 based async support
From a programming model perspective, @RequestMapping methods now support two new return value types: * java.util.concurrent.Callable - used by Spring MVC to obtain the return value asynchronously in a separate thread managed transparently by Spring MVC on behalf of the application. * org.springframework.web.context.request.async.DeferredResult - used by the application to produce the return value asynchronously in a separate thread of its own choosing. The high-level idea is that whatever value a controller normally returns, it can now provide it asynchronously, through a Callable or through a DeferredResult, with all remaining processing -- @responsebody, view resolution, etc, working just the same but completed outside the main request thread. From an SPI perspective, there are several new types: * AsyncExecutionChain - the central class for managing async request processing through a sequence of Callable instances each representing work required to complete request processing asynchronously. * AsyncWebRequest - provides methods for starting, completing, and configuring async request processing. * StandardServletAsyncWebRequest - Servlet 3 based implementation. * AsyncExecutionChainRunnable - the Runnable used for async request execution. All spring-web and spring-webmvc Filter implementations have been updated to participate in async request execution. The open-session-in-view Filter and interceptors implementations in spring-orm will be updated in a separate pull request. Issue: SPR-8517
1 parent fdded07 commit 3642b0f

27 files changed

+2249
-355
lines changed

spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.lang.annotation.Retention;
2222
import java.lang.annotation.RetentionPolicy;
2323
import java.lang.annotation.Target;
24+
import java.util.concurrent.Callable;
2425

2526
/**
2627
* Annotation for mapping web requests onto specific handler classes and/or
@@ -180,12 +181,18 @@
180181
* be converted to the response stream using
181182
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
182183
* converters}.
183-
* <li>A {@link org.springframework.http.HttpEntity HttpEntity&lt;?&gt;} or
184+
* <li>An {@link org.springframework.http.HttpEntity HttpEntity&lt;?&gt;} or
184185
* {@link org.springframework.http.ResponseEntity ResponseEntity&lt;?&gt;} object
185186
* (Servlet-only) to access to the Servlet response HTTP headers and contents.
186187
* The entity body will be converted to the response stream using
187188
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
188189
* converters}.
190+
* <li>A {@link Callable} which is used by Spring MVC to obtain the return
191+
* value asynchronously in a separate thread transparently managed by Spring MVC
192+
* on behalf of the application.
193+
* <li>A {@code org.springframework.web.context.request.async.DeferredResult}
194+
* which the application uses to produce a return value in a separate
195+
* thread of its own choosing, as an alternative to returning a Callable.
189196
* <li><code>void</code> if the method handles the response itself (by
190197
* writing the response content directly, declaring an argument of type
191198
* {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.context.request.async;
18+
19+
import java.util.concurrent.Callable;
20+
21+
/**
22+
* A base class for a Callable that can be used in a chain of Callable instances.
23+
*
24+
* <p>Typical use for async request processing scenarios involves:
25+
* <ul>
26+
* <li>Create an instance of this type and register it via
27+
* {@link AsyncExecutionChain#addDelegatingCallable(AbstractDelegatingCallable)}
28+
* (internally the nodes of the chain will be linked so no need to set up "next").
29+
* <li>Provide an implementation of {@link Callable#call()} that contains the
30+
* logic needed to complete request processing outside the main processing thread.
31+
* <li>In the implementation, delegate to the next Callable to obtain
32+
* its result, e.g. ModelAndView, and then do some post-processing, e.g. view
33+
* resolution. In some cases both pre- and post-processing might be
34+
* appropriate, e.g. setting up {@link ThreadLocal} storage.
35+
* </ul>
36+
*
37+
* @author Rossen Stoyanchev
38+
* @since 3.2
39+
*
40+
* @see AsyncExecutionChain
41+
*/
42+
public abstract class AbstractDelegatingCallable implements Callable<Object> {
43+
44+
private Callable<Object> next;
45+
46+
public void setNextCallable(Callable<Object> nextCallable) {
47+
this.next = nextCallable;
48+
}
49+
50+
protected Callable<Object> getNextCallable() {
51+
return this.next;
52+
}
53+
54+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.context.request.async;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.concurrent.Callable;
22+
23+
import javax.servlet.ServletRequest;
24+
25+
import org.springframework.core.task.AsyncTaskExecutor;
26+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
27+
import org.springframework.util.Assert;
28+
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
29+
30+
/**
31+
* The central class for managing async request processing, mainly intended as
32+
* an SPI and typically not by non-framework classes.
33+
*
34+
* <p>An async execution chain consists of a sequence of Callable instances and
35+
* represents the work required to complete request processing in a separate
36+
* thread. To construct the chain, each layer in the call stack of a normal
37+
* request (e.g. filter, servlet) may contribute an
38+
* {@link AbstractDelegatingCallable} when a request is being processed.
39+
* For example the DispatcherServlet might contribute a Callable that
40+
* performs view resolution while a HandlerAdapter might contribute a Callable
41+
* that returns the ModelAndView, etc. The last Callable is the one that
42+
* actually produces an application-specific value, for example the Callable
43+
* returned by an {@code @RequestMapping} method.
44+
*
45+
* @author Rossen Stoyanchev
46+
* @since 3.2
47+
*/
48+
public final class AsyncExecutionChain {
49+
50+
public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain.class.getName() + ".CALLABLE_CHAIN";
51+
52+
private final List<AbstractDelegatingCallable> delegatingCallables = new ArrayList<AbstractDelegatingCallable>();
53+
54+
private Callable<Object> callable;
55+
56+
private AsyncWebRequest asyncWebRequest;
57+
58+
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("AsyncExecutionChain");
59+
60+
/**
61+
* Private constructor
62+
* @see #getForCurrentRequest()
63+
*/
64+
private AsyncExecutionChain() {
65+
}
66+
67+
/**
68+
* Obtain the AsyncExecutionChain for the current request.
69+
* Or if not found, create an instance and associate it with the request.
70+
*/
71+
public static AsyncExecutionChain getForCurrentRequest(ServletRequest request) {
72+
AsyncExecutionChain chain = (AsyncExecutionChain) request.getAttribute(CALLABLE_CHAIN_ATTRIBUTE);
73+
if (chain == null) {
74+
chain = new AsyncExecutionChain();
75+
request.setAttribute(CALLABLE_CHAIN_ATTRIBUTE, chain);
76+
}
77+
return chain;
78+
}
79+
80+
/**
81+
* Provide an instance of an AsyncWebRequest.
82+
* This property must be set before async request processing can begin.
83+
*/
84+
public void setAsyncWebRequest(AsyncWebRequest asyncRequest) {
85+
this.asyncWebRequest = asyncRequest;
86+
}
87+
88+
/**
89+
* Provide an AsyncTaskExecutor to use when
90+
* {@link #startCallableChainProcessing()} is invoked, for example when a
91+
* controller method returns a Callable.
92+
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
93+
*/
94+
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
95+
this.taskExecutor = taskExecutor;
96+
}
97+
98+
/**
99+
* Whether async request processing has started through one of:
100+
* <ul>
101+
* <li>{@link #startCallableChainProcessing()}
102+
* <li>{@link #startDeferredResultProcessing(DeferredResult)}
103+
* </ul>
104+
*/
105+
public boolean isAsyncStarted() {
106+
return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
107+
}
108+
109+
/**
110+
* Add a Callable with logic required to complete request processing in a
111+
* separate thread. See {@link AbstractDelegatingCallable} for details.
112+
*/
113+
public void addDelegatingCallable(AbstractDelegatingCallable callable) {
114+
Assert.notNull(callable, "Callable required");
115+
this.delegatingCallables.add(callable);
116+
}
117+
118+
/**
119+
* Add the last Callable, for example the one returned by the controller.
120+
* This property must be set prior to invoking
121+
* {@link #startCallableChainProcessing()}.
122+
*/
123+
public AsyncExecutionChain setCallable(Callable<Object> callable) {
124+
Assert.notNull(callable, "Callable required");
125+
this.callable = callable;
126+
return this;
127+
}
128+
129+
/**
130+
* Start the async execution chain by submitting an
131+
* {@link AsyncExecutionChainRunnable} instance to the TaskExecutor provided via
132+
* {@link #setTaskExecutor(AsyncTaskExecutor)} and returning immediately.
133+
* @see AsyncExecutionChainRunnable
134+
*/
135+
public void startCallableChainProcessing() {
136+
startAsync();
137+
this.taskExecutor.execute(new AsyncExecutionChainRunnable(this.asyncWebRequest, buildChain()));
138+
}
139+
140+
private void startAsync() {
141+
Assert.state(this.asyncWebRequest != null, "An AsyncWebRequest is required to start async processing");
142+
this.asyncWebRequest.startAsync();
143+
}
144+
145+
private Callable<Object> buildChain() {
146+
Assert.state(this.callable != null, "The callable field is required to complete the chain");
147+
this.delegatingCallables.add(new StaleAsyncRequestCheckingCallable(asyncWebRequest));
148+
Callable<Object> result = this.callable;
149+
for (int i = this.delegatingCallables.size() - 1; i >= 0; i--) {
150+
AbstractDelegatingCallable callable = this.delegatingCallables.get(i);
151+
callable.setNextCallable(result);
152+
result = callable;
153+
}
154+
return result;
155+
}
156+
157+
/**
158+
* Mark the start of async request processing accepting the provided
159+
* DeferredResult and initializing it such that if
160+
* {@link DeferredResult#set(Object)} is called (from another thread),
161+
* the set Object value will be processed with the execution chain by
162+
* invoking {@link AsyncExecutionChainRunnable}.
163+
* <p>The resulting processing from this method is identical to
164+
* {@link #startCallableChainProcessing()}. The main difference is in
165+
* the threading model, i.e. whether a TaskExecutor is used.
166+
* @see DeferredResult
167+
*/
168+
public void startDeferredResultProcessing(DeferredResult deferredResult) {
169+
Assert.notNull(deferredResult, "A DeferredResult is required");
170+
startAsync();
171+
deferredResult.setValueProcessor(new DeferredResultHandler() {
172+
public void handle(Object value) {
173+
if (asyncWebRequest.isAsyncCompleted()) {
174+
throw new StaleAsyncWebRequestException("Async request processing already completed");
175+
}
176+
setCallable(getSimpleCallable(value));
177+
new AsyncExecutionChainRunnable(asyncWebRequest, buildChain()).run();
178+
}
179+
});
180+
}
181+
182+
private Callable<Object> getSimpleCallable(final Object value) {
183+
return new Callable<Object>() {
184+
public Object call() throws Exception {
185+
return value;
186+
}
187+
};
188+
}
189+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.context.request.async;
18+
19+
import java.util.concurrent.Callable;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
import org.springframework.http.HttpStatus;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* A Runnable for invoking a chain of Callable instances and completing async
28+
* request processing while also dealing with any unhandled exceptions.
29+
*
30+
* @author Rossen Stoyanchev
31+
* @since 3.2
32+
*
33+
* @see AsyncExecutionChain#startCallableChainProcessing()
34+
* @see AsyncExecutionChain#startDeferredResultProcessing(DeferredResult)
35+
*/
36+
public class AsyncExecutionChainRunnable implements Runnable {
37+
38+
private static final Log logger = LogFactory.getLog(AsyncWebRequest.class);
39+
40+
private final AsyncWebRequest asyncWebRequest;
41+
42+
private final Callable<?> callable;
43+
44+
/**
45+
* Class constructor.
46+
* @param asyncWebRequest the async request
47+
* @param callable the async execution chain
48+
*/
49+
public AsyncExecutionChainRunnable(AsyncWebRequest asyncWebRequest, Callable<?> callable) {
50+
Assert.notNull(asyncWebRequest, "An AsyncWebRequest is required");
51+
Assert.notNull(callable, "A Callable is required");
52+
Assert.state(asyncWebRequest.isAsyncStarted(), "Not an async request");
53+
this.asyncWebRequest = asyncWebRequest;
54+
this.callable = callable;
55+
}
56+
57+
/**
58+
* Run the async execution chain and complete the async request.
59+
* <p>A {@link StaleAsyncWebRequestException} is logged at debug level and
60+
* absorbed while any other unhandled {@link Exception} results in a 500
61+
* response code.
62+
*/
63+
public void run() {
64+
try {
65+
logger.debug("Starting async execution chain");
66+
callable.call();
67+
}
68+
catch (StaleAsyncWebRequestException ex) {
69+
logger.trace("Could not complete async request", ex);
70+
}
71+
catch (Exception ex) {
72+
logger.trace("Could not complete async request", ex);
73+
this.asyncWebRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
74+
}
75+
finally {
76+
logger.debug("Exiting async execution chain");
77+
asyncWebRequest.complete();
78+
}
79+
}
80+
81+
}

0 commit comments

Comments
 (0)