From 9c8c967caa4062670ac0660db97feee43bf54500 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 17 Aug 2012 17:05:16 -0400 Subject: [PATCH] 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: Issue: SPR-9694 --- .../AnnotationDrivenBeanDefinitionParser.java | 23 ++ .../annotation/AsyncSupportConfigurer.java | 75 +++++ .../DelegatingWebMvcConfiguration.java | 5 + .../WebMvcConfigurationSupport.java | 18 ++ .../config/annotation/WebMvcConfigurer.java | 5 + .../annotation/WebMvcConfigurerAdapter.java | 7 + .../annotation/WebMvcConfigurerComposite.java | 6 + .../RequestMappingHandlerAdapter.java | 32 +- .../web/servlet/config/spring-mvc-3.2.xsd | 32 ++ .../web/servlet/config/MvcNamespaceTests.java | 13 +- .../DelegatingWebMvcConfigurationTests.java | 78 ++--- ...MvcConfigurationSupportExtensionTests.java | 294 ++++++++++++++++++ .../WebMvcConfigurationSupportTests.java | 208 +------------ .../config/mvc-config-async-support.xml | 13 + 14 files changed, 552 insertions(+), 257 deletions(-) create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java create mode 100644 spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-async-support.xml diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index 89ba05baf6c..aed8f2a201c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -174,6 +174,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { ManagedList messageConverters = getMessageConverters(element, source, parserContext); ManagedList argumentResolvers = getArgumentResolvers(element, source, parserContext); ManagedList returnValueHandlers = getReturnValueHandlers(element, source, parserContext); + String asyncTimeout = getAsyncTimeout(element, source, parserContext); + RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext); RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); handlerAdapterDef.setSource(source); @@ -191,6 +193,12 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { if (returnValueHandlers != null) { handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers); } + if (asyncTimeout != null) { + handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout); + } + if (asyncExecutor != null) { + handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor); + } String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef); RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class); @@ -318,6 +326,21 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { } } + private String getAsyncTimeout(Element element, Object source, ParserContext parserContext) { + Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support"); + return (asyncElement != null) ? asyncElement.getAttribute("default-timeout") : null; + } + + private RuntimeBeanReference getAsyncExecutor(Element element, Object source, ParserContext parserContext) { + Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support"); + if (asyncElement != null) { + if (asyncElement.hasAttribute("task-executor")) { + return new RuntimeBeanReference(asyncElement.getAttribute("task-executor")); + } + } + return null; + } + private ManagedList getArgumentResolvers(Element element, Object source, ParserContext parserContext) { Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers"); if (resolversElement != null) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java new file mode 100644 index 00000000000..5f42833fdcd --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.servlet.config.annotation; + +import java.util.concurrent.Callable; + +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.web.context.request.async.AsyncTask; + +/** + * Helps with configuring a options for asynchronous request processing. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class AsyncSupportConfigurer { + + private AsyncTaskExecutor taskExecutor; + + private Long timeout; + + /** + * Set the default {@link AsyncTaskExecutor} to use when a controller method + * returns a {@link Callable}. Controller methods can override this default on + * a per-request basis by returning an {@link AsyncTask}. + * + *

By default a {@link SimpleAsyncTaskExecutor} instance is used and it's + * highly recommended to change that default in production since the simple + * executor does not re-use threads. + * + * @param taskExecutor the task executor instance to use by default + */ + public AsyncSupportConfigurer setTaskExecutor(AsyncTaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + return this; + } + + /** + * Specify the amount of time, in milliseconds, before asynchronous request + * handling times out. 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. + *

If this value is not set, the default timeout of the underlying + * implementation is used, e.g. 10 seconds on Tomcat with Servlet 3. + * + * @param timeout the timeout value in milliseconds + */ + public AsyncSupportConfigurer setDefaultTimeout(long timeout) { + this.timeout = timeout; + return this; + } + + protected AsyncTaskExecutor getTaskExecutor() { + return this.taskExecutor; + } + + protected Long getTimeout() { + return this.timeout; + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java index d0537a812c4..d9d58035b17 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java @@ -60,6 +60,11 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { this.configurers.configureContentNegotiation(configurer); } + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + this.configurers.configureAsyncSupport(configurer); + } + @Override protected void addViewControllers(ViewControllerRegistry registry) { this.configurers.addViewControllers(registry); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index a759cbe7333..b9f35396130 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -354,6 +354,17 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv adapter.setWebBindingInitializer(webBindingInitializer); adapter.setCustomArgumentResolvers(argumentResolvers); adapter.setCustomReturnValueHandlers(returnValueHandlers); + + AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); + configureAsyncSupport(configurer); + + if (configurer.getTaskExecutor() != null) { + adapter.setTaskExecutor(configurer.getTaskExecutor()); + } + if (configurer.getTimeout() != null) { + adapter.setAsyncRequestTimeout(configurer.getTimeout()); + } + return adapter; } @@ -516,6 +527,13 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv protected void addFormatters(FormatterRegistry registry) { } + /** + * Override this method to configure asynchronous request processing options. + * @see AsyncSupportConfigurer + */ + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + } + /** * Returns a {@link HttpRequestHandlerAdapter} for processing requests * with {@link HttpRequestHandler}s. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java index 97e8a047abc..215d34b7da7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java @@ -74,6 +74,11 @@ public interface WebMvcConfigurer { */ void configureContentNegotiation(ContentNegotiationConfigurer configurer); + /** + * Configure asynchronous request handling options. + */ + void configureAsyncSupport(AsyncSupportConfigurer configurer); + /** * Add resolvers to support custom controller method argument types. *

This does not override the built-in support for resolving handler diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java index 8cafe8ce867..ac63b1cbca9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.java @@ -64,6 +64,13 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer { public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } + /** + * {@inheritDoc} + *

This implementation is empty. + */ + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + } + /** * {@inheritDoc} *

This implementation is empty. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java index 75ba81eede3..e222c3b1260 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java @@ -55,6 +55,12 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer { } } + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + for (WebMvcConfigurer delegate : this.delegates) { + delegate.configureAsyncSupport(configurer); + } + } + public void configureMessageConverters(List> converters) { for (WebMvcConfigurer delegate : this.delegates) { delegate.configureMessageConverters(converters); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index ebfdd5b054a..8567a565ba9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; @@ -62,6 +63,7 @@ import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.async.AsyncTask; import org.springframework.web.context.request.async.AsyncWebRequest; import org.springframework.web.context.request.async.AsyncWebUtils; import org.springframework.web.context.request.async.WebAsyncManager; @@ -400,26 +402,28 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } /** - * Set the AsyncTaskExecutor to use when a controller method returns a - * {@code Callable}. - *

The default instance type is a {@link SimpleAsyncTaskExecutor}. - * It's recommended to change that default in production as the simple - * executor does not re-use threads. + * Set the default {@link AsyncTaskExecutor} to use when a controller method + * return a {@link Callable}. Controller methods can override this default on + * a per-request basis by returning an {@link AsyncTask}. + *

By default a {@link SimpleAsyncTaskExecutor} instance is used. + * It's recommended to change that default in production as the simple executor + * does not re-use threads. */ - public void setAsyncTaskExecutor(AsyncTaskExecutor taskExecutor) { + public void setTaskExecutor(AsyncTaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } /** - * Set the timeout for asynchronous request processing in milliseconds. - * When the timeout begins depends on the underlying async technology. - * With the Servlet 3 async support the timeout begins after the main - * processing thread has exited and has been returned to the container pool. - *

If a value is not provided, the default timeout of the underlying - * async technology is used (10 seconds on Tomcat with Servlet 3 async). + * Specify the amount of time, in milliseconds, before concurrent handling + * should time out. 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. + *

If this value is not set, the default timeout of the underlying + * implementation is used, e.g. 10 seconds on Tomcat with Servlet 3. + * @param timeout the timeout value in milliseconds */ - public void setAsyncRequestTimeout(long asyncRequestTimeout) { - this.asyncRequestTimeout = asyncRequestTimeout; + public void setAsyncRequestTimeout(long timeout) { + this.asyncRequestTimeout = timeout; } /** diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.2.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.2.xsd index d04878dc2af..f58bd9289be 100644 --- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.2.xsd +++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.2.xsd @@ -90,6 +90,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index bedc9ac477d..d7ad9c9e2fd 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -45,6 +45,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockRequestDispatcher; import org.springframework.mock.web.MockServletContext; +import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; @@ -451,7 +452,7 @@ public class MvcNamespaceTests { } @Test - public void testCustomContentNegotiationManager() throws Exception { + public void testContentNegotiationManager() throws Exception { loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 12); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); @@ -462,6 +463,16 @@ public class MvcNamespaceTests { assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest)); } + @Test + public void testAsyncSupportOptions() throws Exception { + loadBeanDefinitions("mvc-config-async-support.xml", 13); + + RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class); + assertNotNull(adapter); + assertEquals(ConcurrentTaskExecutor.class, new DirectFieldAccessor(adapter).getPropertyValue("taskExecutor").getClass()); + assertEquals(2500L, new DirectFieldAccessor(adapter).getPropertyValue("asyncRequestTimeout")); + } + private void loadBeanDefinitions(String fileName, int expectedBeanCount) { XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java index ddb26e672ad..7153c901f93 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java @@ -21,6 +21,7 @@ import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -54,14 +55,14 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv */ public class DelegatingWebMvcConfigurationTests { - private DelegatingWebMvcConfiguration mvcConfiguration; + private DelegatingWebMvcConfiguration delegatingConfig; - private WebMvcConfigurer configurer; + private WebMvcConfigurer webMvcConfigurer; @Before public void setUp() { - configurer = EasyMock.createMock(WebMvcConfigurer.class); - mvcConfiguration = new DelegatingWebMvcConfiguration(); + webMvcConfigurer = EasyMock.createMock(WebMvcConfigurer.class); + delegatingConfig = new DelegatingWebMvcConfiguration(); } @Test @@ -71,18 +72,20 @@ public class DelegatingWebMvcConfigurationTests { Capture conversionService = new Capture(); Capture> resolvers = new Capture>(); Capture> handlers = new Capture>(); + Capture asyncConfigurer = new Capture(); - configurer.configureMessageConverters(capture(converters)); - configurer.configureContentNegotiation(capture(contentNegotiationConfigurer)); - expect(configurer.getValidator()).andReturn(null); - expect(configurer.getMessageCodesResolver()).andReturn(null); - configurer.addFormatters(capture(conversionService)); - configurer.addArgumentResolvers(capture(resolvers)); - configurer.addReturnValueHandlers(capture(handlers)); - replay(configurer); + webMvcConfigurer.configureMessageConverters(capture(converters)); + webMvcConfigurer.configureContentNegotiation(capture(contentNegotiationConfigurer)); + expect(webMvcConfigurer.getValidator()).andReturn(null); + expect(webMvcConfigurer.getMessageCodesResolver()).andReturn(null); + webMvcConfigurer.addFormatters(capture(conversionService)); + webMvcConfigurer.addArgumentResolvers(capture(resolvers)); + webMvcConfigurer.addReturnValueHandlers(capture(handlers)); + webMvcConfigurer.configureAsyncSupport(capture(asyncConfigurer)); + replay(webMvcConfigurer); - mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter(); + delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + RequestMappingHandlerAdapter adapter = delegatingConfig.requestMappingHandlerAdapter(); ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); assertSame(conversionService.getValue(), initializer.getConversionService()); @@ -91,8 +94,9 @@ public class DelegatingWebMvcConfigurationTests { assertEquals(0, resolvers.getValue().size()); assertEquals(0, handlers.getValue().size()); assertEquals(converters.getValue(), adapter.getMessageConverters()); + assertNotNull(asyncConfigurer); - verify(configurer); + verify(webMvcConfigurer); } @Test @@ -104,33 +108,33 @@ public class DelegatingWebMvcConfigurationTests { converters.add(new StringHttpMessageConverter()); } }); - mvcConfiguration = new DelegatingWebMvcConfiguration(); - mvcConfiguration.setConfigurers(configurers); + delegatingConfig = new DelegatingWebMvcConfiguration(); + delegatingConfig.setConfigurers(configurers); - RequestMappingHandlerAdapter adapter = mvcConfiguration.requestMappingHandlerAdapter(); + RequestMappingHandlerAdapter adapter = delegatingConfig.requestMappingHandlerAdapter(); assertEquals("Only one custom converter should be registered", 1, adapter.getMessageConverters().size()); } @Test public void getCustomValidator() { - expect(configurer.getValidator()).andReturn(new LocalValidatorFactoryBean()); - replay(configurer); + expect(webMvcConfigurer.getValidator()).andReturn(new LocalValidatorFactoryBean()); + replay(webMvcConfigurer); - mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - mvcConfiguration.mvcValidator(); + delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.mvcValidator(); - verify(configurer); + verify(webMvcConfigurer); } @Test public void getCustomMessageCodesResolver() { - expect(configurer.getMessageCodesResolver()).andReturn(new DefaultMessageCodesResolver()); - replay(configurer); + expect(webMvcConfigurer.getMessageCodesResolver()).andReturn(new DefaultMessageCodesResolver()); + replay(webMvcConfigurer); - mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - mvcConfiguration.getMessageCodesResolver(); + delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.getMessageCodesResolver(); - verify(configurer); + verify(webMvcConfigurer); } @Test @@ -139,13 +143,13 @@ public class DelegatingWebMvcConfigurationTests { Capture> exceptionResolvers = new Capture>(); Capture contentNegotiationConfigurer = new Capture(); - configurer.configureMessageConverters(capture(converters)); - configurer.configureContentNegotiation(capture(contentNegotiationConfigurer)); - configurer.configureHandlerExceptionResolvers(capture(exceptionResolvers)); - replay(configurer); + webMvcConfigurer.configureMessageConverters(capture(converters)); + webMvcConfigurer.configureContentNegotiation(capture(contentNegotiationConfigurer)); + webMvcConfigurer.configureHandlerExceptionResolvers(capture(exceptionResolvers)); + replay(webMvcConfigurer); - mvcConfiguration.setConfigurers(Arrays.asList(configurer)); - mvcConfiguration.handlerExceptionResolver(); + delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.handlerExceptionResolver(); assertEquals(3, exceptionResolvers.getValue().size()); assertTrue(exceptionResolvers.getValue().get(0) instanceof ExceptionHandlerExceptionResolver); @@ -153,7 +157,7 @@ public class DelegatingWebMvcConfigurationTests { assertTrue(exceptionResolvers.getValue().get(2) instanceof DefaultHandlerExceptionResolver); assertTrue(converters.getValue().size() > 0); - verify(configurer); + verify(webMvcConfigurer); } @Test @@ -165,10 +169,10 @@ public class DelegatingWebMvcConfigurationTests { exceptionResolvers.add(new DefaultHandlerExceptionResolver()); } }); - mvcConfiguration.setConfigurers(configurers); + delegatingConfig.setConfigurers(configurers); HandlerExceptionResolverComposite composite = - (HandlerExceptionResolverComposite) mvcConfiguration.handlerExceptionResolver(); + (HandlerExceptionResolverComposite) delegatingConfig.handlerExceptionResolver(); assertEquals("Only one custom converter is expected", 1, composite.getExceptionResolvers().size()); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java new file mode 100644 index 00000000000..4324cf75a1e --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -0,0 +1,294 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.config.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.TestBean; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.io.FileSystemResourceLoader; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockServletContext; +import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; +import org.springframework.stereotype.Controller; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.DefaultMessageCodesResolver; +import org.springframework.validation.Errors; +import org.springframework.validation.MessageCodesResolver; +import org.springframework.validation.Validator; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.handler.AbstractHandlerMapping; +import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; +import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; +import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * A test fixture with a sub-class of {@link WebMvcConfigurationSupport} that + * implements the various {@link WebMvcConfigurer} extension points. + * + * @author Rossen Stoyanchev + */ +public class WebMvcConfigurationSupportExtensionTests { + + private TestWebMvcConfigurationSupport webConfig; + + private StaticWebApplicationContext webAppContext; + + + @Before + public void setUp() { + this.webAppContext = new StaticWebApplicationContext(); + this.webAppContext.setServletContext(new MockServletContext(new FileSystemResourceLoader())); + this.webAppContext.registerSingleton("controller", TestController.class); + + this.webConfig = new TestWebMvcConfigurationSupport(); + this.webConfig.setApplicationContext(this.webAppContext); + this.webConfig.setServletContext(this.webAppContext.getServletContext()); + } + + @Test + public void handlerMappings() throws Exception { + RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping(); + rmHandlerMapping.setApplicationContext(webAppContext); + rmHandlerMapping.afterPropertiesSet(); + HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/")); + assertNotNull(chain.getInterceptors()); + assertEquals(2, chain.getInterceptors().length); + assertEquals(LocaleChangeInterceptor.class, chain.getInterceptors()[0].getClass()); + assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass()); + + AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) webConfig.viewControllerHandlerMapping(); + handlerMapping.setApplicationContext(webAppContext); + assertNotNull(handlerMapping); + assertEquals(1, handlerMapping.getOrder()); + HandlerExecutionChain handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path")); + assertNotNull(handler.getHandler()); + + handlerMapping = (AbstractHandlerMapping) webConfig.resourceHandlerMapping(); + handlerMapping.setApplicationContext(webAppContext); + assertNotNull(handlerMapping); + assertEquals(Integer.MAX_VALUE-1, handlerMapping.getOrder()); + handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif")); + assertNotNull(handler.getHandler()); + + handlerMapping = (AbstractHandlerMapping) webConfig.defaultServletHandlerMapping(); + handlerMapping.setApplicationContext(webAppContext); + assertNotNull(handlerMapping); + assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder()); + handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/anyPath")); + assertNotNull(handler.getHandler()); + } + + @Test + public void requestMappingHandlerAdapter() throws Exception { + RequestMappingHandlerAdapter adapter = webConfig.requestMappingHandlerAdapter(); + + // ConversionService + String actual = webConfig.mvcConversionService().convert(new TestBean(), String.class); + assertEquals("converted", actual); + + // Message converters + assertEquals(1, adapter.getMessageConverters().size()); + + // Custom argument resolvers and return value handlers + @SuppressWarnings("unchecked") + List argResolvers= (List) + new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers"); + assertEquals(1, argResolvers.size()); + + @SuppressWarnings("unchecked") + List handlers = (List) + new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers"); + assertEquals(1, handlers.size()); + + // Async support options + assertEquals(ConcurrentTaskExecutor.class, new DirectFieldAccessor(adapter).getPropertyValue("taskExecutor").getClass()); + assertEquals(2500L, new DirectFieldAccessor(adapter).getPropertyValue("asyncRequestTimeout")); + + assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect")); + } + + @Test + public void webBindingInitializer() throws Exception { + RequestMappingHandlerAdapter adapter = webConfig.requestMappingHandlerAdapter(); + + ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); + assertNotNull(initializer); + + BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(null, ""); + initializer.getValidator().validate(null, bindingResult); + assertEquals("invalid", bindingResult.getAllErrors().get(0).getCode()); + + String[] codes = initializer.getMessageCodesResolver().resolveMessageCodes("invalid", null); + assertEquals("custom.invalid", codes[0]); + } + + @Test + public void contentNegotiation() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json"); + NativeWebRequest webRequest = new ServletWebRequest(request); + + ContentNegotiationManager manager = webConfig.requestMappingHandlerMapping().getContentNegotiationManager(); + assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest)); + + request.setRequestURI("/foo.xml"); + assertEquals(Arrays.asList(MediaType.APPLICATION_XML), manager.resolveMediaTypes(webRequest)); + + request.setRequestURI("/foo.rss"); + assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest)); + + request.setRequestURI("/foo.atom"); + assertEquals(Arrays.asList(MediaType.APPLICATION_ATOM_XML), manager.resolveMediaTypes(webRequest)); + + request.setRequestURI("/foo"); + request.setParameter("f", "json"); + assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest)); + } + + @Test + public void exceptionResolvers() throws Exception { + HandlerExceptionResolverComposite composite = (HandlerExceptionResolverComposite) webConfig.handlerExceptionResolver(); + assertEquals(1, composite.getExceptionResolvers().size()); + } + + + @Controller + private static class TestController { + + @RequestMapping("/") + public void handle() { + } + } + + /** + * Since WebMvcConfigurationSupport does not implement WebMvcConfigurer, the purpose + * of this test class is also to ensure the two are in sync with each other. Effectively + * that ensures that application config classes that use the combo {@code @EnableWebMvc} + * plus WebMvcConfigurer can switch to extending WebMvcConfigurationSupport directly for + * more advanced configuration needs. + */ + private class TestWebMvcConfigurationSupport extends WebMvcConfigurationSupport implements WebMvcConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new Converter() { + public String convert(TestBean source) { + return "converted"; + } + }); + } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(new MappingJackson2HttpMessageConverter()); + } + + @Override + public Validator getValidator() { + return new Validator() { + public void validate(Object target, Errors errors) { + errors.reject("invalid"); + } + public boolean supports(Class clazz) { + return true; + } + }; + } + + @Override + public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { + configurer.setFavorParameter(true).setParameterName("f"); + } + + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + configurer.setDefaultTimeout(2500).setTaskExecutor(new ConcurrentTaskExecutor()); + } + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(new ModelAttributeMethodProcessor(true)); + } + + @Override + public void addReturnValueHandlers(List returnValueHandlers) { + returnValueHandlers.add(new ModelAttributeMethodProcessor(true)); + } + + @Override + public void configureHandlerExceptionResolvers(List exceptionResolvers) { + exceptionResolvers.add(new SimpleMappingExceptionResolver()); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LocaleChangeInterceptor()); + } + + @SuppressWarnings("serial") + @Override + public MessageCodesResolver getMessageCodesResolver() { + return new DefaultMessageCodesResolver() { + @Override + public String[] resolveMessageCodes(String errorCode, String objectName) { + return new String[] { "custom." + errorCode }; + } + }; + } + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/path"); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("src/test/java"); + } + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable("default"); + } + + } + +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index bc1b2adca42..3c5ad6c7739 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -21,64 +21,44 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Test; -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.TestBean; import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.io.FileSystemResourceLoader; -import org.springframework.format.FormatterRegistry; import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.stereotype.Controller; -import org.springframework.validation.BeanPropertyBindingResult; -import org.springframework.validation.DefaultMessageCodesResolver; -import org.springframework.validation.Errors; -import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; -import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.support.StaticWebApplicationContext; -import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.handler.AbstractHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; -import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; -import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** - * A test fixture for {@link WebMvcConfigurationSupport}. + * A test fixture with an {@link WebMvcConfigurationSupport} instance. * * @author Rossen Stoyanchev */ public class WebMvcConfigurationSupportTests { - private TestWebMvcConfiguration mvcConfiguration; + private WebMvcConfigurationSupport mvcConfiguration; @Before public void setUp() { - mvcConfiguration = new TestWebMvcConfiguration(); + mvcConfiguration = new WebMvcConfigurationSupport(); } @Test @@ -157,8 +137,6 @@ public class WebMvcConfigurationSupportTests { Validator validator = initializer.getValidator(); assertNotNull(validator); assertTrue(validator instanceof LocalValidatorFactoryBean); - - assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect")); } @Test @@ -173,94 +151,6 @@ public class WebMvcConfigurationSupportTests { assertEquals(expectedResolvers.size(), compositeResolver.getExceptionResolvers().size()); } - @Test - public void webMvcConfigurerExtensionHooks() throws Exception { - - StaticWebApplicationContext appCxt = new StaticWebApplicationContext(); - appCxt.setServletContext(new MockServletContext(new FileSystemResourceLoader())); - appCxt.registerSingleton("controller", TestController.class); - - WebConfig webConfig = new WebConfig(); - webConfig.setApplicationContext(appCxt); - webConfig.setServletContext(appCxt.getServletContext()); - - String actual = webConfig.mvcConversionService().convert(new TestBean(), String.class); - assertEquals("converted", actual); - - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json"); - NativeWebRequest webRequest = new ServletWebRequest(request); - ContentNegotiationManager manager = webConfig.requestMappingHandlerMapping().getContentNegotiationManager(); - assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest)); - - request.setRequestURI("/foo.xml"); - assertEquals(Arrays.asList(MediaType.APPLICATION_XML), manager.resolveMediaTypes(webRequest)); - - request.setRequestURI("/foo.rss"); - assertEquals(Arrays.asList(MediaType.valueOf("application/rss+xml")), manager.resolveMediaTypes(webRequest)); - - request.setRequestURI("/foo.atom"); - assertEquals(Arrays.asList(MediaType.APPLICATION_ATOM_XML), manager.resolveMediaTypes(webRequest)); - - request.setRequestURI("/foo"); - request.setParameter("f", "json"); - assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(webRequest)); - - RequestMappingHandlerAdapter adapter = webConfig.requestMappingHandlerAdapter(); - assertEquals(1, adapter.getMessageConverters().size()); - - ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); - assertNotNull(initializer); - - BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(null, ""); - initializer.getValidator().validate(null, bindingResult); - assertEquals("invalid", bindingResult.getAllErrors().get(0).getCode()); - - String[] codes = initializer.getMessageCodesResolver().resolveMessageCodes("invalid", null); - assertEquals("custom.invalid", codes[0]); - - @SuppressWarnings("unchecked") - List argResolvers= (List) - new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers"); - assertEquals(1, argResolvers.size()); - - @SuppressWarnings("unchecked") - List handlers = (List) - new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers"); - assertEquals(1, handlers.size()); - - HandlerExceptionResolverComposite composite = (HandlerExceptionResolverComposite) webConfig.handlerExceptionResolver(); - assertEquals(1, composite.getExceptionResolvers().size()); - - RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping(); - rmHandlerMapping.setApplicationContext(appCxt); - rmHandlerMapping.afterPropertiesSet(); - HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/")); - assertNotNull(chain.getInterceptors()); - assertEquals(2, chain.getInterceptors().length); - assertEquals(LocaleChangeInterceptor.class, chain.getInterceptors()[0].getClass()); - assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass()); - - AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) webConfig.viewControllerHandlerMapping(); - handlerMapping.setApplicationContext(appCxt); - assertNotNull(handlerMapping); - assertEquals(1, handlerMapping.getOrder()); - HandlerExecutionChain handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path")); - assertNotNull(handler.getHandler()); - - handlerMapping = (AbstractHandlerMapping) webConfig.resourceHandlerMapping(); - handlerMapping.setApplicationContext(appCxt); - assertNotNull(handlerMapping); - assertEquals(Integer.MAX_VALUE-1, handlerMapping.getOrder()); - handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif")); - assertNotNull(handler.getHandler()); - - handlerMapping = (AbstractHandlerMapping) webConfig.defaultServletHandlerMapping(); - handlerMapping.setApplicationContext(appCxt); - assertNotNull(handlerMapping); - assertEquals(Integer.MAX_VALUE, handlerMapping.getOrder()); - handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/anyPath")); - assertNotNull(handler.getHandler()); - } @Controller private static class TestController { @@ -270,96 +160,4 @@ public class WebMvcConfigurationSupportTests { } } - private static class TestWebMvcConfiguration extends WebMvcConfigurationSupport { - - } - - /** - * Since WebMvcConfigurationSupport does not implement WebMvcConfigurer, the purpose - * of this test class is also to ensure the two are in sync with each other. Effectively - * that ensures that application config classes that use the combo {@code @EnableWebMvc} - * plus WebMvcConfigurer can switch to extending WebMvcConfigurationSupport directly for - * more advanced configuration needs. - */ - private class WebConfig extends WebMvcConfigurationSupport implements WebMvcConfigurer { - - @Override - public void addFormatters(FormatterRegistry registry) { - registry.addConverter(new Converter() { - public String convert(TestBean source) { - return "converted"; - } - }); - } - - @Override - public void configureMessageConverters(List> converters) { - converters.add(new MappingJackson2HttpMessageConverter()); - } - - @Override - public Validator getValidator() { - return new Validator() { - public void validate(Object target, Errors errors) { - errors.reject("invalid"); - } - public boolean supports(Class clazz) { - return true; - } - }; - } - - @Override - public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { - configurer.setFavorParameter(true).setParameterName("f"); - } - - @Override - public void addArgumentResolvers(List argumentResolvers) { - argumentResolvers.add(new ModelAttributeMethodProcessor(true)); - } - - @Override - public void addReturnValueHandlers(List returnValueHandlers) { - returnValueHandlers.add(new ModelAttributeMethodProcessor(true)); - } - - @Override - public void configureHandlerExceptionResolvers(List exceptionResolvers) { - exceptionResolvers.add(new SimpleMappingExceptionResolver()); - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new LocaleChangeInterceptor()); - } - - @SuppressWarnings("serial") - @Override - public MessageCodesResolver getMessageCodesResolver() { - return new DefaultMessageCodesResolver() { - @Override - public String[] resolveMessageCodes(String errorCode, String objectName) { - return new String[] { "custom." + errorCode }; - } - }; - } - - @Override - public void addViewControllers(ViewControllerRegistry registry) { - registry.addViewController("/path"); - } - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/resources/**").addResourceLocations("src/test/java"); - } - - @Override - public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { - configurer.enable("default"); - } - - } - } diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-async-support.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-async-support.xml new file mode 100644 index 00000000000..ed902a56c15 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-async-support.xml @@ -0,0 +1,13 @@ + + + + + + + + +