diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java index 7aafffc98a..26057a8794 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java @@ -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 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. + *

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()))); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java index 5df30cbc55..00aeebb0fb 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java @@ -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 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();