Polishing and minor refactoring in HandlerMappingIntrospector
Closes gh-30127
This commit is contained in:
@@ -82,7 +82,7 @@ public class HandlerMappingIntrospector
|
|||||||
@Nullable
|
@Nullable
|
||||||
private List<HandlerMapping> handlerMappings;
|
private List<HandlerMapping> handlerMappings;
|
||||||
|
|
||||||
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternHandlerMappings = Collections.emptyMap();
|
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternMappings = Collections.emptyMap();
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,10 +95,55 @@ public class HandlerMappingIntrospector
|
|||||||
if (this.handlerMappings == null) {
|
if (this.handlerMappings == null) {
|
||||||
Assert.notNull(this.applicationContext, "No ApplicationContext");
|
Assert.notNull(this.applicationContext, "No ApplicationContext");
|
||||||
this.handlerMappings = initHandlerMappings(this.applicationContext);
|
this.handlerMappings = initHandlerMappings(this.applicationContext);
|
||||||
this.pathPatternHandlerMappings = initPathPatternMatchableHandlerMappings(this.handlerMappings);
|
|
||||||
|
this.pathPatternMappings = this.handlerMappings.stream()
|
||||||
|
.filter(m -> m instanceof MatchableHandlerMapping hm && hm.getPatternParser() != null)
|
||||||
|
.map(mapping -> (MatchableHandlerMapping) mapping)
|
||||||
|
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {
|
||||||
|
|
||||||
|
Map<String, HandlerMapping> beans =
|
||||||
|
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
|
||||||
|
|
||||||
|
if (!beans.isEmpty()) {
|
||||||
|
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
|
||||||
|
AnnotationAwareOrderComparator.sort(mappings);
|
||||||
|
return Collections.unmodifiableList(mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(initFallback(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
|
||||||
|
Properties properties;
|
||||||
|
try {
|
||||||
|
Resource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
|
||||||
|
properties = PropertiesLoaderUtils.loadProperties(resource);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("Could not load DispatcherServlet.properties: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = properties.getProperty(HandlerMapping.class.getName());
|
||||||
|
String[] names = StringUtils.commaDelimitedListToStringArray(value);
|
||||||
|
List<HandlerMapping> result = new ArrayList<>(names.length);
|
||||||
|
for (String name : names) {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
|
||||||
|
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
|
||||||
|
result.add((HandlerMapping) mapping);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ex) {
|
||||||
|
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the configured or detected {@code HandlerMapping}s.
|
* Return the configured or detected {@code HandlerMapping}s.
|
||||||
*/
|
*/
|
||||||
@@ -109,27 +154,27 @@ public class HandlerMappingIntrospector
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the {@link HandlerMapping} that would handle the given request and
|
* Find the {@link HandlerMapping} that would handle the given request and
|
||||||
* return it as a {@link MatchableHandlerMapping} that can be used to test
|
* return a {@link MatchableHandlerMapping} to use for path matching.
|
||||||
* request-matching criteria.
|
|
||||||
* <p>If the matching HandlerMapping is not an instance of
|
|
||||||
* {@link MatchableHandlerMapping}, an IllegalStateException is raised.
|
|
||||||
* @param request the current request
|
* @param request the current request
|
||||||
* @return the resolved matcher, or {@code null}
|
* @return the resolved {@code MatchableHandlerMapping}, or {@code null}
|
||||||
|
* @throws IllegalStateException if the matching HandlerMapping is not an
|
||||||
|
* instance of {@link MatchableHandlerMapping}
|
||||||
* @throws Exception if any of the HandlerMapping's raise an exception
|
* @throws Exception if any of the HandlerMapping's raise an exception
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
|
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
|
||||||
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
|
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
|
||||||
return doWithMatchingMapping(wrappedRequest, false, (matchedMapping, executionChain) -> {
|
|
||||||
if (matchedMapping instanceof MatchableHandlerMapping matchableHandlerMapping) {
|
return doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> {
|
||||||
PathPatternMatchableHandlerMapping mapping = this.pathPatternHandlerMappings.get(matchedMapping);
|
if (mapping instanceof MatchableHandlerMapping) {
|
||||||
if (mapping != null) {
|
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
|
||||||
|
if (pathPatternMapping != null) {
|
||||||
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
|
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
|
||||||
return new PathSettingHandlerMapping(mapping, requestPath);
|
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
|
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
|
||||||
return new PathSettingHandlerMapping(matchableHandlerMapping, lookupPath);
|
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
|
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
|
||||||
@@ -140,7 +185,7 @@ public class HandlerMappingIntrospector
|
|||||||
@Nullable
|
@Nullable
|
||||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||||
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
|
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
|
||||||
return doWithMatchingMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
|
return doWithHandlerMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
|
||||||
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
|
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
|
||||||
if (interceptor instanceof CorsConfigurationSource ccs) {
|
if (interceptor instanceof CorsConfigurationSource ccs) {
|
||||||
return ccs.getCorsConfiguration(wrappedRequest);
|
return ccs.getCorsConfiguration(wrappedRequest);
|
||||||
@@ -154,15 +199,15 @@ public class HandlerMappingIntrospector
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private <T> T doWithMatchingMapping(
|
private <T> T doWithHandlerMapping(
|
||||||
HttpServletRequest request, boolean ignoreException,
|
HttpServletRequest request, boolean ignoreException,
|
||||||
BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) throws Exception {
|
BiFunction<HandlerMapping, HandlerExecutionChain, T> extractor) throws Exception {
|
||||||
|
|
||||||
Assert.state(this.handlerMappings != null, "Handler mappings not initialized");
|
Assert.state(this.handlerMappings != null, "HandlerMapping's not initialized");
|
||||||
|
|
||||||
boolean parseRequestPath = !this.pathPatternHandlerMappings.isEmpty();
|
boolean parsePath = !this.pathPatternMappings.isEmpty();
|
||||||
RequestPath previousPath = null;
|
RequestPath previousPath = null;
|
||||||
if (parseRequestPath) {
|
if (parsePath) {
|
||||||
previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
|
previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
|
||||||
ServletRequestPathUtils.parseAndCache(request);
|
ServletRequestPathUtils.parseAndCache(request);
|
||||||
}
|
}
|
||||||
@@ -180,11 +225,11 @@ public class HandlerMappingIntrospector
|
|||||||
if (chain == null) {
|
if (chain == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return matchHandler.apply(handlerMapping, chain);
|
return extractor.apply(handlerMapping, chain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (parseRequestPath) {
|
if (parsePath) {
|
||||||
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
|
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,11 +237,11 @@ public class HandlerMappingIntrospector
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private <T> T doWithMatchingMappingIgnoringException(
|
private <T> T doWithHandlerMappingIgnoringException(
|
||||||
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) {
|
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return doWithMatchingMapping(request, true, matchHandler);
|
return doWithHandlerMapping(request, true, matchHandler);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new IllegalStateException("HandlerMapping exception not suppressed", ex);
|
throw new IllegalStateException("HandlerMapping exception not suppressed", ex);
|
||||||
@@ -204,55 +249,6 @@ public class HandlerMappingIntrospector
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static List<HandlerMapping> initHandlerMappings(ApplicationContext applicationContext) {
|
|
||||||
Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
|
||||||
applicationContext, HandlerMapping.class, true, false);
|
|
||||||
if (!beans.isEmpty()) {
|
|
||||||
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
|
|
||||||
AnnotationAwareOrderComparator.sort(mappings);
|
|
||||||
return Collections.unmodifiableList(mappings);
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableList(initFallback(applicationContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
|
|
||||||
Properties props;
|
|
||||||
String path = "DispatcherServlet.properties";
|
|
||||||
try {
|
|
||||||
Resource resource = new ClassPathResource(path, DispatcherServlet.class);
|
|
||||||
props = PropertiesLoaderUtils.loadProperties(resource);
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
String value = props.getProperty(HandlerMapping.class.getName());
|
|
||||||
String[] names = StringUtils.commaDelimitedListToStringArray(value);
|
|
||||||
List<HandlerMapping> result = new ArrayList<>(names.length);
|
|
||||||
for (String name : names) {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
|
|
||||||
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
|
|
||||||
result.add((HandlerMapping) mapping);
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException ex) {
|
|
||||||
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<HandlerMapping, PathPatternMatchableHandlerMapping> initPathPatternMatchableHandlerMappings(
|
|
||||||
List<HandlerMapping> mappings) {
|
|
||||||
|
|
||||||
return mappings.stream()
|
|
||||||
.filter(MatchableHandlerMapping.class::isInstance)
|
|
||||||
.map(MatchableHandlerMapping.class::cast)
|
|
||||||
.filter(mapping -> mapping.getPatternParser() != null)
|
|
||||||
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request wrapper that buffers request attributes in order protect the
|
* Request wrapper that buffers request attributes in order protect the
|
||||||
* underlying request from attribute changes.
|
* underlying request from attribute changes.
|
||||||
@@ -298,26 +294,27 @@ public class HandlerMappingIntrospector
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class PathSettingHandlerMapping implements MatchableHandlerMapping {
|
private static class LookupPathMatchableHandlerMapping implements MatchableHandlerMapping {
|
||||||
|
|
||||||
private final MatchableHandlerMapping delegate;
|
private final MatchableHandlerMapping delegate;
|
||||||
|
|
||||||
private final Object path;
|
private final Object lookupPath;
|
||||||
|
|
||||||
private final String pathAttributeName;
|
private final String pathAttributeName;
|
||||||
|
|
||||||
PathSettingHandlerMapping(MatchableHandlerMapping delegate, Object path) {
|
LookupPathMatchableHandlerMapping(MatchableHandlerMapping delegate, Object lookupPath) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.path = path;
|
this.lookupPath = lookupPath;
|
||||||
this.pathAttributeName = (path instanceof RequestPath ?
|
this.pathAttributeName = (lookupPath instanceof RequestPath ?
|
||||||
ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
|
ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public RequestMatchResult match(HttpServletRequest request, String pattern) {
|
public RequestMatchResult match(HttpServletRequest request, String pattern) {
|
||||||
|
pattern = (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern);
|
||||||
Object previousPath = request.getAttribute(this.pathAttributeName);
|
Object previousPath = request.getAttribute(this.pathAttributeName);
|
||||||
request.setAttribute(this.pathAttributeName, this.path);
|
request.setAttribute(this.pathAttributeName, this.lookupPath);
|
||||||
try {
|
try {
|
||||||
return this.delegate.match(request, pattern);
|
return this.delegate.match(request, pattern);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 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.
|
||||||
@@ -30,8 +30,9 @@ import org.springframework.web.util.pattern.PathPattern;
|
|||||||
import org.springframework.web.util.pattern.PathPatternParser;
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps {@link MatchableHandlerMapping}s configured with a {@link PathPatternParser}
|
* Decorate another {@link MatchableHandlerMapping} that's configured with a
|
||||||
* in order to parse patterns lazily and cache them for re-ues.
|
* {@link PathPatternParser} in order to parse and cache String patterns passed
|
||||||
|
* into the {@code match} method.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 5.3
|
* @since 5.3
|
||||||
@@ -49,8 +50,8 @@ class PathPatternMatchableHandlerMapping implements MatchableHandlerMapping {
|
|||||||
|
|
||||||
|
|
||||||
public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate) {
|
public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate) {
|
||||||
Assert.notNull(delegate, "Delegate MatchableHandlerMapping is required.");
|
Assert.notNull(delegate, "HandlerMapping to delegate to is required.");
|
||||||
Assert.notNull(delegate.getPatternParser(), "PatternParser is required.");
|
Assert.notNull(delegate.getPatternParser(), "Expected HandlerMapping configured to use PatternParser.");
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.parser = delegate.getPatternParser();
|
this.parser = delegate.getPatternParser();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user