Skip to content

Commit 4b45338

Browse files
committed
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-34102 See gh-34064
1 parent 95003e3 commit 4b45338

File tree

9 files changed

+176
-21
lines changed

9 files changed

+176
-21
lines changed

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

+12-7
Original file line numberDiff line numberDiff line change
@@ -401,16 +401,13 @@ private void configurePathMatchingProperties(
401401
RootBeanDefinition handlerMappingDef, Element element, ParserContext context) {
402402

403403
Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
404+
Object source = context.extractSource(element);
404405
if (pathMatchingElement != null) {
405-
Object source = context.extractSource(element);
406-
407406
if (pathMatchingElement.hasAttribute("trailing-slash")) {
408407
boolean useTrailingSlashMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("trailing-slash"));
409408
handlerMappingDef.getPropertyValues().add("useTrailingSlashMatch", useTrailingSlashMatch);
410409
}
411-
412410
boolean preferPathMatcher = false;
413-
414411
if (pathMatchingElement.hasAttribute("suffix-pattern")) {
415412
boolean useSuffixPatternMatch = Boolean.parseBoolean(pathMatchingElement.getAttribute("suffix-pattern"));
416413
handlerMappingDef.getPropertyValues().add("useSuffixPatternMatch", useSuffixPatternMatch);
@@ -435,12 +432,20 @@ private void configurePathMatchingProperties(
435432
pathMatcherRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("path-matcher"));
436433
preferPathMatcher = true;
437434
}
438-
pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, context, source);
439-
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
440-
441435
if (preferPathMatcher) {
436+
pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(pathMatcherRef, context, source);
437+
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
442438
handlerMappingDef.getPropertyValues().add("patternParser", null);
443439
}
440+
else if (pathMatchingElement.hasAttribute("pattern-parser")) {
441+
RuntimeBeanReference patternParserRef = new RuntimeBeanReference(pathMatchingElement.getAttribute("pattern-parser"));
442+
patternParserRef = MvcNamespaceUtils.registerPatternParser(patternParserRef, context, source);
443+
handlerMappingDef.getPropertyValues().add("patternParser", patternParserRef);
444+
}
445+
}
446+
else {
447+
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, context, source);
448+
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
444449
}
445450

446451
}

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

+76-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,9 +28,11 @@
2828
import org.springframework.beans.factory.xml.ParserContext;
2929
import org.springframework.lang.Nullable;
3030
import org.springframework.util.AntPathMatcher;
31+
import org.springframework.util.Assert;
3132
import org.springframework.util.PathMatcher;
3233
import org.springframework.web.cors.CorsConfiguration;
3334
import org.springframework.web.servlet.DispatcherServlet;
35+
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
3436
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
3537
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
3638
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
@@ -39,6 +41,7 @@
3941
import org.springframework.web.servlet.support.SessionFlashMapManager;
4042
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
4143
import org.springframework.web.util.UrlPathHelper;
44+
import org.springframework.web.util.pattern.PathPatternParser;
4245

