Skip to content

Commit 233c15b

Browse files
committed
Add PathPatternRegistry for handler mapping matching
Previously `HandlerMapping` implementation were heavily relying on `String` path patterns, `PathMatcher` implementations and dedicated maps for matching incoming request URL to an actual request handler. This commit adds the `PathPatternRegistry` that holds `PathPattern` instances and the associated request handler — matching results are then shared as `PathMatchResult` instances. `AbstractUrlHandlerMapping` will use this registry directly, but other components dealing with request matching (like `PatternsRequestCondition`) will directly use ordered `PathPattern` collections since ordering is important there. This opens the door for faster request matching and simplifies the design of this part. Issue: SPR-15608
1 parent 1f0d107 commit 233c15b

26 files changed

+729
-661
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.util.pattern;
18+
19+
import java.util.Comparator;
20+
21+
/**
22+
* {@link PathPattern} comparator that takes account of a specified
23+
* path and sorts anything that exactly matches it to be first.
24+
*
25+
* <p>Patterns that have the same specificity are then compared
26+
* using their String representation, in order to avoid
27+
* considering them as "duplicates" when sorting them
28+
* in {@link java.util.Set} collections.
29+
*
30+
* @author Brian Clozel
31+
* @since 5.0
32+
*/
33+
public class PathPatternComparator implements Comparator<PathPattern> {
34+
35+
private final String path;
36+
37+
public PathPatternComparator(String path) {
38+
this.path = path;
39+
}
40+
41+
@Override
42+
public int compare(PathPattern o1, PathPattern o2) {
43+
// Nulls get sorted to the end
44+
if (o1 == null) {
45+
return (o2 == null ? 0 : +1);
46+
}
47+
else if (o2 == null) {
48+
return -1;
49+
}
50+
// exact matches get sorted first
51+
if (o1.getPatternString().equals(path)) {
52+
return (o2.getPatternString().equals(path)) ? 0 : -1;
53+
}
54+
else if (o2.getPatternString().equals(path)) {
55+
return +1;
56+
}
57+
// compare pattern specificity
58+
int result = o1.compareTo(o2);
59+
// if equal specificity, sort using pattern string
60+
if (result == 0) {
61+
return o1.getPatternString().compareTo(o2.getPatternString());
62+
}
63+
return result;
64+
}
65+
66+
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/PathMatchConfigurer.java

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,27 @@
1717
package org.springframework.web.reactive.config;
1818

1919
import org.springframework.lang.Nullable;
20-
import org.springframework.util.PathMatcher;
21-
import org.springframework.web.util.pattern.ParsingPathMatcher;
2220

2321
/**
2422
* Assist with configuring {@code HandlerMapping}'s with path matching options.
2523
*
2624
* @author Rossen Stoyanchev
25+
* @author Brian Clozel
2726
* @since 5.0
2827
*/
2928
public class PathMatchConfigurer {
3029

31-
private Boolean suffixPatternMatch;
32-
3330
private Boolean trailingSlashMatch;
3431

35-
private Boolean registeredSuffixPatternMatch;
36-
37-
private PathMatcher pathMatcher;
38-
32+
private Boolean caseSensitiveMatch;
3933

4034
/**
41-
* Whether to use suffix pattern match (".*") when matching patterns to
42-
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
43-
* <p>By default this is set to {@code true}.
44-
* @see #registeredSuffixPatternMatch
35+
* Whether to match to URLs irrespective of their case.
36+
* If enabled a method mapped to "/users" won't match to "/Users/".
37+
* <p>The default value is {@code false}.
4538
*/
46-
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
47-
this.suffixPatternMatch = suffixPatternMatch;
39+
public PathMatchConfigurer setUseCaseSensitiveMatch(Boolean caseSensitiveMatch) {
40+
this.caseSensitiveMatch = caseSensitiveMatch;
4841
return this;
4942
}
5043

@@ -58,49 +51,14 @@ public PathMatchConfigurer setUseTrailingSlashMatch(Boolean trailingSlashMatch)
5851
return this;
5952
}
6053

61-
/**
62-
* Whether suffix pattern matching should work only against path extensions
63-
* that are explicitly registered. This is generally recommended to reduce
64-
* ambiguity and to avoid issues such as when a "." (dot) appears in the path
65-
* for other reasons.
66-
* <p>By default this is set to "true".
67-
*/
68-
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean registeredSuffixPatternMatch) {
69-
this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
70-
return this;
71-
}
72-
73-
/**
74-
* Set the PathMatcher for matching URL paths against registered URL patterns.
75-
* <p>The default is a {@link org.springframework.web.util.pattern.ParsingPathMatcher}.
76-
*/
77-
public PathMatchConfigurer setPathMatcher(PathMatcher pathMatcher) {
78-
this.pathMatcher = pathMatcher;
79-
return this;
80-
}
81-
82-
@Nullable
83-
protected Boolean isUseSuffixPatternMatch() {
84-
return this.suffixPatternMatch;
85-
}
86-
8754
@Nullable
8855
protected Boolean isUseTrailingSlashMatch() {
8956
return this.trailingSlashMatch;
9057
}
9158

