Skip to content

Commit 9c8c967

Browse files
committed
Add async options to MVC namespace and Java config
The MVC Java config method to implement is WebMvcConfigurer.configureAsyncSupport(AsyncSupportConfigurer) The MVC namespace element is: <mvc:annotation-driven> <mvc:async-support default-timeout="2500" task-executor="myExecutor" /> </mvc:annotation-driven> Issue: SPR-9694
1 parent 4f55518 commit 9c8c967

File tree

14 files changed

+552
-257
lines changed

14 files changed

+552
-257
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
174174
ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
175175
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
176176
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
177+
String asyncTimeout = getAsyncTimeout(element, source, parserContext);
178+
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
177179

178180
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
179181
handlerAdapterDef.setSource(source);
@@ -191,6 +193,12 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
191193
if (returnValueHandlers != null) {
192194
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
193195
}
196+
if (asyncTimeout != null) {
197+
handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
198+
}
199+
if (asyncExecutor != null) {
200+
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
201+
}
194202
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
195203

196204
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
@@ -318,6 +326,21 @@ private RuntimeBeanReference getMessageCodesResolver(Element element, Object sou
318326
}
319327
}
320328

329+
private String getAsyncTimeout(Element element, Object source, ParserContext parserContext) {
330+
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
331+
return (asyncElement != null) ? asyncElement.getAttribute("default-timeout") : null;
332+
}
333+
334+
private RuntimeBeanReference getAsyncExecutor(Element element, Object source, ParserContext parserContext) {
335+
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
336+
if (asyncElement != null) {
337+
if (asyncElement.hasAttribute("task-executor")) {
338+
return new RuntimeBeanReference(asyncElement.getAttribute("task-executor"));
339+
}
340+
}
341+
return null;
342+
}
343+
321344
private ManagedList<?> getArgumentResolvers(Element element, Object source, ParserContext parserContext) {
322345
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
323346
if (resolversElement != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
package org.springframework.web.servlet.config.annotation;
17+
18+
import java.util.concurrent.Callable;
19+
20+
import org.springframework.core.task.AsyncTaskExecutor;
21+
import org.springframework.core.task.SimpleAsyncTaskExecutor;
22+
import org.springframework.web.context.request.async.AsyncTask;
23+
24+
/**
25+
* Helps with configuring a options for asynchronous request processing.
26+
*
27+
* @author Rossen Stoyanchev
28+
* @since 3.2
29+
*/
30+
public class AsyncSupportConfigurer {
31+
32+
private AsyncTaskExecutor taskExecutor;
33+
34+
private Long timeout;
35+
36+
/**
37+
* Set the default {@link AsyncTaskExecutor} to use when a controller method
38+
* returns a {@link Callable}. Controller methods can override this default on
39+
* a per-request basis by returning an {@link AsyncTask}.
40+
*
41+
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used and it's
42+
* highly recommended to change that default in production since the simple
43+
* executor does not re-use threads.
44+
*
45+
* @param taskExecutor the task executor instance to use by default
46+
*/
47+
public AsyncSupportConfigurer setTaskExecutor(AsyncTaskExecutor taskExecutor) {
48+
this.taskExecutor = taskExecutor;
49+
return this;
50+
}
51+
52+
/**
53+
* Specify the amount of time, in milliseconds, before asynchronous request
54+
* handling times out. In Servlet 3, the timeout begins after the main request
55+
* processing thread has exited and ends when the request is dispatched again
56+
* for further processing of the concurrently produced result.
57+
* <p>If this value is not set, the default timeout of the underlying
58+
* implementation is used, e.g. 10 seconds on Tomcat with Servlet 3.
59+
*
60+
* @param timeout the timeout value in milliseconds
61+
*/
62+
public AsyncSupportConfigurer setDefaultTimeout(long timeout) {
63+
this.timeout = timeout;
64+
return this;
65+
}
66+
67+
protected AsyncTaskExecutor getTaskExecutor() {
68+
return this.taskExecutor;
69+
}
70+
71+
protected Long getTimeout() {
72+
return this.timeout;
73+
}
74+
75+
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ protected void configureContentNegotiation(ContentNegotiationConfigurer configur
6060
this.configurers.configureContentNegotiation(configurer);
6161
}
6262

63+
@Override
64+
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
65+
this.configurers.configureAsyncSupport(configurer);
66+
}
67+
6368
@Override
6469
protected void addViewControllers(ViewControllerRegistry registry) {
6570
this.configurers.addViewControllers(registry);

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,17 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
354354
adapter.setWebBindingInitializer(webBindingInitializer);
355355
adapter.setCustomArgumentResolvers(argumentResolvers);
356356
adapter.setCustomReturnValueHandlers(returnValueHandlers);
357+
358+
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
359+
configureAsyncSupport(configurer);
360+
361+
if (configurer.getTaskExecutor() != null) {
362+
adapter.setTaskExecutor(configurer.getTaskExecutor());
363+
}
364+
if (configurer.getTimeout() != null) {
365+
adapter.setAsyncRequestTimeout(configurer.getTimeout());
366+
}
367+
357368
return adapter;
358369
}
359370

@@ -516,6 +527,13 @@ else if (jacksonPresent) {
516527
protected void addFormatters(FormatterRegistry registry) {
517528
}
518529

530+
/**
531+
* Override this method to configure asynchronous request processing options.
532+
* @see AsyncSupportConfigurer
533+
*/
534+
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
535+
}
536+
519537
/**
520538
* Returns a {@link HttpRequestHandlerAdapter} for processing requests
521539
* with {@link HttpRequestHandler}s.

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ public interface WebMvcConfigurer {
7474
*/
7575
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
7676

77+
/**
78+
* Configure asynchronous request handling options.
79+
*/
80+
void configureAsyncSupport(AsyncSupportConfigurer configurer);
81+
7782
/**
7883
* Add resolvers to support custom controller method argument types.
7984
* <p>This does not override the built-in support for resolving handler

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ public Validator getValidator() {
6464
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
6565
}
6666

67+
/**
68+
* {@inheritDoc}
69+
* <p>This implementation is empty.
70+
*/
71+
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
72+
}
73+
6774
/**
6875
* {@inheritDoc}
6976
* <p>This implementation is empty.

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
5555
}
5656
}
5757

58+
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
59+
for (WebMvcConfigurer delegate : this.delegates) {
60+
delegate.configureAsyncSupport(configurer);
61+
}
62+
}
63+
5864
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
5965
for (WebMvcConfigurer delegate : this.delegates) {
6066
delegate.configureMessageConverters(converters);

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Map;
2525
import java.util.Map.Entry;
2626
import java.util.Set;
27+
import java.util.concurrent.Callable;
2728
import java.util.concurrent.ConcurrentHashMap;
2829

2930
import javax.servlet.http.HttpServletRequest;
@@ -62,6 +63,7 @@
6263
import org.springframework.web.context.request.NativeWebRequest;
6364
import org.springframework.web.context.request.ServletWebRequest;
6465
import org.springframework.web.context.request.WebRequest;
66+
import org.springframework.web.context.request.async.AsyncTask;
6567
import org.springframework.web.context.request.async.AsyncWebRequest;
6668
import org.springframework.web.context.request.async.AsyncWebUtils;
6769
import org.springframework.web.context.request.async.WebAsyncManager;
@@ -400,26 +402,28 @@ public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect
400402
}
401403

402404
/**
403-
* Set the AsyncTaskExecutor to use when a controller method returns a
404-
* {@code Callable}.
405-
* <p>The default instance type is a {@link SimpleAsyncTaskExecutor}.
406-
* It's recommended to change that default in production as the simple
407-
* executor does not re-use threads.
405+
* Set the default {@link AsyncTaskExecutor} to use when a controller method
406+
* return a {@link Callable}. Controller methods can override this default on
407+
* a per-request basis by returning an {@link AsyncTask}.
408+
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
409+
* It's recommended to change that default in production as the simple executor
410+
* does not re-use threads.
408411
*/
409-
public void setAsyncTaskExecutor(AsyncTaskExecutor taskExecutor) {
412+
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
410413
this.taskExecutor = taskExecutor;
411414
}
412415

413416
/**
414-
* Set the timeout for asynchronous request processing in milliseconds.
415-
* When the timeout begins depends on the underlying async technology.
416-
* With the Servlet 3 async support the timeout begins after the main
417-
* processing thread has exited and has been returned to the container pool.
418-
* <p>If a value is not provided, the default timeout of the underlying
419-
* async technology is used (10 seconds on Tomcat with Servlet 3 async).
417+
* Specify the amount of time, in milliseconds, before concurrent handling
418+
* should time out. In Servlet 3, the timeout begins after the main request
419+
* processing thread has exited and ends when the request is dispatched again
420+
* for further processing of the concurrently produced result.
421+
* <p>If this value is not set, the default timeout of the underlying
422+
* implementation is used, e.g. 10 seconds on Tomcat with Servlet 3.
423+
* @param timeout the timeout value in milliseconds
420424
*/
421-
public void setAsyncRequestTimeout(long asyncRequestTimeout) {
422-
this.asyncRequestTimeout = asyncRequestTimeout;
425+
public void setAsyncRequestTimeout(long timeout) {
426+
this.asyncRequestTimeout = timeout;
423427
}
424428

425429
/**

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.2.xsd

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,38 @@
9090
</xsd:sequence>
9191
</xsd:complexType>
9292
</xsd:element>
93+
<xsd:element name="async-support" minOccurs="0">
94+
<xsd:annotation>
95+
<xsd:documentation><![CDATA[
96+
Configure options for asynchronous request processing.
97+
]]></xsd:documentation>
98+
</xsd:annotation>
99+
<xsd:complexType>
100+
<xsd:attribute name="task-executor" type="xsd:string">
101+
<xsd:annotation>
102+
<xsd:documentation source="java:org.springframework.core.task.AsyncTaskExecutor"><![CDATA[
103+
The bean name of a default AsyncTaskExecutor to use when a controller method returns a {@link Callable}.
104+
Controller methods can override this default on a per-request basis by returning an AsyncTask.
105+
By default a SimpleAsyncTaskExecutor is used which does not re-use threads and is not recommended for production.
106+
]]></xsd:documentation>
107+
<xsd:appinfo>
108+
<tool:annotation kind="ref">
109+
<tool:expected-type type="java:org.springframework.core.task.AsyncTaskExecutor" />
110+
</tool:annotation>
111+
</xsd:appinfo>
112+
</xsd:annotation>
113+
</xsd:attribute>
114+
<xsd:attribute name="default-timeout" type="xsd:long">
115+
<xsd:annotation>
116+
<xsd:documentation><![CDATA[
117+
Specify the amount of time, in milliseconds, before asynchronous request handling times out.
118+
In Servlet 3, the timeout begins after the main request processing thread has exited and ends when the request is dispatched again for further processing of the concurrently produced result.
119+
If this value is not set, the default timeout of the underlying implementation is used, e.g. 10 seconds on Tomcat with Servlet 3.
120+
]]></xsd:documentation>
121+
</xsd:annotation>
122+
</xsd:attribute>
123+
</xsd:complexType>
124+
</xsd:element>
93125
</xsd:all>
94126
<xsd:attribute name="conversion-service" type="xsd:string">
95127
<xsd:annotation>

spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.mock.web.MockHttpServletResponse;
4646
import org.springframework.mock.web.MockRequestDispatcher;
4747
import org.springframework.mock.web.MockServletContext;
48+
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
4849
import org.springframework.stereotype.Controller;
4950
import org.springframework.validation.BindingResult;
5051
import org.springframework.validation.Errors;
@@ -451,7 +452,7 @@ public void testViewControllersDefaultConfig() {
451452
}
452453

453454
@Test
454-
public void testCustomContentNegotiationManager() throws Exception {
455+
public void testContentNegotiationManager() throws Exception {
455456
loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 12);
456457

457458
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
@@ -462,6 +463,16 @@ public void testCustomContentNegotiationManager() throws Exception {
462463
assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest));
463464
}
464465

466+
@Test
467+
public void testAsyncSupportOptions() throws Exception {
468+
loadBeanDefinitions("mvc-config-async-support.xml", 13);
469+
470+
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
471+
assertNotNull(adapter);
472+
assertEquals(ConcurrentTaskExecutor.class, new DirectFieldAccessor(adapter).getPropertyValue("taskExecutor").getClass());
473+
assertEquals(2500L, new DirectFieldAccessor(adapter).getPropertyValue("asyncRequestTimeout"));
474+
}
475+
465476

466477
private void loadBeanDefinitions(String fileName, int expectedBeanCount) {
467478
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);

0 commit comments

Comments
 (0)