4346
/**
4447
* Convenience methods for use in MVC namespace BeanDefinitionParsers.
@@ -64,6 +67,8 @@ public abstract class MvcNamespaceUtils {
6467

6568
private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher";
6669

70+
private static final String PATTERN_PARSER_BEAN_NAME = "mvcPatternParser";
71+
6772
private static final String CORS_CONFIGURATION_BEAN_NAME = "mvcCorsConfigurations";
6873

6974
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
@@ -105,6 +110,18 @@ else if (!context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
105110
return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME);
106111
}
107112

113+
/**
114+
* Return the {@link PathMatcher} bean definition if it has been registered
115+
* in the context as an alias with its well-known name, or {@code null}.
116+
*/
117+
@Nullable
118+
static RuntimeBeanReference getCustomPathMatcher(ParserContext context) {
119+
if(context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
120+
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
121+
}
122+
return null;
123+
}
124+
108125
/**
109126
* Adds an alias to an existing well-known name or registers a new instance of a {@link PathMatcher}
110127
* under that well-known name, unless already registered.
@@ -117,6 +134,9 @@ public static RuntimeBeanReference registerPathMatcher(@Nullable RuntimeBeanRefe
117134
if (context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
118135
context.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
119136
}
137+
if (context.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
138+
context.getRegistry().removeBeanDefinition(PATH_MATCHER_BEAN_NAME);
139+
}
120140
context.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
121141
}
122142
else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
@@ -130,6 +150,60 @@ else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
130150
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
131151
}
132152

153+
/**
154+
* Return the {@link PathPatternParser} bean definition if it has been registered
155+
* in the context as an alias with its well-known name, or {@code null}.
156+
*/
157+
@Nullable
158+
static RuntimeBeanReference getCustomPatternParser(ParserContext context) {
159+
if (context.getRegistry().isAlias(PATTERN_PARSER_BEAN_NAME)) {
160+
return new RuntimeBeanReference(PATTERN_PARSER_BEAN_NAME);
161+
}
162+
return null;
163+
}
164+
165+
/**
166+
* Adds an alias to an existing well-known name or registers a new instance of a {@link PathPatternParser}
167+
* under that well-known name, unless already registered.
168+
* @return a RuntimeBeanReference to this {@link PathPatternParser} instance
169+
*/
170+
public static RuntimeBeanReference registerPatternParser(@Nullable RuntimeBeanReference patternParserRef,
171+
ParserContext context, @Nullable Object source) {
172+
if (patternParserRef != null) {
173+
if (context.getRegistry().isAlias(PATTERN_PARSER_BEAN_NAME)) {
174+
context.getRegistry().removeAlias(PATTERN_PARSER_BEAN_NAME);
175+
}
176+
context.getRegistry().registerAlias(patternParserRef.getBeanName(), PATTERN_PARSER_BEAN_NAME);
177+
}
178+
else if (!context.getRegistry().isAlias(PATTERN_PARSER_BEAN_NAME) &&
179+
!context.getRegistry().containsBeanDefinition(PATTERN_PARSER_BEAN_NAME)) {
180+
RootBeanDefinition pathMatcherDef = new RootBeanDefinition(PathPatternParser.class);
181+
pathMatcherDef.setSource(source);
182+
pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
183+
context.getRegistry().registerBeanDefinition(PATTERN_PARSER_BEAN_NAME, pathMatcherDef);
184+
context.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATTERN_PARSER_BEAN_NAME));
185+
}
186+
return new RuntimeBeanReference(PATTERN_PARSER_BEAN_NAME);
187+
}
188+
189+
static void configurePathMatching(RootBeanDefinition handlerMappingDef, ParserContext context, @Nullable Object source) {
190+
Assert.isTrue(AbstractHandlerMapping.class.isAssignableFrom(handlerMappingDef.getBeanClass()),
191+
() -> "Handler mapping type [" + handlerMappingDef.getTargetType() + "] not supported");
192+
RuntimeBeanReference customPathMatcherRef = MvcNamespaceUtils.getCustomPathMatcher(context);
193+
RuntimeBeanReference customPatternParserRef = MvcNamespaceUtils.getCustomPatternParser(context);
194+
if (customPathMatcherRef != null) {
195+
handlerMappingDef.getPropertyValues().add("pathMatcher", customPathMatcherRef)
196+
.add("patternParser", null);
197+
}
198+
else if (customPatternParserRef != null) {
199+
handlerMappingDef.getPropertyValues().add("patternParser", customPatternParserRef);
200+
}
201+
else {
202+
handlerMappingDef.getPropertyValues().add("pathMatcher",
203+
MvcNamespaceUtils.registerPathMatcher(null, context, source));
204+
}
205+
}
206+
133207
/**
134208
* Registers an {@link HttpRequestHandlerAdapter} under a well-known
135209
* name unless already registered.
@@ -142,6 +216,7 @@ private static void registerBeanNameUrlHandlerMapping(ParserContext context, @Nu
142216
mappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
143217
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
144218
mappingDef.getPropertyValues().add("corsConfigurations", corsRef);
219+
configurePathMatching(mappingDef, context, source);
145220
context.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, mappingDef);
146221
context.registerComponent(new BeanComponentDefinition(mappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
147222
}

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -92,7 +92,6 @@ public BeanDefinition parse(Element element, ParserContext context) {
9292

9393
registerUrlProvider(context, source);
9494

95-
RuntimeBeanReference pathMatcherRef = MvcNamespaceUtils.registerPathMatcher(null, context, source);
9695
RuntimeBeanReference pathHelperRef = MvcNamespaceUtils.registerUrlPathHelper(null, context, source);
9796

9897
String resourceHandlerName = registerResourceHandler(context, element, pathHelperRef, source);
@@ -111,8 +110,8 @@ public BeanDefinition parse(Element element, ParserContext context) {
111110
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
112111
handlerMappingDef.setSource(source);
113112
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
114-
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
115-
handlerMappingDef.getPropertyValues().add("pathMatcher", pathMatcherRef).add("urlPathHelper", pathHelperRef);
113+
handlerMappingDef.getPropertyValues().add("urlMap", urlMap).add("urlPathHelper", pathHelperRef);
114+
MvcNamespaceUtils.configurePathMatching(handlerMappingDef, context, source);
116115

117116
String orderValue = element.getAttribute("order");
118117
// Use a default of near-lowest precedence, still allowing for even lower precedence in other mappings

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -123,7 +123,7 @@ private BeanDefinition registerHandlerMapping(ParserContext context, @Nullable O
123123

124124
beanDef.setSource(source);
125125
beanDef.getPropertyValues().add("order", "1");
126-
beanDef.getPropertyValues().add("pathMatcher", MvcNamespaceUtils.registerPathMatcher(null, context, source));
126+
MvcNamespaceUtils.configurePathMatching(beanDef, context, source);
127127
beanDef.getPropertyValues().add("urlPathHelper", MvcNamespaceUtils.registerUrlPathHelper(null, context, source));
128128
RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
129129
beanDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc.xsd

+8-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@
8989
<xsd:annotation>
9090
<xsd:documentation><![CDATA[
9191
The bean name of the PathMatcher implementation to use for matching URL paths against registered URL patterns.
92-
Default is AntPathMatcher.
92+
If no bean is provided, the PathPatternParser will be used instead.
93+
]]></xsd:documentation>
94+
</xsd:annotation>
95+
</xsd:attribute>
96+
<xsd:attribute name="pattern-parser" type="xsd:string">
97+
<xsd:annotation>
98+
<xsd:documentation><![CDATA[
99+
The bean name of the PathPatternParser instance to use for matching URL paths against registered URL patterns.
93100
]]></xsd:documentation>
94101
</xsd:annotation>
95102
</xsd:attribute>

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

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public void testPathMatchingConfiguration() {
8989
assertThat(hm.useRegisteredSuffixPatternMatch()).isTrue();
9090
assertThat(hm.getUrlPathHelper()).isInstanceOf(TestPathHelper.class);
9191
assertThat(hm.getPathMatcher()).isInstanceOf(TestPathMatcher.class);
92+
assertThat(hm.getPatternParser()).isNull();
9293
List<String> fileExtensions = hm.getContentNegotiationManager().getAllFileExtensions();
9394
assertThat(fileExtensions).containsExactly("xml");
9495
}

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

+40-6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.springframework.lang.Nullable;
6565
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
6666
import org.springframework.stereotype.Controller;
67+
import org.springframework.util.AntPathMatcher;
6768
import org.springframework.util.PathMatcher;
6869
import org.springframework.validation.BindingResult;
6970
import org.springframework.validation.Errors;
@@ -145,6 +146,7 @@
145146
import org.springframework.web.testfixture.servlet.MockRequestDispatcher;
146147
import org.springframework.web.testfixture.servlet.MockServletContext;
147148
import org.springframework.web.util.UrlPathHelper;
149+
import org.springframework.web.util.pattern.PathPatternParser;
148150

149151
import static org.assertj.core.api.Assertions.assertThat;
150152
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -202,6 +204,8 @@ void testDefaultConfig() throws Exception {
202204
assertThat(mapping).isNotNull();
203205
assertThat(mapping.getOrder()).isEqualTo(0);
204206
assertThat(mapping.getUrlPathHelper().shouldRemoveSemicolonContent()).isTrue();
207+
assertThat(mapping.getPathMatcher()).isEqualTo(appContext.getBean("mvcPathMatcher"));
208+
assertThat(mapping.getPatternParser()).isNotNull();
205209
mapping.setDefaultHandler(handlerMethod);
206210

207211
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.json");
@@ -392,6 +396,8 @@ void testResources() throws Exception {
392396
SimpleUrlHandlerMapping resourceMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
393397
assertThat(resourceMapping).isNotNull();
394398
assertThat(resourceMapping.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE - 1);
399+
assertThat(resourceMapping.getPathMatcher()).isNotNull();
400+
assertThat(resourceMapping.getPatternParser()).isNotNull();
395401

396402
BeanNameUrlHandlerMapping beanNameMapping = appContext.getBean(BeanNameUrlHandlerMapping.class);
397403
assertThat(beanNameMapping).isNotNull();
@@ -423,6 +429,31 @@ void testResources() throws Exception {
423429
.isInstanceOf(NoResourceFoundException.class);
424430
}
425431

432+
@Test
433+
void testUseDeprecatedPathMatcher() throws Exception {
434+
loadBeanDefinitions("mvc-config-deprecated-path-matcher.xml");
435+
Map<String, AbstractHandlerMapping> handlerMappings = appContext.getBeansOfType(AbstractHandlerMapping.class);
436+
AntPathMatcher mvcPathMatcher = appContext.getBean("pathMatcher", AntPathMatcher.class);
437+
assertThat(handlerMappings).hasSize(4);
438+
handlerMappings.forEach((name, hm) -> {
439+
assertThat(hm.getPathMatcher()).as("path matcher for %s", name).isEqualTo(mvcPathMatcher);
440+
assertThat(hm.getPatternParser()).as("pattern parser for %s", name).isNull();
441+
});
442+
}
443+
444+
@Test
445+
void testUsePathPatternParser() throws Exception {
446+
loadBeanDefinitions("mvc-config-custom-pattern-parser.xml");
447+
448+
PathPatternParser patternParser = appContext.getBean("patternParser", PathPatternParser.class);
449+
Map<String, AbstractHandlerMapping> handlerMappings = appContext.getBeansOfType(AbstractHandlerMapping.class);
450+
assertThat(handlerMappings).hasSize(4);
451+
handlerMappings.forEach((name, hm) -> {
452+
assertThat(hm.getPathMatcher()).as("path matcher for %s", name).isNotNull();
453+
assertThat(hm.getPatternParser()).as("pattern parser for %s", name).isEqualTo(patternParser);
454+
});
455+
}
456+
426457
@Test
427458
void testResourcesWithOptionalAttributes() {
428459
loadBeanDefinitions("mvc-config-resources-optional-attrs.xml");
@@ -600,6 +631,9 @@ void testViewControllers() throws Exception {
600631
assertThat(beanNameMapping).isNotNull();
601632
assertThat(beanNameMapping.getOrder()).isEqualTo(2);
602633

634+
assertThat(beanNameMapping.getPathMatcher()).isNotNull();
635+
assertThat(beanNameMapping.getPatternParser()).isNotNull();
636+
603637
MockHttpServletRequest request = new MockHttpServletRequest();
604638
request.setMethod("GET");
605639

@@ -895,11 +929,11 @@ void testPathMatchingHandlerMappings() {
895929
assertThat(viewController.getUrlPathHelper().getClass()).isEqualTo(TestPathHelper.class);
896930
assertThat(viewController.getPathMatcher().getClass()).isEqualTo(TestPathMatcher.class);
897931

898-
for (SimpleUrlHandlerMapping handlerMapping : appContext.getBeansOfType(SimpleUrlHandlerMapping.class).values()) {
932+
appContext.getBeansOfType(SimpleUrlHandlerMapping.class).forEach((name, handlerMapping) -> {
899933
assertThat(handlerMapping).isNotNull();
900-
assertThat(handlerMapping.getUrlPathHelper().getClass()).isEqualTo(TestPathHelper.class);
901-
assertThat(handlerMapping.getPathMatcher().getClass()).isEqualTo(TestPathMatcher.class);
902-
}
934+
assertThat(handlerMapping.getUrlPathHelper().getClass()).as("path helper for %s", name).isEqualTo(TestPathHelper.class);
935+
assertThat(handlerMapping.getPathMatcher().getClass()).as("path matcher for %s", name).isEqualTo(TestPathMatcher.class);
936+
});
903937
}
904938

905939
@Test
@@ -909,7 +943,7 @@ void testCorsMinimal() {
909943
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
910944
assertThat(beanNames).hasSize(2);
911945
for (String beanName : beanNames) {
912-
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
946+
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) appContext.getBean(beanName);
913947
assertThat(handlerMapping).isNotNull();
914948
DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping);
915949
Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource) accessor
@@ -934,7 +968,7 @@ void testCors() {
934968
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
935969
assertThat(beanNames).hasSize(2);
936970
for (String beanName : beanNames) {
937-
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
971+
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) appContext.getBean(beanName);
938972
assertThat(handlerMapping).isNotNull();
939973
DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping);
940974
Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource) accessor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:mvc="http://www.springframework.org/schema/mvc"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
6+
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
7+
8+
<bean id="patternParser" class="org.springframework.web.util.pattern.PathPatternParser"/>
9+
10+
<mvc:annotation-driven>
11+
<mvc:path-matching pattern-parser="patternParser"/>
12+
</mvc:annotation-driven>
13+
14+
<mvc:view-controller path="/foo"/>
15+
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/"/>
16+
17+
</beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:mvc="http://www.springframework.org/schema/mvc"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
6+
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
7+
8+
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher"/>
9+
10+
<mvc:annotation-driven>
11+
<mvc:path-matching path-matcher="pathMatcher"/>
12+
</mvc:annotation-driven>
13+
14+
<mvc:view-controller path="/foo"/>
15+
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/"/>
16+
17+
</beans>

0 commit comments

Comments
 (0)