Improve PathMatcher/PatternParser XML configuration

Prior to this commit, the MVC namespace for the XML Spring configuration
model would use the `PathMatcher` bean instance when provided like this:

```
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"/>
<mvc:annotation-driven>
  <mvc:path-matching path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<mvc:resources mapping="/resources/**" location="classpath:/static/"/>
```

With this configuration, the handler mapping for annotated controller
would use the given `AntPathMatcher` instance but the handler mapping
for resources would still use the default, which is `PathPatternParser`
since 6.0.

This commit ensures that when a custom `path-matcher` is defined, it's
consistently used for all MVC handler mappings as an alias to the
well-known bean name. This allows to use `AntPathMatcher` consistently
while working on a migration path to `PathPatternParser`

This commit also adds a new XML attribute to the path matching
configuration that makes it possible to use a custom `PathPatternParser`
instance:

```
<bean id="patternParser" class="org.springframework.web.util.pattern.PathPatternParser"/>
<mvc:annotation-driven>
  <mvc:path-matching pattern-parser="patternParser"/>
</mvc:annotation-driven>
```

Closes gh-34064
This commit is contained in:
Brian Clozel 2024-12-17 10:13:56 +01:00
parent b0fcde9aa3
commit 1a34b0dd87
9 changed files with 176 additions and 21 deletions

View File

