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 6742aad8af..67427c3ae7 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 @@ -164,10 +164,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv private List interceptors; - private ContentNegotiationManager contentNegotiationManager; - private PathMatchConfigurer pathMatchConfigurer; + private ContentNegotiationManager contentNegotiationManager; + private List> messageConverters; @@ -186,6 +186,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv this.servletContext = servletContext; } + /** * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping * requests to annotated controllers. @@ -207,11 +208,13 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv if (configurer.isUseTrailingSlashMatch() != null) { handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch()); } - if (configurer.getPathMatcher() != null) { - handlerMapping.setPathMatcher(configurer.getPathMatcher()); + UrlPathHelper pathHelper = configurer.getUrlPathHelper(); + if (pathHelper != null) { + handlerMapping.setUrlPathHelper(pathHelper); } - if (configurer.getUrlPathHelper() != null) { - handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper()); + PathMatcher pathMatcher = configurer.getPathMatcher(); + if (pathMatcher != null) { + handlerMapping.setPathMatcher(pathMatcher); } return handlerMapping; @@ -240,48 +243,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv protected void addInterceptors(InterceptorRegistry registry) { } - /** - * Return a {@link ContentNegotiationManager} instance to use to determine - * requested {@linkplain MediaType media types} in a given request. - */ - @Bean - public ContentNegotiationManager mvcContentNegotiationManager() { - if (this.contentNegotiationManager == null) { - ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext); - configurer.mediaTypes(getDefaultMediaTypes()); - configureContentNegotiation(configurer); - try { - this.contentNegotiationManager = configurer.getContentNegotiationManager(); - } - catch (Exception ex) { - throw new BeanInitializationException("Could not create ContentNegotiationManager", ex); - } - } - return this.contentNegotiationManager; - } - - protected Map getDefaultMediaTypes() { - Map map = new HashMap(); - if (romePresent) { - map.put("atom", MediaType.APPLICATION_ATOM_XML); - map.put("rss", MediaType.valueOf("application/rss+xml")); - } - if (jaxb2Present) { - map.put("xml", MediaType.APPLICATION_XML); - } - if (jackson2Present || jacksonPresent) { - map.put("json", MediaType.APPLICATION_JSON); - } - return map; - } - - /** - * Override this method to configure content negotiation. - * @see DefaultServletHandlerConfigurer - */ - protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) { - } - /** * Callback for building the {@link PathMatchConfigurer}. * Delegates to {@link #configurePathMatch}. @@ -312,12 +273,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv */ @Bean public PathMatcher mvcPathMatcher() { - if (getPathMatchConfigurer().getPathMatcher() != null) { - return getPathMatchConfigurer().getPathMatcher(); - } - else { - return new AntPathMatcher(); - } + PathMatcher pathMatcher = getPathMatchConfigurer().getPathMatcher(); + return (pathMatcher != null ? pathMatcher : new AntPathMatcher()); } /** @@ -329,12 +286,50 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv */ @Bean public UrlPathHelper mvcUrlPathHelper() { - if (getPathMatchConfigurer().getUrlPathHelper() != null) { - return getPathMatchConfigurer().getUrlPathHelper(); + UrlPathHelper pathHelper = getPathMatchConfigurer().getUrlPathHelper(); + return (pathHelper != null ? pathHelper : new UrlPathHelper()); + } + + /** + * Return a {@link ContentNegotiationManager} instance to use to determine + * requested {@linkplain MediaType media types} in a given request. + */ + @Bean + public ContentNegotiationManager mvcContentNegotiationManager() { + if (this.contentNegotiationManager == null) { + ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext); + configurer.mediaTypes(getDefaultMediaTypes()); + configureContentNegotiation(configurer); + try { + this.contentNegotiationManager = configurer.getContentNegotiationManager(); + } + catch (Exception ex) { + throw new BeanInitializationException("Could not create ContentNegotiationManager", ex); + } } - else { - return new UrlPathHelper(); + return this.contentNegotiationManager; + } + + protected Map getDefaultMediaTypes() { + Map map = new HashMap(4); + if (romePresent) { + map.put("atom", MediaType.APPLICATION_ATOM_XML); + map.put("rss", MediaType.valueOf("application/rss+xml")); } + if (jaxb2Present) { + map.put("xml", MediaType.APPLICATION_XML); + } + if (jackson2Present || jacksonPresent) { + map.put("json", MediaType.APPLICATION_JSON); + } + return map; + } + + /** + * Override this method to configure content negotiation. + * @see DefaultServletHandlerConfigurer + */ + protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) { } /** @@ -450,7 +445,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); configureAsyncSupport(configurer); - if (configurer.getTaskExecutor() != null) { adapter.setTaskExecutor(configurer.getTaskExecutor()); } @@ -475,6 +469,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv return initializer; } + /** + * Override this method to provide a custom {@link MessageCodesResolver}. + */ + protected MessageCodesResolver getMessageCodesResolver() { + return null; + } + + /** + * Override this method to configure asynchronous request processing options. + * @see AsyncSupportConfigurer + */ + protected void configureAsyncSupport(AsyncSupportConfigurer configurer) { + } + /** * Return a {@link FormattingConversionService} for use with annotated * controller methods and the {@code spring:eval} JSP tag. @@ -487,6 +495,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv return conversionService; } + /** + * Override this method to add custom {@link Converter}s and {@link Formatter}s. + */ + protected void addFormatters(FormatterRegistry registry) { + } + /** * Return a global {@link Validator} instance for example for validating * {@code @ModelAttribute} and {@code @RequestBody} method arguments. @@ -511,7 +525,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv catch (LinkageError ex) { throw new BeanInitializationException("Could not load default validator class", ex); } - validator = (Validator) BeanUtils.instantiate(clazz); + validator = (Validator) BeanUtils.instantiateClass(clazz); } else { validator = new NoOpValidator(); @@ -527,13 +541,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv return null; } - /** - * Override this method to provide a custom {@link MessageCodesResolver}. - */ - protected MessageCodesResolver getMessageCodesResolver() { - return null; - } - /** * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to * the ones registered by default. @@ -625,19 +632,6 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv } } - /** - * Override this method to add custom {@link Converter}s and {@link Formatter}s. - */ - 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. @@ -670,11 +664,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv public HandlerExceptionResolver handlerExceptionResolver() { List exceptionResolvers = new ArrayList(); configureHandlerExceptionResolvers(exceptionResolvers); - if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers); } - HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); 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 36d720f99e..62e8caefec 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 @@ -61,14 +61,6 @@ public interface WebMvcConfigurer { */ void configureMessageConverters(List> converters); - /** - * Provide a custom {@link Validator} instead of the one created by default. - * The default implementation, assuming JSR-303 is on the classpath, is: - * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}. - * Leave the return value as {@code null} to keep the default. - */ - Validator getValidator(); - /** * Configure content negotiation options. */ @@ -79,7 +71,6 @@ public interface WebMvcConfigurer { */ void configureAsyncSupport(AsyncSupportConfigurer configurer); - /** * Helps with configuring HandlerMappings path matching options such as trailing slash match, * suffix registration, path matcher and path helper. @@ -126,13 +117,6 @@ public interface WebMvcConfigurer { */ void addInterceptors(InterceptorRegistry registry); - /** - * Provide a custom {@link MessageCodesResolver} for building message codes - * from data binding and validation error codes. Leave the return value as - * {@code null} to keep the default. - */ - MessageCodesResolver getMessageCodesResolver(); - /** * Add view controllers to create a direct mapping between a URL path and * view name without the need for a controller in between. @@ -154,4 +138,19 @@ public interface WebMvcConfigurer { */ void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer); + /** + * Provide a custom {@link Validator} instead of the one created by default. + * The default implementation, assuming JSR-303 is on the classpath, is: + * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}. + * Leave the return value as {@code null} to keep the default. + */ + Validator getValidator(); + + /** + * Provide a custom {@link MessageCodesResolver} for building message codes + * from data binding and validation error codes. Leave the return value as + * {@code null} to keep the default. + */ + MessageCodesResolver getMessageCodesResolver(); + } 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 e7fd2a88b9..f4744f2ce1 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 @@ -28,7 +28,7 @@ import org.springframework.web.servlet.HandlerExceptionResolver; /** * An implementation of {@link WebMvcConfigurer} with empty methods allowing - * sub-classes to override only the methods they're interested in. + * subclasses to override only the methods they're interested in. * * @author Rossen Stoyanchev * @since 3.1 @@ -49,14 +49,6 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer { public void configureMessageConverters(List> converters) { } - /** - * {@inheritDoc} - *

This implementation returns {@code null} - */ - public Validator getValidator() { - return null; - } - /** * {@inheritDoc} *

This implementation is empty. @@ -99,14 +91,6 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer { public void configureHandlerExceptionResolvers(List exceptionResolvers) { } - /** - * {@inheritDoc} - *

This implementation is empty. - */ - public MessageCodesResolver getMessageCodesResolver() { - return null; - } - /** * {@inheritDoc} *

This implementation is empty. @@ -135,4 +119,20 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer { public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { } + /** + * {@inheritDoc} + *

This implementation returns {@code null}. + */ + public Validator getValidator() { + return null; + } + + /** + * {@inheritDoc} + *

This implementation returns {@code null}. + */ + public MessageCodesResolver getMessageCodesResolver() { + return null; + } + } 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 2ec2a4effd..b0312d30c8 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 @@ -21,6 +21,7 @@ import java.util.List; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.util.CollectionUtils; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -28,7 +29,7 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.HandlerExceptionResolver; /** - * An {@link WebMvcConfigurer} implementation that delegates to other {@link WebMvcConfigurer} instances. + * A {@link WebMvcConfigurer} that delegates to one or more others. * * @author Rossen Stoyanchev * @since 3.1 @@ -37,12 +38,14 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer { private final List delegates = new ArrayList(); + public void addWebMvcConfigurers(List configurers) { - if (configurers != null) { + if (!CollectionUtils.isEmpty(configurers)) { this.delegates.addAll(configurers); } } + public void addFormatters(FormatterRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addFormatters(registry); @@ -126,6 +129,17 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer { return selectSingleInstance(candidates, Validator.class); } + public MessageCodesResolver getMessageCodesResolver() { + List candidates = new ArrayList(); + for (WebMvcConfigurer configurer : this.delegates) { + MessageCodesResolver messageCodesResolver = configurer.getMessageCodesResolver(); + if (messageCodesResolver != null) { + candidates.add(messageCodesResolver); + } + } + return selectSingleInstance(candidates, MessageCodesResolver.class); + } + private T selectSingleInstance(List instances, Class instanceType) { if (instances.size() > 1) { throw new IllegalStateException( @@ -139,15 +153,4 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer { } } - public MessageCodesResolver getMessageCodesResolver() { - List candidates = new ArrayList(); - for (WebMvcConfigurer configurer : this.delegates) { - MessageCodesResolver messageCodesResolver = configurer.getMessageCodesResolver(); - if (messageCodesResolver != null) { - candidates.add(messageCodesResolver); - } - } - return selectSingleInstance(candidates, MessageCodesResolver.class); - } - } 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 890b310de9..5819c93cb6 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 @@ -17,7 +17,7 @@ package org.springframework.web.servlet.config.annotation; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Before; @@ -26,6 +26,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; + import org.springframework.core.convert.ConversionService; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.HttpMessageConverter; @@ -88,10 +89,10 @@ public class DelegatingWebMvcConfigurationTests { delegatingConfig = new DelegatingWebMvcConfiguration(); } + @Test public void requestMappingHandlerAdapter() throws Exception { - - delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer)); RequestMappingHandlerAdapter adapter = delegatingConfig.requestMappingHandlerAdapter(); ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); @@ -132,7 +133,7 @@ public class DelegatingWebMvcConfigurationTests { public void getCustomValidator() { given(webMvcConfigurer.getValidator()).willReturn(new LocalValidatorFactoryBean()); - delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer)); delegatingConfig.mvcValidator(); verify(webMvcConfigurer).getValidator(); @@ -142,7 +143,7 @@ public class DelegatingWebMvcConfigurationTests { public void getCustomMessageCodesResolver() { given(webMvcConfigurer.getMessageCodesResolver()).willReturn(new DefaultMessageCodesResolver()); - delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer)); delegatingConfig.getMessageCodesResolver(); verify(webMvcConfigurer).getMessageCodesResolver(); @@ -150,8 +151,7 @@ public class DelegatingWebMvcConfigurationTests { @Test public void handlerExceptionResolver() throws Exception { - - delegatingConfig.setConfigurers(Arrays.asList(webMvcConfigurer)); + delegatingConfig.setConfigurers(Collections.singletonList(webMvcConfigurer)); delegatingConfig.handlerExceptionResolver(); verify(webMvcConfigurer).configureMessageConverters(converters.capture()); @@ -177,7 +177,7 @@ public class DelegatingWebMvcConfigurationTests { delegatingConfig.setConfigurers(configurers); HandlerExceptionResolverComposite composite = - (HandlerExceptionResolverComposite) delegatingConfig.handlerExceptionResolver(); + (HandlerExceptionResolverComposite) delegatingConfig.handlerExceptionResolver(); assertEquals("Only one custom converter is expected", 1, composite.getExceptionResolvers().size()); } @@ -200,11 +200,16 @@ public class DelegatingWebMvcConfigurationTests { RequestMappingHandlerMapping handlerMapping = delegatingConfig.requestMappingHandlerMapping(); assertNotNull(handlerMapping); - assertTrue(handlerMapping.useRegisteredSuffixPatternMatch()); - assertTrue(handlerMapping.useSuffixPatternMatch()); - assertFalse(handlerMapping.useTrailingSlashMatch()); - assertSame(pathHelper, handlerMapping.getUrlPathHelper()); - assertSame(pathMatcher, handlerMapping.getPathMatcher()); + assertEquals("PathMatchConfigurer should configure RegisteredSuffixPatternMatch", + true, handlerMapping.useRegisteredSuffixPatternMatch()); + assertEquals("PathMatchConfigurer should configure SuffixPatternMatch", + true, handlerMapping.useSuffixPatternMatch()); + assertEquals("PathMatchConfigurer should configure TrailingSlashMatch", + false, handlerMapping.useTrailingSlashMatch()); + assertEquals("PathMatchConfigurer should configure UrlPathHelper", + pathHelper, handlerMapping.getUrlPathHelper()); + assertEquals("PathMatchConfigurer should configure PathMatcher", + pathMatcher, handlerMapping.getPathMatcher()); } }