Configurable limit on HandlerMappingIntrospector

Closes gh-34918
This commit is contained in:
rstoyanchev 2025-05-28 10:55:49 +01:00
parent 67ed64a41d
commit 983af78352
2 changed files with 34 additions and 9 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -99,6 +99,8 @@ public class HandlerMappingIntrospector
private static final String CACHED_RESULT_ATTRIBUTE =
HandlerMappingIntrospector.class.getName() + ".CachedResult";
private static final int DEFAULT_CACHE_LIMIT = 2048;
@Nullable
private ApplicationContext applicationContext;
@ -108,9 +110,30 @@ public class HandlerMappingIntrospector
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternMappings = Collections.emptyMap();
private int patternCacheLimit = DEFAULT_CACHE_LIMIT;
private final CacheResultLogHelper cacheLogHelper = new CacheResultLogHelper();
/**
* Set a limit on the maximum number of security patterns passed into
* {@link MatchableHandlerMapping#match} at runtime to cache.
* <p>By default, this is set to 2048.
* @param patternCacheLimit the limit to use
* @since 6.2.8
*/
public void setPatternCacheLimit(int patternCacheLimit) {
this.patternCacheLimit = patternCacheLimit;
}
/**
* Return the configured limit on security patterns to cache.
* @since 6.2.8
*/
public int getPatternCacheLimit() {
return this.patternCacheLimit;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
@ -124,8 +147,9 @@ public class HandlerMappingIntrospector
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));
.map(hm -> (MatchableHandlerMapping) hm)
.collect(Collectors.toMap(hm -> hm, (MatchableHandlerMapping delegate) ->
new PathPatternMatchableHandlerMapping(delegate, getPatternCacheLimit())));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -39,28 +39,29 @@ import org.springframework.web.util.pattern.PathPatternParser;
*/
class PathPatternMatchableHandlerMapping implements MatchableHandlerMapping {
private static final int MAX_PATTERNS = 1024;
private final MatchableHandlerMapping delegate;
private final PathPatternParser parser;
private final Map<String, PathPattern> pathPatternCache = new ConcurrentHashMap<>();
private final int cacheLimit;
public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate) {
public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate, int cacheLimit) {
Assert.notNull(delegate, "HandlerMapping to delegate to is required.");
Assert.notNull(delegate.getPatternParser(), "Expected HandlerMapping configured to use PatternParser.");
this.delegate = delegate;
this.parser = delegate.getPatternParser();
this.cacheLimit = cacheLimit;
}
@Nullable
@Override
public RequestMatchResult match(HttpServletRequest request, String pattern) {
PathPattern pathPattern = this.pathPatternCache.computeIfAbsent(pattern, value -> {
Assert.state(this.pathPatternCache.size() < MAX_PATTERNS, "Max size for pattern cache exceeded.");
Assert.state(this.pathPatternCache.size() < this.cacheLimit, "Max size for pattern cache exceeded.");
return this.parser.parse(pattern);
});
PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();