Skip to content

Commit eafe3af

Browse files
committed
Polishing and minor refactoring in HandlerMappingIntrospector
Closes gh-30128
1 parent 26e0343 commit eafe3af

File tree

2 files changed

+82
-84
lines changed

2 files changed

+82
-84
lines changed

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

Lines changed: 76 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -82,7 +82,7 @@ public class HandlerMappingIntrospector
8282
@Nullable
8383
private List<HandlerMapping> handlerMappings;
8484

85-
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternHandlerMappings = Collections.emptyMap();
85+
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternMappings = Collections.emptyMap();
8686

8787

8888
/**
@@ -113,10 +113,55 @@ public void afterPropertiesSet() {
113113
if (this.handlerMappings == null) {
114114
Assert.notNull(this.applicationContext, "No ApplicationContext");
115115
this.handlerMappings = initHandlerMappings(this.applicationContext);
116-
this.pathPatternHandlerMappings = initPathPatternMatchableHandlerMappings(this.handlerMappings);
116+
117+
this.pathPatternMappings = this.handlerMappings.stream()
118+
.filter(m -> m instanceof MatchableHandlerMapping && ((MatchableHandlerMapping) m).getPatternParser() != null)
119+
.map(mapping -> (MatchableHandlerMapping) mapping)
120+
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
117121
}
118122
}
119123

124+
private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {
125+
126+
Map<String, HandlerMapping> beans =
127+
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
128+
129+
if (!beans.isEmpty()) {
130+
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
131+
AnnotationAwareOrderComparator.sort(mappings);
132+
return Collections.unmodifiableList(mappings);
133+
}
134+
135+
return Collections.unmodifiableList(initFallback(context));
136+
}
137+
138+
private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
139+
Properties properties;
140+
try {
141+
Resource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
142+
properties = PropertiesLoaderUtils.loadProperties(resource);
143+
}
144+
catch (IOException ex) {
145+
throw new IllegalStateException("Could not load DispatcherServlet.properties: " + ex.getMessage());
146+
}
147+
148+
String value = properties.getProperty(HandlerMapping.class.getName());
149+
String[] names = StringUtils.commaDelimitedListToStringArray(value);
150+
List<HandlerMapping> result = new ArrayList<>(names.length);
151+
for (String name : names) {
152+
try {
153+
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
154+
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
155+
result.add((HandlerMapping) mapping);
156+
}
157+
catch (ClassNotFoundException ex) {
158+
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
159+
}
160+
}
161+
return result;
162+
}
163+
164+
120165
/**
121166
* Return the configured or detected {@code HandlerMapping}s.
122167
*/
@@ -127,27 +172,27 @@ public List<HandlerMapping> getHandlerMappings() {
127172

128173
/**
129174
* Find the {@link HandlerMapping} that would handle the given request and
130-
* return it as a {@link MatchableHandlerMapping} that can be used to test
131-
* request-matching criteria.
132-
* <p>If the matching HandlerMapping is not an instance of
133-
* {@link MatchableHandlerMapping}, an IllegalStateException is raised.
175+
* return a {@link MatchableHandlerMapping} to use for path matching.
134176
* @param request the current request
135-
* @return the resolved matcher, or {@code null}
177+
* @return the resolved {@code MatchableHandlerMapping}, or {@code null}
178+
* @throws IllegalStateException if the matching HandlerMapping is not an
179+
* instance of {@link MatchableHandlerMapping}
136180
* @throws Exception if any of the HandlerMapping's raise an exception
137181
*/
138182
@Nullable
139183
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
140184
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
141-
return doWithMatchingMapping(wrappedRequest, false, (matchedMapping, executionChain) -> {
142-
if (matchedMapping instanceof MatchableHandlerMapping) {
143-
PathPatternMatchableHandlerMapping mapping = this.pathPatternHandlerMappings.get(matchedMapping);
144-
if (mapping != null) {
185+
186+
return doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> {
187+
if (mapping instanceof MatchableHandlerMapping) {
188+
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
189+
if (pathPatternMapping != null) {
145190
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
146-
return new PathSettingHandlerMapping(mapping, requestPath);
191+
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
147192
}
148193
else {
149194
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
150-
return new PathSettingHandlerMapping((MatchableHandlerMapping) matchedMapping, lookupPath);
195+
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
151196
}
152197
}
153198
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
@@ -158,7 +203,7 @@ public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest req
158203
@Nullable
159204
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
160205
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
161-
return doWithMatchingMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
206+
return doWithHandlerMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
162207
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
163208
if (interceptor instanceof CorsConfigurationSource) {
164209
return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrappedRequest);
@@ -172,15 +217,15 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
172217
}
173218

174219
@Nullable
175-
private <T> T doWithMatchingMapping(
220+
private <T> T doWithHandlerMapping(
176221
HttpServletRequest request, boolean ignoreException,
177-
BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) throws Exception {
222+
BiFunction<HandlerMapping, HandlerExecutionChain, T> extractor) throws Exception {
178223

179-
Assert.notNull(this.handlerMappings, "Handler mappings not initialized");
224+
Assert.state(this.handlerMappings != null, "HandlerMapping's not initialized");
180225

181-
boolean parseRequestPath = !this.pathPatternHandlerMappings.isEmpty();
226+
boolean parsePath = !this.pathPatternMappings.isEmpty();
182227
RequestPath previousPath = null;
183-
if (parseRequestPath) {
228+
if (parsePath) {
184229
previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
185230
ServletRequestPathUtils.parseAndCache(request);
186231
}
@@ -198,79 +243,30 @@ private <T> T doWithMatchingMapping(
198243
if (chain == null) {
199244
continue;
200245
}
201-
return matchHandler.apply(handlerMapping, chain);
246+
return extractor.apply(handlerMapping, chain);
202247
}
203248
}
204249
finally {
205-
if (parseRequestPath) {
250+
if (parsePath) {
206251
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
207252
}
208253
}
209254
return null;
210255
}
211256

212257
@Nullable
213-
private <T> T doWithMatchingMappingIgnoringException(
258+
private <T> T doWithHandlerMappingIgnoringException(
214259
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) {
215260

216261
try {
217-
return doWithMatchingMapping(request, true, matchHandler);
262+
return doWithHandlerMapping(request, true, matchHandler);
218263
}
219264
catch (Exception ex) {
220265
throw new IllegalStateException("HandlerMapping exception not suppressed", ex);
221266
}
222267
}
223268

224269

225-
private static List<HandlerMapping> initHandlerMappings(ApplicationContext applicationContext) {
226-
Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
227-
applicationContext, HandlerMapping.class, true, false);
228-
if (!beans.isEmpty()) {
229-
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
230-
AnnotationAwareOrderComparator.sort(mappings);
231-
return Collections.unmodifiableList(mappings);
232-
}
233-
return Collections.unmodifiableList(initFallback(applicationContext));
234-
}
235-
236-
private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
237-
Properties props;
238-
String path = "DispatcherServlet.properties";
239-
try {
240-
Resource resource = new ClassPathResource(path, DispatcherServlet.class);
241-
props = PropertiesLoaderUtils.loadProperties(resource);
242-
}
243-
catch (IOException ex) {
244-
throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
245-
}
246-
247-
String value = props.getProperty(HandlerMapping.class.getName());
248-
String[] names = StringUtils.commaDelimitedListToStringArray(value);
249-
List<HandlerMapping> result = new ArrayList<>(names.length);
250-
for (String name : names) {
251-
try {
252-
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
253-
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
254-
result.add((HandlerMapping) mapping);
255-
}
256-
catch (ClassNotFoundException ex) {
257-
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
258-
}
259-
}
260-
return result;
261-
}
262-
263-
private static Map<HandlerMapping, PathPatternMatchableHandlerMapping> initPathPatternMatchableHandlerMappings(
264-
List<HandlerMapping> mappings) {
265-
266-
return mappings.stream()
267-
.filter(mapping -> mapping instanceof MatchableHandlerMapping)
268-
.map(mapping -> (MatchableHandlerMapping) mapping)
269-
.filter(mapping -> mapping.getPatternParser() != null)
270-
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
271-
}
272-
273-
274270
/**
275271
* Request wrapper that buffers request attributes in order protect the
276272
* underlying request from attribute changes.
@@ -316,26 +312,27 @@ public void removeAttribute(String name) {
316312
}
317313

318314

319-
private static class PathSettingHandlerMapping implements MatchableHandlerMapping {
315+
private static class LookupPathMatchableHandlerMapping implements MatchableHandlerMapping {
320316

321317
private final MatchableHandlerMapping delegate;
322318

323-
private final Object path;
319+
private final Object lookupPath;
324320

325321
private final String pathAttributeName;
326322

327-
PathSettingHandlerMapping(MatchableHandlerMapping delegate, Object path) {
323+
LookupPathMatchableHandlerMapping(MatchableHandlerMapping delegate, Object lookupPath) {
328324
this.delegate = delegate;
329-
this.path = path;
330-
this.pathAttributeName = (path instanceof RequestPath ?
325+
this.lookupPath = lookupPath;
326+
this.pathAttributeName = (lookupPath instanceof RequestPath ?
331327
ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
332328
}
333329

334330
@Nullable
335331
@Override
336332
public RequestMatchResult match(HttpServletRequest request, String pattern) {
333+
pattern = (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern);
337334
Object previousPath = request.getAttribute(this.pathAttributeName);
338-
request.setAttribute(this.pathAttributeName, this.path);
335+
request.setAttribute(this.pathAttributeName, this.lookupPath);
339336
try {
340337
return this.delegate.match(request, pattern);
341338
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -30,8 +30,9 @@
3030
import org.springframework.web.util.pattern.PathPatternParser;
3131

3232
/**
33-
* Wraps {@link MatchableHandlerMapping}s configured with a {@link PathPatternParser}
34-
* in order to parse patterns lazily and cache them for re-ues.
33+
* Decorate another {@link MatchableHandlerMapping} that's configured with a
34+
* {@link PathPatternParser} in order to parse and cache String patterns passed
35+
* into the {@code match} method.
3536
*
3637
* @author Rossen Stoyanchev
3738
* @since 5.3
@@ -49,8 +50,8 @@ class PathPatternMatchableHandlerMapping implements MatchableHandlerMapping {
4950

5051

5152
public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate) {
52-
Assert.notNull(delegate, "Delegate MatchableHandlerMapping is required.");
53-
Assert.notNull(delegate.getPatternParser(), "PatternParser is required.");
53+
Assert.notNull(delegate, "HandlerMapping to delegate to is required.");
54+
Assert.notNull(delegate.getPatternParser(), "Expected HandlerMapping configured to use PatternParser.");
5455
this.delegate = delegate;
5556
this.parser = delegate.getPatternParser();
5657
}

0 commit comments

Comments
 (0)