Backport MVC Java config path-related config options

Issue: SPR-14186
This commit is contained in:
Rossen Stoyanchev 2016-04-21 13:50:03 -04:00
parent 3d6b0ca5be
commit cf39078fbb
18 changed files with 657 additions and 20 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -163,6 +163,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables); handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
} }
configurePathMatchingProperties(handlerMappingDef, element, parserContext);
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext); RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext); RuntimeBeanReference validator = getValidator(element, source, parserContext);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element); RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
@ -309,6 +311,40 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return contentNegotiationManagerRef; return contentNegotiationManagerRef;
} }
private void configurePathMatchingProperties(RootBeanDefinition handlerMappingDef, Element element,
ParserContext parserContext) {
Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
if (pathMatchingElement != null) {
Object source = parserContext.extractSource(element);
if (pathMatchingElement.hasAttribute("suffix-pattern")) {
Boolean useSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("suffix-pattern"));
handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch);
}
if (pathMatchingElement.hasAttribute("trailing-slash")) {
Boolean useTrailingSlashMatch = Boolean.valueOf(pathMatchingElement.getAttribute("trailing-slash"));
handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useTrailingSlashMatch);
}
if (pathMatchingElement.hasAttribute("registered-suffixes-only")) {
Boolean useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("registered-suffixes-only"));
handlerMappingDef.getPropertyValues().add("useRegisteredSuffixPatternMatch", useRegisteredSuffixPatternMatch);
}
RuntimeBeanReference pathHelperRef = null;
if (pathMatchingElement.hasAttribute("path-helper")) {
pathHelperRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-helper"));
}
pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(pathHelperRef, parserContext, source);
handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef);
RuntimeBeanReference pathMatcherRef = null;
if (pathMatchingElement.hasAttribute("path-matcher")) {
pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher"));
}
pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, parserContext, source);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
}
}
private Properties getDefaultMediaTypes() { private Properties getDefaultMediaTypes() {
Properties props = new Properties(); Properties props = new Properties();
if (romePresent) { if (romePresent) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,12 +17,16 @@
package org.springframework.web.servlet.config; package org.springframework.web.servlet.config;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.util.UrlPathHelper;
/** /**
* Convenience methods for use in MVC namespace BeanDefinitionParsers. * Convenience methods for use in MVC namespace BeanDefinitionParsers.
@ -41,12 +45,70 @@ abstract class MvcNamespaceUtils {
private static final String HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME = private static final String HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME =
HttpRequestHandlerAdapter.class.getName(); HttpRequestHandlerAdapter.class.getName();
private static final String URL_PATH_HELPER_BEAN_NAME = "mvcUrlPathHelper";
private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher";
public static void registerDefaultComponents(ParserContext parserContext, Object source) { public static void registerDefaultComponents(ParserContext parserContext, Object source) {
registerBeanNameUrlHandlerMapping(parserContext, source); registerBeanNameUrlHandlerMapping(parserContext, source);
registerHttpRequestHandlerAdapter(parserContext, source); registerHttpRequestHandlerAdapter(parserContext, source);
registerSimpleControllerHandlerAdapter(parserContext, source); registerSimpleControllerHandlerAdapter(parserContext, source);
} }
/**
* Adds an alias to an existing well-known name or registers a new instance of a {@link UrlPathHelper}
* under that well-known name, unless already registered.
* @return a RuntimeBeanReference to this {@link UrlPathHelper} instance
* @since 3.2.17
*/
public static RuntimeBeanReference registerUrlPathHelper(RuntimeBeanReference urlPathHelperRef,
ParserContext parserContext, Object source) {
if (urlPathHelperRef != null) {
if (parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) {
parserContext.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME);
}
parserContext.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME);
}
else if (!parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)
&& !parserContext.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) {
RootBeanDefinition urlPathHelperDef = new RootBeanDefinition(UrlPathHelper.class);
urlPathHelperDef.setSource(source);
urlPathHelperDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef);
parserContext.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME));
}
return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME);
}
/**
* Adds an alias to an existing well-known name or registers a new instance of a {@link PathMatcher}
* under that well-known name, unless already registered.
* @return a RuntimeBeanReference to this {@link PathMatcher} instance
* @since 3.2.17
*/
public static RuntimeBeanReference registerPathMatcher(RuntimeBeanReference pathMatcherRef,
ParserContext parserContext, Object source) {
if (pathMatcherRef != null) {
if (parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
parserContext.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
}
parserContext.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
}
else if (!parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)
&& !parserContext.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
RootBeanDefinition pathMatcherDef = new RootBeanDefinition(AntPathMatcher.class);
pathMatcherDef.setSource(source);
pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef);
parserContext.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME));
}
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
}
/** /**
* Registers an {@link HttpRequestHandlerAdapter} under a well-known * Registers an {@link HttpRequestHandlerAdapter} under a well-known
* name unless already registered. * name unless already registered.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.util.Map;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.ManagedMap;
@ -62,10 +63,15 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
} }
urlMap.put(resourceRequestPath, resourceHandlerName); urlMap.put(resourceRequestPath, resourceHandlerName);
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, parserContext, source);
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class); RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source); handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap); handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef);
String order = element.getAttribute("order"); String order = element.getAttribute("order");
// use a default of near-lowest precedence, still allowing for even lower precedence in other mappings // use a default of near-lowest precedence, still allowing for even lower precedence in other mappings

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -76,6 +76,10 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class); RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source); handlerMappingDef.setSource(source);
handlerMappingDef.getPropertyValues().add("order", "1"); handlerMappingDef.getPropertyValues().add("order", "1");
handlerMappingDef.getPropertyValues().add("pathMatcher",
MvcNamespaceUtils.registerPathMatcher(null, parserContext, source));
handlerMappingDef.getPropertyValues().add("urlPathHelper",
MvcNamespaceUtils.registerUrlPathHelper(null, parserContext, source));
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef); parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME)); parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -67,6 +67,11 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
this.configurers.configureAsyncSupport(configurer); this.configurers.configureAsyncSupport(configurer);
} }
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}
@Override @Override
protected void addViewControllers(ViewControllerRegistry registry) { protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry); this.configurers.addViewControllers(registry);

View File

@ -0,0 +1,129 @@
/*
* Copyright 2002-2016 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 org.springframework.util.PathMatcher;
import org.springframework.web.util.UrlPathHelper;
/**
* Helps with configuring HandlerMappings path matching options such as trailing
* slash match, suffix registration, path matcher and path helper.
*
* <p>Configured path matcher and path helper instances are shared for:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
* </ul>
*
* @author Brian Clozel
* @since 3.2.17
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
* @see org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
*/
public class PathMatchConfigurer {
private Boolean suffixPatternMatch;
private Boolean trailingSlashMatch;
private Boolean registeredSuffixPatternMatch;
private UrlPathHelper urlPathHelper;
private PathMatcher pathMatcher;
/**
* Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>By default this is set to {@code true}.
* @see #registeredSuffixPatternMatch
*/
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
this.suffixPatternMatch = suffixPatternMatch;
return this;
}
/**
* Whether to match to URLs irrespective of the presence of a trailing slash.
* If enabled a method mapped to "/users" also matches to "/users/".
* <p>The default value is {@code true}.
*/
public PathMatchConfigurer setUseTrailingSlashMatch(Boolean trailingSlashMatch) {
this.trailingSlashMatch = trailingSlashMatch;
return this;
}
/**
* Whether suffix pattern matching should work only against path extensions
* explicitly registered when you
* {@link WebMvcConfigurer#configureContentNegotiation configure content
* negotiation}. This is generally recommended to reduce ambiguity and to
* avoid issues such as when a "." appears in the path for other reasons.
* <p>By default this is set to "false".
* @see WebMvcConfigurer#configureContentNegotiation
*/
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(
Boolean registeredSuffixPatternMatch) {
this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
return this;
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
*/
public PathMatchConfigurer setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
return this;
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* @see org.springframework.util.AntPathMatcher
*/
public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
return this;
}
public Boolean isUseSuffixPatternMatch() {
return this.suffixPatternMatch;
}
public Boolean isUseTrailingSlashMatch() {
return this.trailingSlashMatch;
}
public Boolean isUseRegisteredSuffixPatternMatch() {
return this.registeredSuffixPatternMatch;
}
public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -47,7 +47,9 @@ import org.springframework.http.converter.json.MappingJacksonHttpMessageConverte
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
@ -75,6 +77,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.util.UrlPathHelper;
/** /**
* This is the main class providing the configuration behind the MVC Java config. * This is the main class providing the configuration behind the MVC Java config.
@ -163,6 +166,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private ContentNegotiationManager contentNegotiationManager; private ContentNegotiationManager contentNegotiationManager;
private PathMatchConfigurer pathMatchConfigurer;
private List<HttpMessageConverter<?>> messageConverters; private List<HttpMessageConverter<?>> messageConverters;
@ -191,6 +196,24 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setOrder(0); handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors()); handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager()); handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
}
if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
}
if (configurer.isUseTrailingSlashMatch() != null) {
handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
}
if (configurer.getPathMatcher() != null) {
handlerMapping.setPathMatcher(configurer.getPathMatcher());
}
if (configurer.getUrlPathHelper() != null) {
handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper());
}
return handlerMapping; return handlerMapping;
} }
@ -259,6 +282,61 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) { protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
} }
/**
* Callback for building the {@link PathMatchConfigurer}.
* Delegates to {@link #configurePathMatch}.
* @since 3.2.17
*/
protected PathMatchConfigurer getPathMatchConfigurer() {
if (this.pathMatchConfigurer == null) {
this.pathMatchConfigurer = new PathMatchConfigurer();
configurePathMatch(this.pathMatchConfigurer);
}
return this.pathMatchConfigurer;
}
/**
* Override this method to configure path matching options.
* @see PathMatchConfigurer
* @since 3.2.17
*/
public void configurePathMatch(PathMatchConfigurer configurer) {
}
/**
* Return a global {@link PathMatcher} instance for path matching
* patterns in {@link HandlerMapping}s.
* This instance can be configured using the {@link PathMatchConfigurer}
* in {@link #configurePathMatch(PathMatchConfigurer)}.
* @since 3.2.17
*/
@Bean
public PathMatcher mvcPathMatcher() {
if (getPathMatchConfigurer().getPathMatcher() != null) {
return getPathMatchConfigurer().getPathMatcher();
}
else {
return new AntPathMatcher();
}
}
/**
* Return a global {@link UrlPathHelper} instance for path matching
* patterns in {@link HandlerMapping}s.
* This instance can be configured using the {@link PathMatchConfigurer}
* in {@link #configurePathMatch(PathMatchConfigurer)}.
* @since 3.2.17
*/
@Bean
public UrlPathHelper mvcUrlPathHelper() {
if (getPathMatchConfigurer().getUrlPathHelper() != null) {
return getPathMatchConfigurer().getUrlPathHelper();
}
else {
return new UrlPathHelper();
}
}
/** /**
* Return a handler mapping ordered at 1 to map URL paths directly to * Return a handler mapping ordered at 1 to map URL paths directly to
* view names. To configure view controllers, override * view names. To configure view controllers, override
@ -272,6 +350,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = (handlerMapping != null ? handlerMapping : new EmptyHandlerMapping()); handlerMapping = (handlerMapping != null ? handlerMapping : new EmptyHandlerMapping());
handlerMapping.setInterceptors(getInterceptors()); handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
return handlerMapping; return handlerMapping;
} }
@ -305,7 +385,13 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
addResourceHandlers(registry); addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = (handlerMapping != null ? handlerMapping : new EmptyHandlerMapping()); if (handlerMapping != null) {
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
}
else {
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping; return handlerMapping;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -79,6 +79,20 @@ public interface WebMvcConfigurer {
*/ */
void configureAsyncSupport(AsyncSupportConfigurer configurer); void configureAsyncSupport(AsyncSupportConfigurer configurer);
/**
* Helps with configuring HandlerMappings path matching options such as trailing slash match,
* suffix registration, path matcher and path helper.
* Configured path matcher and path helper instances are shared for:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
* </ul>
* @since 3.2.17
*/
void configurePathMatch(PathMatchConfigurer configurer);
/** /**
* Add resolvers to support custom controller method argument types. * Add resolvers to support custom controller method argument types.
* <p>This does not override the built-in support for resolving handler * <p>This does not override the built-in support for resolving handler

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -71,6 +71,13 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
public void configureAsyncSupport(AsyncSupportConfigurer configurer) { public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
} }
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void configurePathMatch(PathMatchConfigurer configurer) {
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation is empty. * <p>This implementation is empty.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -61,6 +61,12 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
} }
} }
public void configurePathMatch(PathMatchConfigurer configurer) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configurePathMatch(configurer);
}
}
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (WebMvcConfigurer delegate : this.delegates) { for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureMessageConverters(converters); delegate.configureMessageConverters(converters);

View File

@ -21,6 +21,62 @@
</xsd:annotation> </xsd:annotation>
<xsd:complexType> <xsd:complexType>
<xsd:all minOccurs="0"> <xsd:all minOccurs="0">
<xsd:element name="path-matching" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures the path matching part of the Spring MVC Controller programming model.
Like annotation-driven, code-based alternatives are also documented in EnableWebMvc javadoc.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="suffix-pattern" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether to use suffix pattern match (".*") when matching patterns to requests. If enabled
a method mapped to "/users" also matches to "/users.*".
The default value is true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="trailing-slash" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether to match to URLs irrespective of the presence of a trailing slash.
If enabled a method mapped to "/users" also matches to "/users/".
The default value is true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="registered-suffixes-only" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether suffix pattern matching should work only against path extensions
explicitly registered when you configure content negotiation.
This is generally recommended to reduce ambiguity and to
avoid issues such as when a "." appears in the path for other reasons.
The default value is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="path-helper" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name of the UrlPathHelper to use for resolution of lookup paths.
Use this to override the default UrlPathHelper with a custom subclass, or to share common UrlPathHelper settings across
multiple HandlerMappings and MethodNameResolvers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="path-matcher" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name of the PathMatcher implementation to use for matching URL paths against registered URL patterns.
Default is AntPathMatcher.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="message-converters" minOccurs="0"> <xsd:element name="message-converters" minOccurs="0">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,11 +16,14 @@
package org.springframework.web.servlet.config; package org.springframework.web.servlet.config;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.util.List; import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.DirectFieldAccessor;
@ -30,6 +33,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.MessageCodesResolver;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.bind.support.WebArgumentResolver;
@ -42,7 +46,9 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter;
import org.springframework.web.util.UrlPathHelper;
/** /**
* Test fixture for the configuration in mvc-config-annotation-driven.xml. * Test fixture for the configuration in mvc-config-annotation-driven.xml.
@ -70,6 +76,21 @@ public class AnnotationDrivenBeanDefinitionParserTests {
assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect")); assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));
} }
@Test
public void testPathMatchingConfiguration() {
loadBeanDefinitions("mvc-config-path-matching.xml");
RequestMappingHandlerMapping hm = this.appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(hm);
assertTrue(hm.useSuffixPatternMatch());
assertFalse(hm.useTrailingSlashMatch());
assertTrue(hm.useRegisteredSuffixPatternMatch());
assertThat(hm.getUrlPathHelper(), Matchers.instanceOf(TestPathHelper.class));
assertThat(hm.getPathMatcher(), Matchers.instanceOf(TestPathMatcher.class));
List<String> fileExtensions = hm.getContentNegotiationManager().getAllFileExtensions();
assertThat(fileExtensions, Matchers.contains("xml"));
assertThat(fileExtensions, Matchers.hasSize(1));
}
@Test @Test
public void testMessageConverters() { public void testMessageConverters() {
loadBeanDefinitions("mvc-config-message-converters.xml"); loadBeanDefinitions("mvc-config-message-converters.xml");
@ -198,3 +219,7 @@ class TestMessageCodesResolver implements MessageCodesResolver {
} }
} }
class TestPathMatcher extends AntPathMatcher { }
class TestPathHelper extends UrlPathHelper { }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,9 +20,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -47,6 +49,7 @@ import org.springframework.mock.web.test.MockRequestDispatcher;
import org.springframework.mock.web.test.MockServletContext; import org.springframework.mock.web.test.MockServletContext;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.PathMatcher;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
@ -79,6 +82,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor; import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
import org.springframework.web.util.UrlPathHelper;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -89,6 +93,10 @@ import static org.junit.Assert.*;
*/ */
public class MvcNamespaceTests { public class MvcNamespaceTests {
private static final String VIEWCONTROLLER_BEAN_NAME =
"org.springframework.web.servlet.config.viewControllerHandlerMapping";
private GenericWebApplicationContext appContext; private GenericWebApplicationContext appContext;
private TestController handler; private TestController handler;
@ -234,7 +242,7 @@ public class MvcNamespaceTests {
@Test @Test
public void testResources() throws Exception { public void testResources() throws Exception {
loadBeanDefinitions("mvc-config-resources.xml", 5); loadBeanDefinitions("mvc-config-resources.xml", 7);
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class); HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter); assertNotNull(adapter);
@ -267,7 +275,7 @@ public class MvcNamespaceTests {
@Test @Test
public void testResourcesWithOptionalAttributes() throws Exception { public void testResourcesWithOptionalAttributes() throws Exception {
loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 5); loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 7);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
@ -349,7 +357,7 @@ public class MvcNamespaceTests {
@Test @Test
public void testViewControllers() throws Exception { public void testViewControllers() throws Exception {
loadBeanDefinitions("mvc-config-view-controllers.xml", 15); loadBeanDefinitions("mvc-config-view-controllers.xml", 17);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
@ -409,7 +417,7 @@ public class MvcNamespaceTests {
/** WebSphere gives trailing servlet path slashes by default!! */ /** WebSphere gives trailing servlet path slashes by default!! */
@Test @Test
public void testViewControllersOnWebSphere() throws Exception { public void testViewControllersOnWebSphere() throws Exception {
loadBeanDefinitions("mvc-config-view-controllers.xml", 15); loadBeanDefinitions("mvc-config-view-controllers.xml", 17);
SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class); SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);
@ -453,7 +461,7 @@ public class MvcNamespaceTests {
@Test @Test
public void testViewControllersDefaultConfig() { public void testViewControllersDefaultConfig() {
loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 4); loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 6);
BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class); BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
assertNotNull(beanNameMapping); assertNotNull(beanNameMapping);
@ -492,6 +500,27 @@ public class MvcNamespaceTests {
assertEquals(1, deferredResultInterceptors.length); assertEquals(1, deferredResultInterceptors.length);
} }
@Test
public void testPathMatchingHandlerMappings() throws Exception {
loadBeanDefinitions("mvc-config-path-matching-mappings.xml", 19);
RequestMappingHandlerMapping requestMapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(requestMapping);
assertEquals(TestPathHelper.class, requestMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, requestMapping.getPathMatcher().getClass());
SimpleUrlHandlerMapping viewController = appContext.getBean(VIEWCONTROLLER_BEAN_NAME, SimpleUrlHandlerMapping.class);
assertNotNull(viewController);
assertEquals(TestPathHelper.class, viewController.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, viewController.getPathMatcher().getClass());
for (SimpleUrlHandlerMapping handlerMapping : appContext.getBeansOfType(SimpleUrlHandlerMapping.class).values()) {
assertNotNull(handlerMapping);
assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass());
}
}
private void loadBeanDefinitions(String fileName, int expectedBeanCount) { private void loadBeanDefinitions(String fileName, int expectedBeanCount) {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
@ -564,4 +593,45 @@ public class MvcNamespaceTests {
public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { } public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { }
public static class TestPathMatcher implements PathMatcher {
@Override
public boolean isPattern(String path) {
return false;
}
@Override
public boolean match(String pattern, String path) {
return path.matches(pattern);
}
@Override
public boolean matchStart(String pattern, String path) {
return false;
}
@Override
public String extractPathWithinPattern(String pattern, String path) {
return null;
}
@Override
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
return null;
}
@Override
public Comparator<String> getPatternComparator(String path) {
return null;
}
@Override
public String combine(String pattern1, String pattern2) {
return null;
}
}
public static class TestPathHelper extends UrlPathHelper {
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.FormattingConversionService; import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.PathMatcher;
import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
@ -40,7 +41,9 @@ import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.util.UrlPathHelper;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*; import static org.mockito.BDDMockito.*;
@ -178,4 +181,30 @@ public class DelegatingWebMvcConfigurationTests {
assertEquals("Only one custom converter is expected", 1, composite.getExceptionResolvers().size()); assertEquals("Only one custom converter is expected", 1, composite.getExceptionResolvers().size());
} }
@Test
public void configurePathMatch() throws Exception {
final PathMatcher pathMatcher = mock(PathMatcher.class);
final UrlPathHelper pathHelper = mock(UrlPathHelper.class);
List<WebMvcConfigurer> configurers = new ArrayList<WebMvcConfigurer>();
configurers.add(new WebMvcConfigurerAdapter() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseRegisteredSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUrlPathHelper(pathHelper)
.setPathMatcher(pathMatcher);
}
});
delegatingConfig.setConfigurers(configurers);
RequestMappingHandlerMapping handlerMapping = delegatingConfig.requestMappingHandlerMapping();
assertNotNull(handlerMapping);
assertTrue(handlerMapping.useRegisteredSuffixPatternMatch());
assertTrue(handlerMapping.useSuffixPatternMatch());
assertFalse(handlerMapping.useTrailingSlashMatch());
assertSame(pathHelper, handlerMapping.getUrlPathHelper());
assertSame(pathMatcher, handlerMapping.getPathMatcher());
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -35,6 +35,7 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockServletContext; import org.springframework.mock.web.test.MockServletContext;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.DefaultMessageCodesResolver; import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
@ -62,6 +63,7 @@ import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/** /**
* A test fixture with a sub-class of {@link WebMvcConfigurationSupport} that * A test fixture with a sub-class of {@link WebMvcConfigurationSupport} that
@ -92,6 +94,10 @@ public class WebMvcConfigurationSupportExtensionTests {
RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping(); RequestMappingHandlerMapping rmHandlerMapping = webConfig.requestMappingHandlerMapping();
rmHandlerMapping.setApplicationContext(webAppContext); rmHandlerMapping.setApplicationContext(webAppContext);
rmHandlerMapping.afterPropertiesSet(); rmHandlerMapping.afterPropertiesSet();
assertEquals(TestPathHelper.class, rmHandlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, rmHandlerMapping.getPathMatcher().getClass());
HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/")); HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
assertNotNull(chain.getInterceptors()); assertNotNull(chain.getInterceptors());
assertEquals(2, chain.getInterceptors().length); assertEquals(2, chain.getInterceptors().length);
@ -102,6 +108,8 @@ public class WebMvcConfigurationSupportExtensionTests {
handlerMapping.setApplicationContext(webAppContext); handlerMapping.setApplicationContext(webAppContext);
assertNotNull(handlerMapping); assertNotNull(handlerMapping);
assertEquals(1, handlerMapping.getOrder()); assertEquals(1, handlerMapping.getOrder());
assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass());
HandlerExecutionChain handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path")); HandlerExecutionChain handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/path"));
assertNotNull(handler.getHandler()); assertNotNull(handler.getHandler());
@ -109,6 +117,8 @@ public class WebMvcConfigurationSupportExtensionTests {
handlerMapping.setApplicationContext(webAppContext); handlerMapping.setApplicationContext(webAppContext);
assertNotNull(handlerMapping); assertNotNull(handlerMapping);
assertEquals(Integer.MAX_VALUE-1, handlerMapping.getOrder()); assertEquals(Integer.MAX_VALUE-1, handlerMapping.getOrder());
assertEquals(TestPathHelper.class, handlerMapping.getUrlPathHelper().getClass());
assertEquals(TestPathMatcher.class, handlerMapping.getPathMatcher().getClass());
handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif")); handler = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif"));
assertNotNull(handler.getHandler()); assertNotNull(handler.getHandler());
@ -280,6 +290,12 @@ public class WebMvcConfigurationSupportExtensionTests {
registry.addInterceptor(new LocaleChangeInterceptor()); registry.addInterceptor(new LocaleChangeInterceptor());
} }
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPathMatcher(new TestPathMatcher());
configurer.setUrlPathHelper(new TestPathHelper());
}
@SuppressWarnings("serial") @SuppressWarnings("serial")
@Override @Override
public MessageCodesResolver getMessageCodesResolver() { public MessageCodesResolver getMessageCodesResolver() {
@ -308,4 +324,8 @@ public class WebMvcConfigurationSupportExtensionTests {
} }
private class TestPathHelper extends UrlPathHelper {}
private class TestPathMatcher extends AntPathMatcher {}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,16 +27,23 @@ import javax.servlet.http.HttpServletRequest;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.FormattingConversionService; import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockServletContext; import org.springframework.mock.web.test.MockServletContext;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerExecutionChain;
@ -49,6 +56,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.util.UrlPathHelper;
/** /**
* A test fixture with an {@link WebMvcConfigurationSupport} instance. * A test fixture with an {@link WebMvcConfigurationSupport} instance.
@ -163,6 +171,32 @@ public class WebMvcConfigurationSupportTests {
assertNotNull(eher.getApplicationContext()); assertNotNull(eher.getApplicationContext());
} }
@Test
public void defaultPathMatchConfiguration() throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(WebConfig.class);
context.refresh();
UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class);
PathMatcher pathMatcher = context.getBean(PathMatcher.class);
assertNotNull(urlPathHelper);
assertNotNull(pathMatcher);
assertEquals(AntPathMatcher.class, pathMatcher.getClass());
}
@EnableWebMvc
@Configuration
@SuppressWarnings("unused")
static class WebConfig {
@Bean
public TestController testController() {
return new TestController();
}
}
@Controller @Controller
private static class TestController { private static class TestController {

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" />
<mvc:annotation-driven>
<mvc:path-matching
path-helper="pathHelper"
path-matcher="pathMatcher" />
</mvc:annotation-driven>
<mvc:view-controller path="/" view-name="home"/>
<mvc:view-controller path="/test" view-name="test"/>
<bean id="pathMatcher" class="org.springframework.web.servlet.config.MvcNamespaceTests$TestPathMatcher" />
<bean id="pathHelper" class="org.springframework.web.servlet.config.MvcNamespaceTests$TestPathHelper" />
</beans>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher" />
</mvc:annotation-driven>
<bean id="pathMatcher" class="org.springframework.web.servlet.config.TestPathMatcher" />
<bean id="pathHelper" class="org.springframework.web.servlet.config.TestPathHelper" />
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>
xml=application/rss+xml
</value>
</property>
</bean>
</beans>