9259
@Nullable
93-
protected Boolean isUseRegisteredSuffixPatternMatch() {
94-
return this.registeredSuffixPatternMatch;
95-
}
96-
97-
@Nullable
98-
public PathMatcher getPathMatcher() {
99-
if (this.pathMatcher instanceof ParsingPathMatcher && (this.trailingSlashMatch || this.suffixPatternMatch)) {
100-
throw new IllegalStateException("When using a ParsingPathMatcher, useTrailingSlashMatch" +
101-
" and useSuffixPatternMatch should be set to 'false'.");
102-
}
103-
return this.pathMatcher;
60+
protected Boolean isUseCaseSensitiveMatch() {
61+
return this.caseSensitiveMatch;
10462
}
10563

10664
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceHandlerRegistry.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.springframework.context.ApplicationContext;
2727
import org.springframework.lang.Nullable;
2828
import org.springframework.util.Assert;
29-
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
29+
import org.springframework.web.reactive.handler.AbstractUrlHandlerMapping;
3030
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
3131
import org.springframework.web.reactive.resource.ResourceWebHandler;
3232
import org.springframework.web.server.WebHandler;
@@ -76,7 +76,7 @@ public ResourceHandlerRegistry(ApplicationContext applicationContext) {
7676
* URL path patterns. The handler will be invoked for every incoming request
7777
* that matches to one of the specified path patterns.
7878
* <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"}
79-
* are allowed. See {@link org.springframework.web.util.pattern.ParsingPathMatcher}
79+
* are allowed. See {@link org.springframework.web.util.pattern.PathPattern}
8080
* for more details on the syntax.
8181
* @return A {@link ResourceHandlerRegistration} to use to further
8282
* configure the registered resource handler
@@ -115,7 +115,7 @@ public ResourceHandlerRegistry setOrder(int order) {
115115
* of no registrations.
116116
*/
117117
@Nullable
118-
protected AbstractHandlerMapping getHandlerMapping() {
118+
protected AbstractUrlHandlerMapping getHandlerMapping() {
119119
if (this.registrations.isEmpty()) {
120120
return null;
121121
}

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.springframework.http.codec.ServerCodecConfigurer;
3737
import org.springframework.lang.Nullable;
3838
import org.springframework.util.ClassUtils;
39-
import org.springframework.util.PathMatcher;
4039
import org.springframework.validation.Errors;
4140
import org.springframework.validation.MessageCodesResolver;
4241
import org.springframework.validation.Validator;
@@ -73,13 +72,6 @@
7372
*/
7473
public class WebFluxConfigurationSupport implements ApplicationContextAware {
7574

76-
static final boolean jackson2Present =
77-
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
78-
WebFluxConfigurationSupport.class.getClassLoader()) &&
79-
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
80-
WebFluxConfigurationSupport.class.getClassLoader());
81-
82-
8375
private Map<String, CorsConfiguration> corsConfigurations;
8476

8577
private PathMatchConfigurer pathMatchConfigurer;
@@ -118,21 +110,11 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping() {
118110
mapping.setCorsConfigurations(getCorsConfigurations());
119111

120112
PathMatchConfigurer configurer = getPathMatchConfigurer();
121-
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
122-
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
123-
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
124-
if (useSuffixPatternMatch != null) {
125-
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
126-
}
127-
if (useRegisteredSuffixPatternMatch != null) {
128-
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
113+
if (configurer.isUseTrailingSlashMatch() != null) {
114+
mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
129115
}
130-
if (useTrailingSlashMatch != null) {
131-
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
132-
}
133-
PathMatcher pathMatcher = configurer.getPathMatcher();
134-
if (pathMatcher != null) {
135-
mapping.setPathMatcher(pathMatcher);
116+
if (configurer.isUseCaseSensitiveMatch() != null) {
117+
mapping.setUseCaseSensitiveMatch(configurer.isUseCaseSensitiveMatch());
136118
}
137119
return mapping;
138120
}
@@ -224,9 +206,12 @@ public HandlerMapping resourceHandlerMapping() {
224206

225207
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
226208
if (handlerMapping != null) {
227-
PathMatchConfigurer pathMatchConfigurer = getPathMatchConfigurer();
228-
if (pathMatchConfigurer.getPathMatcher() != null) {
229-
handlerMapping.setPathMatcher(pathMatchConfigurer.getPathMatcher());
209+
PathMatchConfigurer configurer = getPathMatchConfigurer();
210+
if (configurer.isUseTrailingSlashMatch() != null) {
211+
handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
212+
}
213+
if (configurer.isUseCaseSensitiveMatch() != null) {
214+
handlerMapping.setUseCaseSensitiveMatch(configurer.isUseCaseSensitiveMatch());
230215
}
231216
}
232217
else {

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,6 @@ default void addCorsMappings(CorsRegistry registry) {
5858
/**
5959
* Configure path matching options.
6060
*
61-
* <p>Note that if a {@link org.springframework.web.util.pattern.ParsingPathMatcher}
62-
* is configured here,
63-
* the {@link PathMatchConfigurer#setUseTrailingSlashMatch(Boolean)} and
64-
* {@link PathMatchConfigurer#setUseSuffixPatternMatch(Boolean)} options must be set
65-
* to {@literal false}as they can lead to illegal patterns,
66-
* see {@link org.springframework.web.util.pattern.ParsingPathMatcher}.
67-
*
6861
* {@code HandlerMapping}s with path matching options.
6962
* @param configurer the {@link PathMatchConfigurer} instance
7063
*/

spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.core.Ordered;
2525
import org.springframework.lang.Nullable;
2626
import org.springframework.util.Assert;
27-
import org.springframework.util.PathMatcher;
2827
import org.springframework.web.cors.CorsConfiguration;
2928
import org.springframework.web.cors.reactive.CorsConfigurationSource;
3029
import org.springframework.web.cors.reactive.CorsProcessor;
@@ -34,7 +33,7 @@
3433
import org.springframework.web.reactive.HandlerMapping;
3534
import org.springframework.web.server.ServerWebExchange;
3635
import org.springframework.web.server.WebHandler;
37-
import org.springframework.web.util.pattern.ParsingPathMatcher;
36+
import org.springframework.web.util.pattern.PathPatternParser;
3837

3938
/**
4039
* Abstract base class for {@link org.springframework.web.reactive.HandlerMapping}
@@ -52,7 +51,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport im
5251

5352
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
5453

55-
private PathMatcher pathMatcher = new ParsingPathMatcher();
54+
private PathPatternParser patternParser = new PathPatternParser();
5655

5756
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
5857

@@ -74,22 +73,28 @@ public final int getOrder() {
7473
}
7574

7675
/**
77-
* Set the PathMatcher implementation to use for matching URL paths
78-
* against registered URL patterns.
79-
* <p>The default is a {@link ParsingPathMatcher}.
76+
* Whether to match to URLs irrespective of their case.
77+
* If enabled a method mapped to "/users" won't match to "/Users/".
78+
* <p>The default value is {@code false}.
8079
*/
81-
public void setPathMatcher(PathMatcher pathMatcher) {
82-
Assert.notNull(pathMatcher, "PathMatcher must not be null");
83-
this.pathMatcher = pathMatcher;
84-
this.globalCorsConfigSource.setPathMatcher(pathMatcher);
80+
public void setUseCaseSensitiveMatch(boolean caseSensitiveMatch) {
81+
this.patternParser.setCaseSensitive(caseSensitiveMatch);
8582
}
8683

8784
/**
88-
* Return the PathMatcher implementation to use for matching URL paths
89-
* against registered URL patterns.
85+
* Whether to match to URLs irrespective of the presence of a trailing slash.
86+
* If enabled a method mapped to "/users" also matches to "/users/".
87+
* <p>The default value is {@code true}.
9088
*/
91-
public PathMatcher getPathMatcher() {
92-
return this.pathMatcher;
89+
public void setUseTrailingSlashMatch(boolean trailingSlashMatch) {
90+
this.patternParser.setMatchOptionalTrailingSlash(trailingSlashMatch);
91+
}
92+
93+
/**
94+
* Return the {@link PathPatternParser} instance.
95+
*/
96+
public PathPatternParser getPathPatternParser() {
97+
return this.patternParser;
9398
}
9499

95100
/**

0 commit comments

Comments
 (0)