@ -407,16 +407,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition handlerMappingDef, Element element, ParserContext context) { RootBeanDefinition handlerMappingDef, Element element, ParserContext context) {
Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching"); Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
Object source = context.extractSource(element);
if (pathMatchingElement != null) { if (pathMatchingElement != null) {
Object source = context.extractSource(element);
if (pathMatchingElement.hasAttribute("trailing-slash")) { if (pathMatchingElement.hasAttribute("trailing-slash")) {
boolean useTrailingSlashMatch = Boolean.parseBoolean(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; boolean preferPathMatcher = false;
if (pathMatchingElement.hasAttribute("suffix-pattern")) { if (pathMatchingElement.hasAttribute("suffix-pattern")) {
boolean useSuffixPatternMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("suffix-pattern")); boolean useSuffixPatternMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("suffix-pattern"));
handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch); handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch);
@ -441,12 +438,20 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher")); pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher"));
preferPathMatcher = true; preferPathMatcher = true;
} }
pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, context, source);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
if (preferPathMatcher) { if (preferPathMatcher) {
pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, context, source);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
handlerMappingDef.getPropertyValues().add("patternParser", null); handlerMappingDef.getPropertyValues().add("patternParser", null);
} }
else if (pathMatchingElement.hasAttribute("pattern-parser")) {
RuntimeBeanReference patternParserRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("pattern-parser"));
patternParserRef = MvcNamespaceUtils.registerPatternParser(patternParserRef, context, source);
handlerMappingDef.getPropertyValues().add("patternParser", patternParserRef);
}
}
else {
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, context, source);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 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.
@ -28,9 +28,11 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
@ -39,6 +41,7 @@ import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.support.SessionFlashMapManager; import org.springframework.web.servlet.support.SessionFlashMapManager;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
/** /**
* Convenience methods for use in MVC namespace BeanDefinitionParsers. * Convenience methods for use in MVC namespace BeanDefinitionParsers.
@ -64,6 +67,8 @@ public abstract class MvcNamespaceUtils {
private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher"; private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher";
private static final String PATTERN_PARSER_BEAN_NAME = "mvcPatternParser";
private static final String CORS_CONFIGURATION_BEAN_NAME = "mvcCorsConfigurations"; private static final String CORS_CONFIGURATION_BEAN_NAME = "mvcCorsConfigurations";
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"; private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
@ -105,6 +110,18 @@ public abstract class MvcNamespaceUtils {
return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME); return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME);
} }
/**
* Return the {@link PathMatcher} bean definition if it has been registered
* in the context as an alias with its well-known name, or {@code null}.
*/
@Nullable
static RuntimeBeanReference getCustomPathMatcher(ParserContext context) {
if(context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
}
return null;
}
/** /**
* Adds an alias to an existing well-known name or registers a new instance of a {@link PathMatcher} * 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. * under that well-known name, unless already registered.
@ -117,6 +134,9 @@ public abstract class MvcNamespaceUtils {
if (context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) { if (context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
context.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME); context.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
} }
if (context.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
context.getRegistry().removeBeanDefinition(PATH_MATCHER_BEAN_NAME);
}
context.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME); context.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
} }
else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) && else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
@ -130,6 +150,60 @@ public abstract class MvcNamespaceUtils {
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME); return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
} }
/**
* Return the {@link PathPatternParser} bean definition if it has been registered
* in the context as an alias with its well-known name, or {@code null}.
*/
@Nullable
static RuntimeBeanReference getCustomPatternParser(ParserContext context) {
if (context.getRegistry().isAlias(PATTERN_PARSER_BEAN_NAME)) {
return new RuntimeBeanReference(PATTERN_PARSER_BEAN_NAME);
}
return null;
}
/**
* Adds an alias to an existing well-known name or registers a new instance of a {@link PathPatternParser}
* under that well-known name, unless already registered.
* @return a RuntimeBeanReference to this {@link PathPatternParser} instance
*/
public static RuntimeBeanReference registerPatternParser(@Nullable RuntimeBeanReference patternParserRef,
ParserContext context, @Nullable Object source) {
if (patternParserRef != null) {
if (context.getRegistry().isAlias(PATTERN_PARSER_BEAN_NAME)) {
context.getRegistry().removeAlias(PATTERN_PARSER_BEAN_NAME);
}
context.getRegistry().registerAlias(patternParserRef.getBeanName(), PATTERN_PARSER_BEAN_NAME);
}
else if (!context.getRegistry().isAlias(PATTERN_PARSER_BEAN_NAME) &&
!context.getRegistry().containsBeanDefinition(PATTERN_PARSER_BEAN_NAME)) {
RootBeanDefinition pathMatcherDef = new RootBeanDefinition(PathPatternParser.class);
pathMatcherDef.setSource(source);
pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(PATTERN_PARSER_BEAN_NAME, pathMatcherDef);
context.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATTERN_PARSER_BEAN_NAME));
}
return new RuntimeBeanReference(PATTERN_PARSER_BEAN_NAME);
}
static void configurePathMatching(RootBeanDefinition handlerMappingDef, ParserContext context, @Nullable Object source) {
Assert.isTrue(AbstractHandlerMapping.class.isAssignableFrom(handlerMappingDef.getBeanClass()),
() -> "Handler mapping type [" + handlerMappingDef.getTargetType() + "] not supported");
RuntimeBeanReference customPathMatcherRef = MvcNamespaceUtils.getCustomPathMatcher(context);
RuntimeBeanReference customPatternParserRef = MvcNamespaceUtils.getCustomPatternParser(context);
if (customPathMatcherRef != null) {
handlerMappingDef.getPropertyValues().add("pathMatcher", customPathMatcherRef)
.add("patternParser", null);
}
else if (customPatternParserRef != null) {
handlerMappingDef.getPropertyValues().add("patternParser", customPatternParserRef);
}
else {
handlerMappingDef.getPropertyValues().add("pathMatcher",
MvcNamespaceUtils.registerPathMatcher(null, context, source));
}
}
/** /**
* Registers an {@link HttpRequestHandlerAdapter} under a well-known * Registers an {@link HttpRequestHandlerAdapter} under a well-known
* name unless already registered. * name unless already registered.
@ -142,6 +216,7 @@ public abstract class MvcNamespaceUtils {
mappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport mappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source); RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
mappingDef.getPropertyValues().add("corsConfigurations", corsRef); mappingDef.getPropertyValues().add("corsConfigurations", corsRef);
configurePathMatching(mappingDef, context, source);
context.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, mappingDef); context.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, mappingDef);
context.registerComponent(new BeanComponentDefinition(mappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME)); context.registerComponent(new BeanComponentDefinition(mappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2024 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.
@ -92,7 +92,6 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
registerUrlProvider(context, source); registerUrlProvider(context, source);
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, context, source);
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, context, source); RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, context, source);
String resourceHandlerName = registerResourceHandler(context, element, pathHelperRef, source); String resourceHandlerName = registerResourceHandler(context, element, pathHelperRef, source);
@ -111,8 +110,8 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
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).add("urlPathHelper", pathHelperRef);
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef).add("urlPathHelper", pathHelperRef); MvcNamespaceUtils.configurePathMatching(handlerMappingDef, context, source);
String orderValue = element.getAttribute("order"); String orderValue = 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-2022 the original author or authors. * Copyright 2002-2024 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.
@ -123,7 +123,7 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
beanDef.setSource(source); beanDef.setSource(source);
beanDef.getPropertyValues().add("order", "1"); beanDef.getPropertyValues().add("order", "1");
beanDef.getPropertyValues().add("pathMatcher", MvcNamespaceUtils.registerPathMatcher(null, context, source)); MvcNamespaceUtils.configurePathMatching(beanDef, context, source);
beanDef.getPropertyValues().add("urlPathHelper", MvcNamespaceUtils.registerUrlPathHelper(null, context, source)); beanDef.getPropertyValues().add("urlPathHelper", MvcNamespaceUtils.registerUrlPathHelper(null, context, source));
RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source); RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
beanDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef); beanDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);

View File

@ -89,7 +89,14 @@
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The bean name of the PathMatcher implementation to use for matching URL paths against registered URL patterns. The bean name of the PathMatcher implementation to use for matching URL paths against registered URL patterns.
Default is AntPathMatcher. If no bean is provided, the PathPatternParser will be used instead.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="pattern-parser" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name of the PathPatternParser instance to use for matching URL paths against registered URL patterns.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>

View File

@ -89,6 +89,7 @@ class AnnotationDrivenBeanDefinitionParserTests {
assertThat(hm.useRegisteredSuffixPatternMatch()).isTrue(); assertThat(hm.useRegisteredSuffixPatternMatch()).isTrue();
assertThat(hm.getUrlPathHelper()).isInstanceOf(TestPathHelper.class); assertThat(hm.getUrlPathHelper()).isInstanceOf(TestPathHelper.class);
assertThat(hm.getPathMatcher()).isInstanceOf(TestPathMatcher.class); assertThat(hm.getPathMatcher()).isInstanceOf(TestPathMatcher.class);
assertThat(hm.getPatternParser()).isNull();
List<String> fileExtensions = hm.getContentNegotiationManager().getAllFileExtensions(); List<String> fileExtensions = hm.getContentNegotiationManager().getAllFileExtensions();
assertThat(fileExtensions).containsExactly("xml"); assertThat(fileExtensions).containsExactly("xml");
} }

View File

@ -64,6 +64,7 @@ import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConve
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
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.util.PathMatcher; 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;
@ -145,6 +146,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import org.springframework.web.testfixture.servlet.MockRequestDispatcher; import org.springframework.web.testfixture.servlet.MockRequestDispatcher;
import org.springframework.web.testfixture.servlet.MockServletContext; import org.springframework.web.testfixture.servlet.MockServletContext;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -202,6 +204,8 @@ public class MvcNamespaceTests {
assertThat(mapping).isNotNull(); assertThat(mapping).isNotNull();
assertThat(mapping.getOrder()).isEqualTo(0); assertThat(mapping.getOrder()).isEqualTo(0);
assertThat(mapping.getUrlPathHelper().shouldRemoveSemicolonContent()).isTrue(); assertThat(mapping.getUrlPathHelper().shouldRemoveSemicolonContent()).isTrue();
assertThat(mapping.getPathMatcher()).isEqualTo(appContext.getBean("mvcPathMatcher"));
assertThat(mapping.getPatternParser()).isNotNull();
mapping.setDefaultHandler(handlerMethod); mapping.setDefaultHandler(handlerMethod);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json");
@ -392,6 +396,8 @@ public class MvcNamespaceTests {
SimpleUrlHandlerMapping resourceMapping = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping resourceMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertThat(resourceMapping).isNotNull(); assertThat(resourceMapping).isNotNull();
assertThat(resourceMapping.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE - 1); assertThat(resourceMapping.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE - 1);
assertThat(resourceMapping.getPathMatcher()).isNotNull();
assertThat(resourceMapping.getPatternParser()).isNotNull();
BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class); BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
assertThat(beanNameMapping).isNotNull(); assertThat(beanNameMapping).isNotNull();
@ -423,6 +429,31 @@ public class MvcNamespaceTests {
.isInstanceOf(NoResourceFoundException.class); .isInstanceOf(NoResourceFoundException.class);
} }
@Test
void testUseDeprecatedPathMatcher() throws Exception {
loadBeanDefinitions("mvc-config-deprecated-path-matcher.xml");
Map<String, AbstractHandlerMapping> handlerMappings = appContext.getBeansOfType(AbstractHandlerMapping.class);
AntPathMatcher mvcPathMatcher = appContext.getBean("pathMatcher", AntPathMatcher.class);
assertThat(handlerMappings).hasSize(4);
handlerMappings.forEach((name, hm) -> {
assertThat(hm.getPathMatcher()).as("path matcher for %s", name).isEqualTo(mvcPathMatcher);
assertThat(hm.getPatternParser()).as("pattern parser for %s", name).isNull();
});
}
@Test
void testUsePathPatternParser() throws Exception {
loadBeanDefinitions("mvc-config-custom-pattern-parser.xml");
PathPatternParser patternParser = appContext.getBean("patternParser", PathPatternParser.class);
Map<String, AbstractHandlerMapping> handlerMappings = appContext.getBeansOfType(AbstractHandlerMapping.class);
assertThat(handlerMappings).hasSize(4);
handlerMappings.forEach((name, hm) -> {
assertThat(hm.getPathMatcher()).as("path matcher for %s", name).isNotNull();
assertThat(hm.getPatternParser()).as("pattern parser for %s", name).isEqualTo(patternParser);
});
}
@Test @Test
void testResourcesWithOptionalAttributes() { void testResourcesWithOptionalAttributes() {
loadBeanDefinitions("mvc-config-resources-optional-attrs.xml"); loadBeanDefinitions("mvc-config-resources-optional-attrs.xml");
@ -601,6 +632,9 @@ public class MvcNamespaceTests {
assertThat(beanNameMapping).isNotNull(); assertThat(beanNameMapping).isNotNull();
assertThat(beanNameMapping.getOrder()).isEqualTo(2); assertThat(beanNameMapping.getOrder()).isEqualTo(2);
assertThat(beanNameMapping.getPathMatcher()).isNotNull();
assertThat(beanNameMapping.getPatternParser()).isNotNull();
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("GET"); request.setMethod("GET");
@ -896,11 +930,11 @@ public class MvcNamespaceTests {
assertThat(viewController.getUrlPathHelper().getClass()).isEqualTo(TestPathHelper.class); assertThat(viewController.getUrlPathHelper().getClass()).isEqualTo(TestPathHelper.class);
assertThat(viewController.getPathMatcher().getClass()).isEqualTo(TestPathMatcher.class); assertThat(viewController.getPathMatcher().getClass()).isEqualTo(TestPathMatcher.class);
for (SimpleUrlHandlerMapping handlerMapping : appContext.getBeansOfType(SimpleUrlHandlerMapping.class).values()) { appContext.getBeansOfType(SimpleUrlHandlerMapping.class).forEach((name, handlerMapping) -> {
assertThat(handlerMapping).isNotNull(); assertThat(handlerMapping).isNotNull();
assertThat(handlerMapping.getUrlPathHelper().getClass()).isEqualTo(TestPathHelper.class); assertThat(handlerMapping.getUrlPathHelper().getClass()).as("path helper for %s", name).isEqualTo(TestPathHelper.class);
assertThat(handlerMapping.getPathMatcher().getClass()).isEqualTo(TestPathMatcher.class); assertThat(handlerMapping.getPathMatcher().getClass()).as("path matcher for %s", name).isEqualTo(TestPathMatcher.class);
} });
} }
@Test @Test
@ -910,7 +944,7 @@ public class MvcNamespaceTests {
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class); String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
assertThat(beanNames).hasSize(2); assertThat(beanNames).hasSize(2);
for (String beanName : beanNames) { for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName); AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) appContext.getBean(beanName);
assertThat(handlerMapping).isNotNull(); assertThat(handlerMapping).isNotNull();
DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping); DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping);
Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource) accessor Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource) accessor
@ -935,7 +969,7 @@ public class MvcNamespaceTests {
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class); String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
assertThat(beanNames).hasSize(2); assertThat(beanNames).hasSize(2);
for (String beanName : beanNames) { for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName); AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) appContext.getBean(beanName);
assertThat(handlerMapping).isNotNull(); assertThat(handlerMapping).isNotNull();
DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping); DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping);
Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource) accessor Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource) accessor

View File

@ -0,0 +1,17 @@
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<bean id="patternParser" class="org.springframework.web.util.pattern.PathPatternParser"/>
<mvc:annotation-driven>
<mvc:path-matching pattern-parser="patternParser"/>
</mvc:annotation-driven>
<mvc:view-controller path="/foo"/>
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/"/>
</beans>

View File

@ -0,0 +1,17 @@
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"/>
<mvc:annotation-driven>
<mvc:path-matching path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<mvc:view-controller path="/foo"/>
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/"/>
</beans>