Browse Source

Enable use of parsed patterns by default in Spring MVC

Closes gh-28607
pull/28544/merge
rstoyanchev 4 years ago
parent
commit
92cf1e13e8
  1. 18
      spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java
  2. 6
      spring-web/src/main/java/org/springframework/web/cors/UrlBasedCorsConfigurationSource.java
  3. 6
      spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java
  4. 26
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
  5. 79
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/PathMatchConfigurer.java
  6. 76
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
  7. 65
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
  8. 4
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
  9. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
  10. 16
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
  11. 3
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
  12. 19
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationIntegrationTests.java
  13. 9
      spring-webmvc/src/test/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMappingTests.java
  14. 8
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractServletHandlerMethodTests.java
  15. 1
      spring-webmvc/src/test/resources/org/springframework/web/servlet/handler/map3.xml
  16. 42
      src/docs/asciidoc/web/webmvc.adoc

18
spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 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.
@ -127,6 +127,8 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
@Nullable @Nullable
private FlashMapManager flashMapManager; private FlashMapManager flashMapManager;
private boolean preferPathMatcher = false;
@Nullable @Nullable
private PathPatternParser patternParser; private PathPatternParser patternParser;
@ -317,8 +319,10 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
* @param parser the parser to use * @param parser the parser to use
* @since 5.3 * @since 5.3
*/ */
public void setPatternParser(PathPatternParser parser) { public StandaloneMockMvcBuilder setPatternParser(@Nullable PathPatternParser parser) {
this.patternParser = parser; this.patternParser = parser;
this.preferPathMatcher = (this.patternParser == null);
return this;
} }
/** /**
@ -332,6 +336,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
@Deprecated @Deprecated
public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch; this.useSuffixPatternMatch = useSuffixPatternMatch;
this.preferPathMatcher |= useSuffixPatternMatch;
return this; return this;
} }
@ -468,15 +473,16 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
RequestMappingHandlerMapping handlerMapping = handlerMappingFactory.get(); RequestMappingHandlerMapping handlerMapping = handlerMappingFactory.get();
handlerMapping.setEmbeddedValueResolver(new StaticStringValueResolver(placeholderValues)); handlerMapping.setEmbeddedValueResolver(new StaticStringValueResolver(placeholderValues));
if (patternParser != null) { if (patternParser == null && preferPathMatcher) {
handlerMapping.setPatternParser(patternParser); handlerMapping.setPatternParser(null);
}
else {
handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch); handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
if (removeSemicolonContent != null) { if (removeSemicolonContent != null) {
handlerMapping.setRemoveSemicolonContent(removeSemicolonContent); handlerMapping.setRemoveSemicolonContent(removeSemicolonContent);
} }
} }
else if (patternParser != null) {
handlerMapping.setPatternParser(patternParser);
}
handlerMapping.setUseTrailingSlashMatch(useTrailingSlashPatternMatch); handlerMapping.setUseTrailingSlashMatch(useTrailingSlashPatternMatch);
handlerMapping.setOrder(0); handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));

6
spring-web/src/main/java/org/springframework/web/cors/UrlBasedCorsConfigurationSource.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2022 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.
@ -53,7 +53,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
*/ */
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource { public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
private static PathMatcher defaultPathMatcher = new AntPathMatcher(); private static final PathMatcher defaultPathMatcher = new AntPathMatcher();
private final PathPatternParser patternParser; private final PathPatternParser patternParser;
@ -157,7 +157,7 @@ public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
* pattern matching with {@link PathMatcher} or with parsed {@link PathPattern}s. * pattern matching with {@link PathMatcher} or with parsed {@link PathPattern}s.
* <p>In Spring MVC, either a resolved String lookupPath or a parsed * <p>In Spring MVC, either a resolved String lookupPath or a parsed
* {@code RequestPath} is always available within {@code DispatcherServlet} * {@code RequestPath} is always available within {@code DispatcherServlet}
* processing. However in a Servlet {@code Filter} such as {@code CorsFilter} * processing. However, in a Servlet {@code Filter} such as {@code CorsFilter}
* that may or may not be the case. * that may or may not be the case.
* <p>By default this is set to {@code true} in which case lazy lookupPath * <p>By default this is set to {@code true} in which case lazy lookupPath
* initialization is allowed. Set this to {@code false} when an * initialization is allowed. Set this to {@code false} when an

6
spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java

@ -252,6 +252,9 @@ public abstract class ServletRequestPathUtils {
if (UrlPathHelper.servlet4Present) { if (UrlPathHelper.servlet4Present) {
String servletPathPrefix = Servlet4Delegate.getServletPathPrefix(request); String servletPathPrefix = Servlet4Delegate.getServletPathPrefix(request);
if (StringUtils.hasText(servletPathPrefix)) { if (StringUtils.hasText(servletPathPrefix)) {
if (servletPathPrefix.endsWith("/")) {
servletPathPrefix = servletPathPrefix.substring(0, servletPathPrefix.length() - 1);
}
return new ServletRequestPath(requestUri, request.getContextPath(), servletPathPrefix); return new ServletRequestPath(requestUri, request.getContextPath(), servletPathPrefix);
} }
} }
@ -272,8 +275,7 @@ public abstract class ServletRequestPathUtils {
if (mapping == null) { if (mapping == null) {
mapping = request.getHttpServletMapping(); mapping = request.getHttpServletMapping();
} }
MappingMatch match = mapping.getMappingMatch(); if (!ObjectUtils.nullSafeEquals(mapping.getMappingMatch(), MappingMatch.PATH)) {
if (!ObjectUtils.nullSafeEquals(match, MappingMatch.PATH)) {
return null; return null;
} }
String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE); String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 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.
@ -405,22 +405,28 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
if (pathMatchingElement != null) { if (pathMatchingElement != null) {
Object source = context.extractSource(element); Object source = context.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")) { if (pathMatchingElement.hasAttribute("trailing-slash")) {
Boolean useTrailingSlashMatch = Boolean.valueOf(pathMatchingElement.getAttribute("trailing-slash")); boolean useTrailingSlashMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("trailing-slash"));
handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useTrailingSlashMatch); handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useTrailingSlashMatch);
} }
boolean preferPathMatcher = false;
if (pathMatchingElement.hasAttribute("suffix-pattern")) {
boolean useSuffixPatternMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("suffix-pattern"));
handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch);
preferPathMatcher |= useSuffixPatternMatch;
}
if (pathMatchingElement.hasAttribute("registered-suffixes-only")) { if (pathMatchingElement.hasAttribute("registered-suffixes-only")) {
Boolean useRegisteredSuffixPatternMatch = Boolean.valueOf(pathMatchingElement.getAttribute("registered-suffixes-only")); boolean useRegisteredSuffixPatternMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("registered-suffixes-only"));
handlerMappingDef.getPropertyValues().add("useRegisteredSuffixPatternMatch", useRegisteredSuffixPatternMatch); handlerMappingDef.getPropertyValues().add("useRegisteredSuffixPatternMatch", useRegisteredSuffixPatternMatch);
preferPathMatcher |= useRegisteredSuffixPatternMatch;
} }
RuntimeBeanReference pathHelperRef = null; RuntimeBeanReference pathHelperRef = null;
if (pathMatchingElement.hasAttribute("path-helper")) { if (pathMatchingElement.hasAttribute("path-helper")) {
pathHelperRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-helper")); pathHelperRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-helper"));
preferPathMatcher = true;
} }
pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(pathHelperRef, context, source); pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(pathHelperRef, context, source);
handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef); handlerMappingDef.getPropertyValues().add("urlPathHelper", pathHelperRef);
@ -428,10 +434,16 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
RuntimeBeanReference pathMatcherRef = null; RuntimeBeanReference pathMatcherRef = null;
if (pathMatchingElement.hasAttribute("path-matcher")) { if (pathMatchingElement.hasAttribute("path-matcher")) {
pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher")); pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher"));
preferPathMatcher = true;
} }
pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, context, source); pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, context, source);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef); handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
if (preferPathMatcher) {
handlerMappingDef.getPropertyValues().add("patternParser", null);
}
} }
} }
private Properties getDefaultMediaTypes() { private Properties getDefaultMediaTypes() {

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 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.
@ -23,7 +23,6 @@ import java.util.function.Predicate;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern;
@ -34,14 +33,19 @@ import org.springframework.web.util.pattern.PathPatternParser;
* <ul> * <ul>
* <li>{@link WebMvcConfigurationSupport#requestMappingHandlerMapping}</li> * <li>{@link WebMvcConfigurationSupport#requestMappingHandlerMapping}</li>
* <li>{@link WebMvcConfigurationSupport#viewControllerHandlerMapping}</li> * <li>{@link WebMvcConfigurationSupport#viewControllerHandlerMapping}</li>
* <li>{@link WebMvcConfigurationSupport#beanNameHandlerMapping}</li>
* <li>{@link WebMvcConfigurationSupport#routerFunctionMapping}</li>
* <li>{@link WebMvcConfigurationSupport#resourceHandlerMapping}</li> * <li>{@link WebMvcConfigurationSupport#resourceHandlerMapping}</li>
* </ul> * </ul>
* *
* @author Brian Clozel * @author Brian Clozel
* @author Rossen Stoyanchev
* @since 4.0.3 * @since 4.0.3
*/ */
public class PathMatchConfigurer { public class PathMatchConfigurer {
private boolean preferPathMatcher = false;
@Nullable @Nullable
private PathPatternParser patternParser; private PathPatternParser patternParser;
@ -74,17 +78,28 @@ public class PathMatchConfigurer {
/** /**
* Enable use of parsed {@link PathPattern}s as described in * Set the {@link PathPatternParser} to parse {@link PathPattern patterns}
* {@link AbstractHandlerMapping#setPatternParser(PathPatternParser)}. * with for URL path matching. Parsed patterns provide a more modern and
* <p><strong>Note:</strong> This is mutually exclusive with use of * efficient alternative to String path matching via {@link AntPathMatcher}.
* {@link #setUrlPathHelper(UrlPathHelper)} and * <p><strong>Note:</strong> This property is mutually exclusive with the
* {@link #setPathMatcher(PathMatcher)}. * following other, {@code AntPathMatcher} related properties:
* <p>By default this is not enabled. * <ul>
* <li>{@link #setUseSuffixPatternMatch(Boolean)}
* <li>{@link #setUseRegisteredSuffixPatternMatch(Boolean)}
* <li>{@link #setUrlPathHelper(UrlPathHelper)}
* <li>{@link #setPathMatcher(PathMatcher)}
* </ul>
* <p>By default, as of 6.0, a {@link PathPatternParser} with default
* settings is used, which enables parsed {@link PathPattern patterns}.
* Set this property to {@code null} to fall back on String path matching via
* {@link AntPathMatcher} instead, or alternatively, setting one of the above
* listed {@code AntPathMatcher} related properties has the same effect.
* @param patternParser the parser to pre-parse patterns with * @param patternParser the parser to pre-parse patterns with
* @since 5.3 * @since 5.3
*/ */
public PathMatchConfigurer setPatternParser(PathPatternParser patternParser) { public PathMatchConfigurer setPatternParser(@Nullable PathPatternParser patternParser) {
this.patternParser = patternParser; this.patternParser = patternParser;
this.preferPathMatcher = (patternParser == null);
return this; return this;
} }
@ -120,9 +135,11 @@ public class PathMatchConfigurer {
/** /**
* Whether to use suffix pattern match (".*") when matching patterns to * Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*". * requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p><strong>Note:</strong> This property is mutually exclusive with
* {@link #setPatternParser(PathPatternParser)}. If set, it enables use of
* String path matching, unless a {@code PathPatternParser} is also
* explicitly set in which case this property is ignored.
* <p>By default this is set to {@code false}. * <p>By default this is set to {@code false}.
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension * {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options. As there is no replacement for this method, in 5.2.x it is * config options. As there is no replacement for this method, in 5.2.x it is
@ -132,6 +149,7 @@ public class PathMatchConfigurer {
@Deprecated @Deprecated
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) { public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
this.suffixPatternMatch = suffixPatternMatch; this.suffixPatternMatch = suffixPatternMatch;
this.preferPathMatcher |= suffixPatternMatch;
return this; return this;
} }
@ -141,9 +159,11 @@ public class PathMatchConfigurer {
* {@link WebMvcConfigurer#configureContentNegotiation configure content * {@link WebMvcConfigurer#configureContentNegotiation configure content
* negotiation}. This is generally recommended to reduce ambiguity and to * negotiation}. This is generally recommended to reduce ambiguity and to
* avoid issues such as when a "." appears in the path for other reasons. * avoid issues such as when a "." appears in the path for other reasons.
* <p><strong>Note:</strong> This property is mutually exclusive with
* {@link #setPatternParser(PathPatternParser)}. If set, it enables use of
* String path matching, unless a {@code PathPatternParser} is also
* explicitly set in which case this property is ignored.
* <p>By default this is set to "false". * <p>By default this is set to "false".
* <p><strong>Note:</strong> This property is mutually exclusive with and
* ignored when {@link #setPatternParser(PathPatternParser)} is set.
* @deprecated as of 5.2.4. See class-level note in * @deprecated as of 5.2.4. See class-level note in
* {@link RequestMappingHandlerMapping} on the deprecation of path extension * {@link RequestMappingHandlerMapping} on the deprecation of path extension
* config options. * config options.
@ -151,31 +171,54 @@ public class PathMatchConfigurer {
@Deprecated @Deprecated
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean registeredSuffixPatternMatch) { public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean registeredSuffixPatternMatch) {
this.registeredSuffixPatternMatch = registeredSuffixPatternMatch; this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
this.preferPathMatcher |= registeredSuffixPatternMatch;
return this; return this;
} }
/** /**
* Set the UrlPathHelper to use to resolve the mapping path for the application. * Set the UrlPathHelper to use to resolve the mapping path for the application.
* <p><strong>Note:</strong> This property is mutually exclusive with and * <p><strong>Note:</strong> This property is mutually exclusive with
* ignored when {@link #setPatternParser(PathPatternParser)} is set. * {@link #setPatternParser(PathPatternParser)}. If set, it enables use of
* String path matching, unless a {@code PathPatternParser} is also
* explicitly set in which case this property is ignored.
* <p>By default this is an instance of {@link UrlPathHelper} with default
* settings.
*/ */
public PathMatchConfigurer setUrlPathHelper(UrlPathHelper urlPathHelper) { public PathMatchConfigurer setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper; this.urlPathHelper = urlPathHelper;
this.preferPathMatcher = true;
return this; return this;
} }
/** /**
* Set the PathMatcher to use for String pattern matching. * Set the PathMatcher to use for String pattern matching.
* <p>By default this is {@link AntPathMatcher}. * <p><strong>Note:</strong> This property is mutually exclusive with
* <p><strong>Note:</strong> This property is mutually exclusive with and * {@link #setPatternParser(PathPatternParser)}. If set, it enables use of
* ignored when {@link #setPatternParser(PathPatternParser)} is set. * String path matching, unless a {@code PathPatternParser} is also
* explicitly set in which case this property is ignored.
* <p>By default this is an instance of {@link AntPathMatcher} with default
* settings.
*/ */
public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) { public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher; this.pathMatcher = pathMatcher;
this.preferPathMatcher = true;
return this; return this;
} }
/**
* Whether to prefer {@link PathMatcher}. This is the case when either is true:
* <ul>
* <li>{@link PathPatternParser} is explicitly set to {@code null}.
* <li>{@link PathPatternParser} is not explicitly set, and a
* {@link PathMatcher} related option is explicitly set.
* </ul>
* @since 6.0
*/
protected boolean preferPathMatcher() {
return (this.patternParser == null && this.preferPathMatcher);
}
/** /**
* Return the {@link PathPatternParser} to use, if configured. * Return the {@link PathPatternParser} to use, if configured.
* @since 5.3 * @since 5.3

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 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.
@ -313,18 +313,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0); mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager); mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer pathConfig = getPathMatchConfigurer(); initHandlerMapping(mapping, conversionService, resourceUrlProvider);
if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
else {
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.preferPathMatcher()) {
Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch(); Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) { if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch); mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
@ -334,10 +328,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch); mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
} }
} }
Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch(); Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) { if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch); mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
} }
if (pathConfig.getPathPrefixes() != null) { if (pathConfig.getPathPrefixes() != null) {
mapping.setPathPrefixes(pathConfig.getPathPrefixes()); mapping.setPathPrefixes(pathConfig.getPathPrefixes());
} }
@ -497,21 +493,29 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext); ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
addViewControllers(registry); addViewControllers(registry);
AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping(); AbstractHandlerMapping mapping = registry.buildHandlerMapping();
if (handlerMapping == null) { initHandlerMapping(mapping, conversionService, resourceUrlProvider);
return null; return mapping;
}
private void initHandlerMapping(
@Nullable AbstractHandlerMapping mapping, FormattingConversionService conversionService,
ResourceUrlProvider resourceUrlProvider) {
if (mapping == null) {
return;
} }
PathMatchConfigurer pathConfig = getPathMatchConfigurer(); PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) { if (pathConfig.preferPathMatcher()) {
handlerMapping.setPatternParser(pathConfig.getPatternParser()); mapping.setPatternParser(null);
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
} }
else { else if (pathConfig.getPatternParser() != null) {
handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault()); mapping.setPatternParser(pathConfig.getPatternParser());
handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
} }
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations()); mapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
} }
/** /**
@ -532,18 +536,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping(); BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2); mapping.setOrder(2);
initHandlerMapping(mapping, conversionService, resourceUrlProvider);
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
else {
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping; return mapping;
} }
@ -599,20 +592,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper()); this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
addResourceHandlers(registry); addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); AbstractHandlerMapping mapping = registry.getHandlerMapping();
if (handlerMapping == null) { initHandlerMapping(mapping, conversionService, resourceUrlProvider);
return null; return mapping;
}
if (pathConfig.getPatternParser() != null) {
handlerMapping.setPatternParser(pathConfig.getPatternParser());
}
else {
handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
} }
/** /**

65
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 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.
@ -49,6 +49,7 @@ import org.springframework.web.cors.CorsProcessor;
import org.springframework.web.cors.CorsUtils; import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.DefaultCorsProcessor; import org.springframework.web.cors.DefaultCorsProcessor;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
@ -87,7 +88,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
private Object defaultHandler; private Object defaultHandler;
@Nullable @Nullable
private PathPatternParser patternParser; private PathPatternParser patternParser = new PathPatternParser();
private UrlPathHelper urlPathHelper = new UrlPathHelper(); private UrlPathHelper urlPathHelper = new UrlPathHelper();
@ -127,43 +128,45 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
} }
/** /**
* Enable use of pre-parsed {@link PathPattern}s as an alternative to * Set the {@link PathPatternParser} to parse {@link PathPattern patterns}
* String pattern matching with {@link AntPathMatcher}. The syntax is * with for URL path matching. Parsed patterns provide a more modern and
* largely the same but the {@code PathPattern} syntax is more tailored for * efficient alternative to String path matching via {@link AntPathMatcher}.
* web applications, and its implementation is more efficient. * <p><strong>Note:</strong> This property is mutually exclusive with the
* <p>This property is mutually exclusive with the following others which * below properties, all of which are not necessary for parsed patterns and
* are effectively ignored when this is set: * are ignored when a {@code PathPatternParser} is available:
* <ul> * <ul>
* <li>{@link #setAlwaysUseFullPath} -- {@code PathPatterns} always use the * <li>{@link #setAlwaysUseFullPath} -- parsed patterns always use the
* full path and ignore the servletPath/pathInfo which are decoded and * full path and consider the servletPath only when a Servlet is mapped by
* partially normalized and therefore not comparable against the * path prefix.
* {@link HttpServletRequest#getRequestURI() requestURI}. * <li>{@link #setRemoveSemicolonContent} -- parsed patterns always
* <li>{@link #setRemoveSemicolonContent} -- {@code PathPatterns} always
* ignore semicolon content for path matching purposes, but path parameters * ignore semicolon content for path matching purposes, but path parameters
* remain available for use in controllers via {@code @MatrixVariable}. * remain available for use in controllers via {@code @MatrixVariable}.
* <li>{@link #setUrlDecode} -- {@code PathPatterns} match one decoded path * <li>{@link #setUrlDecode} -- parsed patterns match one decoded path
* segment at a time and never need the full decoded path which can cause * segment at a time and therefore don't need to decode the full path.
* issues due to decoded reserved characters. * <li>{@link #setUrlPathHelper} -- for parsed patterns, the request path
* <li>{@link #setUrlPathHelper} -- the request path is pre-parsed globally * is parsed once in {@link org.springframework.web.servlet.DispatcherServlet
* by the {@link org.springframework.web.servlet.DispatcherServlet * DispatcherServlet} or in
* DispatcherServlet} or by
* {@link org.springframework.web.filter.ServletRequestPathFilter * {@link org.springframework.web.filter.ServletRequestPathFilter
* ServletRequestPathFilter} using {@link ServletRequestPathUtils} and saved * ServletRequestPathFilter} using {@link ServletRequestPathUtils} and cached
* in a request attribute for re-use. * in a request attribute.
* <li>{@link #setPathMatcher} -- patterns are parsed to {@code PathPatterns} * <li>{@link #setPathMatcher} -- a parsed patterns encapsulates the logic
* and used instead of String matching with {@code PathMatcher}. * for path matching and does need a {@code PathMatcher}.
* </ul> * </ul>
* <p>By default this is not set. * <p>By default, as of 6.0, this is set to a {@link PathPatternParser}
* instance with default settings and therefore use of parsed patterns is
* enabled. Set this to {@code null} to switch to String path matching
* via {@link AntPathMatcher} instead.
* @param patternParser the parser to use * @param patternParser the parser to use
* @since 5.3 * @since 5.3
*/ */
public void setPatternParser(PathPatternParser patternParser) { public void setPatternParser(@Nullable PathPatternParser patternParser) {
this.patternParser = patternParser; this.patternParser = patternParser;
} }
/** /**
* Return the {@link #setPatternParser(PathPatternParser) configured} * Return the {@link #setPatternParser(PathPatternParser) configured}
* {@code PathPatternParser}, or {@code null}. * {@code PathPatternParser}, or {@code null} otherwise which indicates that
* String pattern matching with {@link AntPathMatcher} is enabled instead.
* @since 5.3 * @since 5.3
*/ */
@Nullable @Nullable
@ -570,7 +573,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
protected String initLookupPath(HttpServletRequest request) { protected String initLookupPath(HttpServletRequest request) {
if (usesPathPatterns()) { if (usesPathPatterns()) {
request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE); request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request); RequestPath requestPath = getRequestPath(request);
String lookupPath = requestPath.pathWithinApplication().value(); String lookupPath = requestPath.pathWithinApplication().value();
return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath); return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
} }
@ -579,6 +582,14 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
} }
} }
private RequestPath getRequestPath(HttpServletRequest request) {
// Expect pre-parsed path with DispatcherServlet,
// but otherwise parse per handler lookup + cache for handling
return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null ?
ServletRequestPathUtils.getParsedRequestPath(request) :
ServletRequestPathUtils.parseAndCache(request);
}
/** /**
* Build a {@link HandlerExecutionChain} for the given handler, including * Build a {@link HandlerExecutionChain} for the given handler, including
* applicable interceptors. * applicable interceptors.

4
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 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.
@ -102,7 +102,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
@Override @Override
public void setPatternParser(PathPatternParser patternParser) { public void setPatternParser(@Nullable PathPatternParser patternParser) {
Assert.state(this.mappingRegistry.getRegistrations().isEmpty(), Assert.state(this.mappingRegistry.getRegistrations().isEmpty(),
"PathPatternParser must be set before the initialization of " + "PathPatternParser must be set before the initialization of " +
"request mappings through InitializingBean#afterPropertiesSet."); "request mappings through InitializingBean#afterPropertiesSet.");

2
spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java

@ -75,7 +75,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
@Override @Override
public void setPatternParser(PathPatternParser patternParser) { public void setPatternParser(@Nullable PathPatternParser patternParser) {
Assert.state(this.handlerMap.isEmpty(), Assert.state(this.handlerMap.isEmpty(),
"PathPatternParser must be set before the initialization of " + "PathPatternParser must be set before the initialization of " +
"the handler map via ApplicationContextAware#setApplicationContext."); "the handler map via ApplicationContextAware#setApplicationContext.");

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

@ -76,6 +76,8 @@ import org.springframework.web.util.pattern.PathPatternParser;
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware { implements MatchableHandlerMapping, EmbeddedValueResolverAware {
private boolean defaultPatternParser = true;
private boolean useSuffixPatternMatch = false; private boolean useSuffixPatternMatch = false;
private boolean useRegisteredSuffixPatternMatch = false; private boolean useRegisteredSuffixPatternMatch = false;
@ -92,6 +94,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration(); private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
@Override
public void setPatternParser(@Nullable PathPatternParser patternParser) {
if (patternParser != null) {
this.defaultPatternParser = false;
}
super.setPatternParser(patternParser);
}
/** /**
* Whether to use suffix pattern match (".*") when matching patterns to * Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*". * requests. If enabled a method mapped to "/users" also matches to "/users.*".
@ -191,6 +201,12 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
this.config.setTrailingSlashMatch(useTrailingSlashMatch()); this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager()); this.config.setContentNegotiationManager(getContentNegotiationManager());
if (getPatternParser() != null && this.defaultPatternParser &&
(this.useSuffixPatternMatch || this.useRegisteredSuffixPatternMatch)) {
setPatternParser(null);
}
if (getPatternParser() != null) { if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser()); this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch, Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,

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

@ -37,6 +37,7 @@ import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import jakarta.servlet.RequestDispatcher; import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.MappingMatch;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -137,6 +138,7 @@ import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver; import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer; import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver; import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
import org.springframework.web.testfixture.servlet.MockHttpServletMapping;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse; import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.testfixture.servlet.MockRequestDispatcher; import org.springframework.web.testfixture.servlet.MockRequestDispatcher;
@ -688,6 +690,7 @@ public class MvcNamespaceTests {
request.setRequestURI("/myapp/app/"); request.setRequestURI("/myapp/app/");
request.setContextPath("/myapp"); request.setContextPath("/myapp");
request.setServletPath("/app/"); request.setServletPath("/app/");
request.setHttpServletMapping(new MockHttpServletMapping("", "", "", MappingMatch.PATH));
chain = mapping2.getHandler(request); chain = mapping2.getHandler(request);
assertThat(chain.getInterceptorList().size()).isEqualTo(4); assertThat(chain.getInterceptorList().size()).isEqualTo(4);
assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue(); assertThat(chain.getInterceptorList().get(1) instanceof ConversionServiceExposingInterceptor).isTrue();

19
spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationIntegrationTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2022 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.
@ -181,6 +181,7 @@ public class DelegatingWebMvcConfigurationIntegrationTests {
this.context = webContext; this.context = webContext;
} }
@Configuration @Configuration
static class ViewControllerConfiguration implements WebMvcConfigurer { static class ViewControllerConfiguration implements WebMvcConfigurer {
@ -188,8 +189,16 @@ public class DelegatingWebMvcConfigurationIntegrationTests {
public void addViewControllers(ViewControllerRegistry registry) { public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test"); registry.addViewController("/test");
} }
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// tests need to check the "mvcPathMatcher" and "mvcUrlPathHelper" instances
configurer.setPatternParser(null);
}
} }
@Configuration @Configuration
static class ResourceHandlerConfiguration implements WebMvcConfigurer { static class ResourceHandlerConfiguration implements WebMvcConfigurer {
@ -197,5 +206,13 @@ public class DelegatingWebMvcConfigurationIntegrationTests {
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**"); registry.addResourceHandler("/resources/**");
} }
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// tests need to check the "mvcPathMatcher" and "mvcUrlPathHelper" instances
configurer.setPatternParser(null);
}
} }
} }

9
spring-webmvc/src/test/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMappingTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2022 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.
@ -37,8 +37,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/ */
public class BeanNameUrlHandlerMappingTests { public class BeanNameUrlHandlerMappingTests {
public static final String CONF = "/org/springframework/web/servlet/handler/map1.xml";
private ConfigurableWebApplicationContext wac; private ConfigurableWebApplicationContext wac;
@ -47,7 +45,7 @@ public class BeanNameUrlHandlerMappingTests {
MockServletContext sc = new MockServletContext(""); MockServletContext sc = new MockServletContext("");
wac = new XmlWebApplicationContext(); wac = new XmlWebApplicationContext();
wac.setServletContext(sc); wac.setServletContext(sc);
wac.setConfigLocations(new String[] {CONF}); wac.setConfigLocations("/org/springframework/web/servlet/handler/map1.xml");
wac.refresh(); wac.refresh();
} }
@ -55,7 +53,7 @@ public class BeanNameUrlHandlerMappingTests {
public void requestsWithoutHandlers() throws Exception { public void requestsWithoutHandlers() throws Exception {
HandlerMapping hm = (HandlerMapping) wac.getBean("handlerMapping"); HandlerMapping hm = (HandlerMapping) wac.getBean("handlerMapping");
MockHttpServletRequest req = new MockHttpServletRequest("GET", "/mypath/nonsense.html"); MockHttpServletRequest req = new MockHttpServletRequest("GET", "/myapp/mypath/nonsense.html");
req.setContextPath("/myapp"); req.setContextPath("/myapp");
Object h = hm.getHandler(req); Object h = hm.getHandler(req);
assertThat(h == null).as("Handler is null").isTrue(); assertThat(h == null).as("Handler is null").isTrue();
@ -121,6 +119,7 @@ public class BeanNameUrlHandlerMappingTests {
@Test @Test
public void requestsWithFullPaths() throws Exception { public void requestsWithFullPaths() throws Exception {
BeanNameUrlHandlerMapping hm = new BeanNameUrlHandlerMapping(); BeanNameUrlHandlerMapping hm = new BeanNameUrlHandlerMapping();
hm.setPatternParser(null); // the test targets AntPathPatcher-specific feature
hm.setAlwaysUseFullPath(true); hm.setAlwaysUseFullPath(true);
hm.setApplicationContext(wac); hm.setApplicationContext(wac);
Object bean = wac.getBean("godCtrl"); Object bean = wac.getBean("godCtrl");

8
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/AbstractServletHandlerMethodTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2022 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.
@ -29,7 +29,6 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.testfixture.servlet.MockServletConfig; import org.springframework.web.testfixture.servlet.MockServletConfig;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -97,9 +96,8 @@ public abstract class AbstractServletHandlerMethodTests {
} }
BeanDefinition mappingDef = wac.getBeanDefinition("handlerMapping"); BeanDefinition mappingDef = wac.getBeanDefinition("handlerMapping");
if (usePathPatterns && !mappingDef.hasAttribute("patternParser")) { if (!usePathPatterns) {
BeanDefinition parserDef = register("parser", PathPatternParser.class, wac); mappingDef.getPropertyValues().add("patternParser", null);
mappingDef.getPropertyValues().add("patternParser", parserDef);
} }
register("handlerAdapter", RequestMappingHandlerAdapter.class, wac); register("handlerAdapter", RequestMappingHandlerAdapter.class, wac);

1
spring-webmvc/src/test/resources/org/springframework/web/servlet/handler/map3.xml

@ -11,6 +11,7 @@
</bean> </bean>
<bean id="urlMapping2" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <bean id="urlMapping2" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="patternParser"><null/></property>
<property name="urlDecode"><value>true</value></property> <property name="urlDecode"><value>true</value></property>
<property name="mappings"><ref bean="mappings"/></property> <property name="mappings"><ref bean="mappings"/></property>
</bean> </bean>

42
src/docs/asciidoc/web/webmvc.adoc

@ -580,8 +580,8 @@ initialization parameters (`init-param` elements) to the Servlet declaration in
The Servlet API exposes the full request path as `requestURI` and further sub-divides it The Servlet API exposes the full request path as `requestURI` and further sub-divides it
into `contextPath`, `servletPath`, and `pathInfo` whose values vary depending on how a into `contextPath`, `servletPath`, and `pathInfo` whose values vary depending on how a
Servlet is mapped. From these inputs, Spring MVC needs to determine the lookup path to Servlet is mapped. From these inputs, Spring MVC needs to determine the lookup path to
use for handler mapping, which is the path within the mapping of the `DispatcherServlet` use for mapping handlers, which should exclude the `contextPath` and any `servletMapping`
itself, excluding the `contextPath` and any `servletMapping` prefix, if present. prefix, if applicable.
The `servletPath` and `pathInfo` are decoded and that makes them impossible to compare The `servletPath` and `pathInfo` are decoded and that makes them impossible to compare
directly to the full `requestURI` in order to derive the lookupPath and that makes it directly to the full `requestURI` in order to derive the lookupPath and that makes it
@ -611,15 +611,17 @@ encoded path which may not always work well. Furthermore, sometimes the
`DispatcherServlet` needs to share the URL space with another Servlet and may need to `DispatcherServlet` needs to share the URL space with another Servlet and may need to
be mapped by prefix. be mapped by prefix.
The above issues can be addressed more comprehensively by switching from `PathMatcher` to The above issues are addressed when using `PathPatternParser` and parsed patterns, as
the parsed `PathPattern` available in 5.3 or higher, see an alternative to String path matching with `AntPathMatcher`. The `PathPatternParser` has
<<mvc-ann-requestmapping-pattern-comparison>>. Unlike `AntPathMatcher` which needs been available for use in Spring MVC from version 5.3, and is enabled by default from
either the lookup path decoded or the controller mapping encoded, a parsed `PathPattern` version 6.0. Unlike `AntPathMatcher` which needs either the lookup path decoded or the
matches to a parsed representation of the path called `RequestPath`, one path segment controller mapping encoded, a parsed `PathPattern` matches to a parsed representation
at a time. This allows decoding and sanitizing path segment values individually without of the path called `RequestPath`, one path segment at a time. This allows decoding and
the risk of altering the structure of the path. Parsed `PathPattern` also supports sanitizing path segment values individually without the risk of altering the structure
the use of `servletPath` prefix mapping as long as the prefix is kept simple and does of the path. Parsed `PathPattern` also supports the use of `servletPath` prefix mapping
not have any characters that need to be encoded. as long as a Servlet path mapping is used and the prefix is kept simple, i.e. it has no
encoded characters. For pattern syntax details and comparison, see
<<mvc-ann-requestmapping-pattern-comparison>>.
@ -1617,11 +1619,11 @@ filesystem, and other locations. It is less efficient and the String path input
challenge for dealing effectively with encoding and other issues with URLs. challenge for dealing effectively with encoding and other issues with URLs.
`PathPattern` is the recommended solution for web applications and it is the only choice in `PathPattern` is the recommended solution for web applications and it is the only choice in
Spring WebFlux. Prior to version 5.3, `AntPathMatcher` was the only choice in Spring MVC Spring WebFlux. It was enabled for use in Spring MVC from version 5.3 and is enabled by
and continues to be the default. However `PathPattern` can be enabled in the default from version 6.0. See <<mvc-config-path-matching, MVC config>> for
<<mvc-config-path-matching, MVC config>>. customizations of path matching options.
`PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition it also `PathPattern` supports the same pattern syntax as `AntPathMatcher`. In addition, it also
supports the capturing pattern, e.g. `+{*spring}+`, for matching 0 or more path segments supports the capturing pattern, e.g. `+{*spring}+`, for matching 0 or more path segments
at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple at the end of a path. `PathPattern` also restricts the use of `+**+` for matching multiple
path segments such that it's only allowed at the end of a pattern. This eliminates many path segments such that it's only allowed at the end of a pattern. This eliminates many
@ -5997,9 +5999,7 @@ The following example shows how to customize path matching in Java configuration
@Override @Override
public void configurePathMatch(PathMatchConfigurer configurer) { public void configurePathMatch(PathMatchConfigurer configurer) {
configurer configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
.setPatternParser(new PathPatternParser())
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
} }
private PathPatternParser patternParser() { private PathPatternParser patternParser() {
@ -6015,9 +6015,7 @@ The following example shows how to customize path matching in Java configuration
class WebConfig : WebMvcConfigurer { class WebConfig : WebMvcConfigurer {
override fun configurePathMatch(configurer: PathMatchConfigurer) { override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
.setPatternParser(patternParser)
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
} }
fun patternParser(): PathPatternParser { fun patternParser(): PathPatternParser {
@ -6026,7 +6024,7 @@ The following example shows how to customize path matching in Java configuration
} }
---- ----
The following example shows how to achieve the same configuration in XML: The following example shows how to customize path matching in XML configuration:
[source,xml,indent=0,subs="verbatim,quotes"] [source,xml,indent=0,subs="verbatim,quotes"]
---- ----

Loading…
Cancel
Save