Skip to content

Commit 202fa5c

Browse files
committed
Polishing and minor refactoring in HandlerMappingIntrospector
Closes gh-30127
1 parent 8010de8 commit 202fa5c

File tree

2 files changed

+81
-83
lines changed

2 files changed

+81
-83
lines changed

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

+75-78
Original file line numberDiff line numberDiff line change
@@ -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
@Override
@@ -95,10 +95,55 @@ public void afterPropertiesSet() {
9595
if (this.handlerMappings == null) {
9696
Assert.notNull(this.applicationContext, "No ApplicationContext");
9797
this.handlerMappings = initHandlerMappings(this.applicationContext);
98-
this.pathPatternHandlerMappings = initPathPatternMatchableHandlerMappings(this.handlerMappings);
98+
99+
this.pathPatternMappings = this.handlerMappings.stream()
100+
.filter(m -> m instanceof MatchableHandlerMapping hm && hm.getPatternParser() != null)
101+
.map(mapping -> (MatchableHandlerMapping) mapping)
102+
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
99103
}
100104
}
101105

106+
private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {
107+
108+
Map<String, HandlerMapping> beans =
109+
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
110+
111+
if (!beans.isEmpty()) {
112+
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
113+
AnnotationAwareOrderComparator.sort(mappings);
114+
return Collections.unmodifiableList(mappings);
115+
}
116+
117+
return Collections.unmodifiableList(initFallback(context));
118+
}
119+
120+
private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
121+
Properties properties;
122+
try {
123+
Resource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
124+
properties = PropertiesLoaderUtils.loadProperties(resource);
125+
}
126+
catch (IOException ex) {
127+
throw new IllegalStateException("Could not load DispatcherServlet.properties: " + ex.getMessage());
128+
}
129+
130+
String value = properties.getProperty(HandlerMapping.class.getName());
131+
String[] names = StringUtils.commaDelimitedListToStringArray(value);
132+
List<HandlerMapping> result = new ArrayList<>(names.length);
133+
for (String name : names) {
134+
try {
135+
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
136+
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
137+
result.add((HandlerMapping) mapping);
138+
}
139+
catch (ClassNotFoundException ex) {
140+
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
141+
}
142+
}
143+
return result;
144+
}
145+
146+
102147
/**
103148
* Return the configured or detected {@code HandlerMapping}s.
104149
*/
@@ -109,27 +154,27 @@ public List<HandlerMapping> getHandlerMappings() {
109154

110155
/**
111156
* Find the {@link HandlerMapping} that would handle the given request and
112-
* return it as a {@link MatchableHandlerMapping} that can be used to test
113-
* request-matching criteria.
114-
* <p>If the matching HandlerMapping is not an instance of
115-
* {@link MatchableHandlerMapping}, an IllegalStateException is raised.
157+
* return a {@link MatchableHandlerMapping} to use for path matching.
116158
* @param request the current request
117-
* @return the resolved matcher, or {@code null}
159+
* @return the resolved {@code MatchableHandlerMapping}, or {@code null}
160+
* @throws IllegalStateException if the matching HandlerMapping is not an
161+
* instance of {@link MatchableHandlerMapping}
118162
* @throws Exception if any of the HandlerMapping's raise an exception
119163
*/
120164
@Nullable
121165
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
122166
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
123-
return doWithMatchingMapping(wrappedRequest, false, (matchedMapping, executionChain) -> {
124-
if (matchedMapping instanceof MatchableHandlerMapping matchableHandlerMapping) {
125-
PathPatternMatchableHandlerMapping mapping = this.pathPatternHandlerMappings.get(matchedMapping);
126-
if (mapping != null) {
167+
168+
return doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> {
169+
if (mapping instanceof MatchableHandlerMapping) {
170+
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
171+
if (pathPatternMapping != null) {
127172
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
128-
return new PathSettingHandlerMapping(mapping, requestPath);
173+
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
129174
}
130175
else {
131176
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
132-
return new PathSettingHandlerMapping(matchableHandlerMapping, lookupPath);
177+
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
133178
}
134179
}
135180
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
@@ -140,7 +185,7 @@ public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest req
140185
@Nullable
141186
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
142187
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
143-
return doWithMatchingMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
188+
return doWithHandlerMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
144189
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
145190
if (interceptor instanceof CorsConfigurationSource ccs) {
146191
return ccs.getCorsConfiguration(wrappedRequest);
@@ -154,15 +199,15 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
154199
}
155200

156201
@Nullable
157-
private <T> T doWithMatchingMapping(
202+
private <T> T doWithHandlerMapping(
158203
HttpServletRequest request, boolean ignoreException,
159-
BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) throws Exception {
204+
BiFunction<HandlerMapping, HandlerExecutionChain, T> extractor) throws Exception {
160205

161-
Assert.state(this.handlerMappings != null, "Handler mappings not initialized");
206+
Assert.state(this.handlerMappings != null, "HandlerMapping's not initialized");
162207

163-
boolean parseRequestPath = !this.pathPatternHandlerMappings.isEmpty();
208+
boolean parsePath = !this.pathPatternMappings.isEmpty();
164209
RequestPath previousPath = null;
165-
if (parseRequestPath) {
210+
if (parsePath) {
166211
previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
167212
ServletRequestPathUtils.parseAndCache(request);
168213
}
@@ -180,79 +225,30 @@ private <T> T doWithMatchingMapping(
180225
if (chain == null) {
181226
continue;
182227
}
183-
return matchHandler.apply(handlerMapping, chain);
228+
return extractor.apply(handlerMapping, chain);
184229
}
185230
}
186231
finally {
187-
if (parseRequestPath) {
232+
if (parsePath) {
188233
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
189234
}
190235
}
191236
return null;
192237
}
193238

194239
@Nullable
195-
private <T> T doWithMatchingMappingIgnoringException(
240+
private <T> T doWithHandlerMappingIgnoringException(
196241
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) {
197242

198243
try {
199-
return doWithMatchingMapping(request, true, matchHandler);
244+
return doWithHandlerMapping(request, true, matchHandler);
200245
}
201246
catch (Exception ex) {
202247
throw new IllegalStateException("HandlerMapping exception not suppressed", ex);
203248
}
204249
}
205250

206251

207-
private static List<HandlerMapping> initHandlerMappings(ApplicationContext applicationContext) {
208-
Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
209-
applicationContext, HandlerMapping.class, true, false);
210-
if (!beans.isEmpty()) {
211-
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
212-
AnnotationAwareOrderComparator.sort(mappings);
213-
return Collections.unmodifiableList(mappings);
214-
}
215-
return Collections.unmodifiableList(initFallback(applicationContext));
216-
}
217-
218-
private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
219-
Properties props;
220-
String path = "DispatcherServlet.properties";
221-
try {
222-
Resource resource = new ClassPathResource(path, DispatcherServlet.class);
223-
props = PropertiesLoaderUtils.loadProperties(resource);
224-
}
225-
catch (IOException ex) {
226-
throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
227-
}
228-
229-
String value = props.getProperty(HandlerMapping.class.getName());
230-
String[] names = StringUtils.commaDelimitedListToStringArray(value);
231-
List<HandlerMapping> result = new ArrayList<>(names.length);
232-
for (String name : names) {
233-
try {
234-
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
235-
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
236-
result.add((HandlerMapping) mapping);
237-
}
238-
catch (ClassNotFoundException ex) {
239-
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
240-
}
241-
}
242-
return result;
243-
}
244-
245-
private static Map<HandlerMapping, PathPatternMatchableHandlerMapping> initPathPatternMatchableHandlerMappings(
246-
List<HandlerMapping> mappings) {
247-
248-
return mappings.stream()
249-
.filter(MatchableHandlerMapping.class::isInstance)
250-
.map(MatchableHandlerMapping.class::cast)
251-
.filter(mapping -> mapping.getPatternParser() != null)
252-
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
253-
}
254-
255-
256252
/**
257253
* Request wrapper that buffers request attributes in order protect the
258254
* underlying request from attribute changes.
@@ -298,26 +294,27 @@ public void removeAttribute(String name) {
298294
}
299295

300296

301-
private static class PathSettingHandlerMapping implements MatchableHandlerMapping {
297+
private static class LookupPathMatchableHandlerMapping implements MatchableHandlerMapping {
302298

303299
private final MatchableHandlerMapping delegate;
304300

305-
private final Object path;
301+
private final Object lookupPath;
306302

307303
private final String pathAttributeName;
308304

309-
PathSettingHandlerMapping(MatchableHandlerMapping delegate, Object path) {
305+
LookupPathMatchableHandlerMapping(MatchableHandlerMapping delegate, Object lookupPath) {
310306
this.delegate = delegate;
311-
this.path = path;
312-
this.pathAttributeName = (path instanceof RequestPath ?
307+
this.lookupPath = lookupPath;
308+
this.pathAttributeName = (lookupPath instanceof RequestPath ?
313309
ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
314310
}
315311

316312
@Nullable
317313
@Override
318314
public RequestMatchResult match(HttpServletRequest request, String pattern) {
315+
pattern = (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern);
319316
Object previousPath = request.getAttribute(this.pathAttributeName);
320-
request.setAttribute(this.pathAttributeName, this.path);
317+
request.setAttribute(this.pathAttributeName, this.lookupPath);
321318
try {
322319
return this.delegate.match(request, pattern);
323320
}

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

+6-5
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-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)