diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java
index 93c0094c3c4..5b85bb09f0e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java
@@ -38,8 +38,6 @@ import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
-import org.springframework.web.servlet.handler.MatchableHandlerMapping;
-import org.springframework.web.servlet.handler.RequestMatchResult;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
@@ -57,7 +55,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
* @since 5.2
*/
@SuppressWarnings("removal")
-public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean, MatchableHandlerMapping {
+public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
private @Nullable RouterFunction> routerFunction;
@@ -230,10 +228,4 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE, request);
}
- @SuppressWarnings("removal")
- @Deprecated(since = "7.0", forRemoval = true)
- @Override
- public @Nullable RequestMatchResult match(HttpServletRequest request, String pattern) {
- throw new UnsupportedOperationException("This HandlerMapping uses PathPatterns");
- }
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
index 86b8bff446f..210603c7f75 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
@@ -62,7 +62,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
* @since 16.04.2003
*/
@SuppressWarnings("removal")
-public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
+public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
private @Nullable Object rootHandler;
@@ -465,19 +465,6 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
}
- @SuppressWarnings("removal")
- @Override
- @Deprecated(since = "7.0", forRemoval = true)
- public @Nullable RequestMatchResult match(HttpServletRequest request, String pattern) {
- Assert.state(getPatternParser() == null, "This HandlerMapping uses PathPatterns.");
- String lookupPath = UrlPathHelper.getResolvedLookupPath(request);
- if (getPathMatcher().match(pattern, lookupPath)) {
- return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
- }
- return null;
- }
-
-
/**
* Return the handler mappings as a read-only Map, with the registered path
* or pattern as key and the handler object (or handler bean name in case of
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
deleted file mode 100644
index 0fbdb2fe285..00000000000
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java
+++ /dev/null
@@ -1,631 +0,0 @@
-/*
- * Copyright 2002-present 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.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.web.servlet.handler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.BiFunction;
-import java.util.stream.Collectors;
-
-import jakarta.servlet.DispatcherType;
-import jakarta.servlet.Filter;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletRequestWrapper;
-import jakarta.servlet.http.HttpServletResponse;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.beans.factory.BeanFactoryUtils;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.core.annotation.AnnotationAwareOrderComparator;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PropertiesLoaderUtils;
-import org.springframework.http.server.RequestPath;
-import org.springframework.http.server.ServletServerHttpRequest;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.StringUtils;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.CorsUtils;
-import org.springframework.web.cors.PreFlightRequestHandler;
-import org.springframework.web.servlet.DispatcherServlet;
-import org.springframework.web.servlet.HandlerExecutionChain;
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.servlet.NoHandlerFoundException;
-import org.springframework.web.util.ServletRequestPathUtils;
-import org.springframework.web.util.UrlPathHelper;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-/**
- * Helper class to get information from the {@code HandlerMapping} that would
- * serve a specific request.
- *
- *
Provides the following methods:
- *
- * - {@link #getMatchableHandlerMapping} — obtain a {@code HandlerMapping}
- * to check request-matching criteria against.
- *
- {@link #getCorsConfiguration} — obtain the CORS configuration for the
- * request.
- *
- *
- * Note that this is primarily an SPI to allow Spring Security
- * to align its pattern matching with the same pattern matching that would be
- * used in Spring MVC for a given request, in order to avoid security issues.
- *
- *
Use of this component incurs the performance overhead of mapping the
- * request, and should not be repeated multiple times per request.
- * {@link #createCacheFilter()} exposes a Filter to cache the results.
- * Applications that rely on Spring Security don't need to deploy this Filter
- * since Spring Security doe that. However, other custom security layers, used
- * in place of Spring Security that use this component should deploy the cache
- * Filter with requirements described in the Javadoc for the method.
- *
- * @author Rossen Stoyanchev
- * @since 4.3.1
- * @deprecated in favor of using just {@link PathPatternParser}; when
- * {@link #allHandlerMappingsUsePathPatternParser} returns true, it is sufficient
- * to use that to align with handler mappings.
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "7.0", forRemoval = true)
-public class HandlerMappingIntrospector
- implements CorsConfigurationSource, PreFlightRequestHandler, ApplicationContextAware, InitializingBean {
-
- private static final Log logger = LogFactory.getLog(HandlerMappingIntrospector.class.getName());
-
- private static final String CACHED_RESULT_ATTRIBUTE =
- HandlerMappingIntrospector.class.getName() + ".CachedResult";
-
- private static final int DEFAULT_CACHE_LIMIT = 2048;
-
-
- private @Nullable ApplicationContext applicationContext;
-
- private @Nullable List handlerMappings;
-
- 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;
- }
-
- @Override
- public void afterPropertiesSet() {
- if (this.handlerMappings == null) {
- Assert.notNull(this.applicationContext, "No ApplicationContext");
- this.handlerMappings = initHandlerMappings(this.applicationContext);
-
- this.pathPatternMappings = this.handlerMappings.stream()
- .filter(m -> m instanceof MatchableHandlerMapping hm && hm.getPatternParser() != null)
- .map(hm -> (MatchableHandlerMapping) hm)
- .collect(Collectors.toMap(hm -> hm, (MatchableHandlerMapping delegate) ->
- new PathPatternMatchableHandlerMapping(delegate, getPatternCacheLimit())));
- }
- }
-
- private static List initHandlerMappings(ApplicationContext context) {
-
- Map beans =
- BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
-
- if (!beans.isEmpty()) {
- List mappings = new ArrayList<>(beans.values());
- AnnotationAwareOrderComparator.sort(mappings);
- return Collections.unmodifiableList(mappings);
- }
-
- return Collections.unmodifiableList(initFallback(context));
- }
-
- private static List 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 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.
- */
- public List getHandlerMappings() {
- return (this.handlerMappings != null ? this.handlerMappings : Collections.emptyList());
- }
-
- /**
- * Return {@code true} if all {@link HandlerMapping} beans
- * {@link HandlerMapping#usesPathPatterns() use parsed PathPatterns},
- * and {@code false} if any don't.
- * @since 6.2
- */
- public boolean allHandlerMappingsUsePathPatternParser() {
- Assert.state(this.handlerMappings != null, "Not yet initialized via afterPropertiesSet.");
- return getHandlerMappings().stream().allMatch(HandlerMapping::usesPathPatterns);
- }
-
-
- /**
- * Find the matching {@link HandlerMapping} for the request, and invoke the
- * handler it returns as a {@link PreFlightRequestHandler}.
- * @throws NoHandlerFoundException if no handler matches the request
- * @since 6.2
- */
- @Override
- public void handlePreFlight(HttpServletRequest request, HttpServletResponse response) throws Exception {
- Assert.state(this.handlerMappings != null, "Not yet initialized via afterPropertiesSet.");
- Assert.state(CorsUtils.isPreFlightRequest(request), "Not a pre-flight request.");
- RequestPath previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
- try {
- ServletRequestPathUtils.parseAndCache(request);
- for (HandlerMapping mapping : this.handlerMappings) {
- HandlerExecutionChain chain = mapping.getHandler(request);
- if (chain != null) {
- Object handler = chain.getHandler();
- if (handler instanceof PreFlightRequestHandler preFlightHandler) {
- preFlightHandler.handlePreFlight(request, response);
- return;
- }
- throw new IllegalStateException("Expected PreFlightRequestHandler: " + handler.getClass());
- }
- }
- throw new NoHandlerFoundException(
- request.getMethod(), request.getRequestURI(), new ServletServerHttpRequest(request).getHeaders());
- }
- finally {
- ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
- }
- }
-
-
- /**
- * {@link Filter} that looks up the {@code MatchableHandlerMapping} and
- * {@link CorsConfiguration} for the request proactively before delegating
- * to the rest of the chain, caching the result in a request attribute, and
- * restoring it after the chain returns.
- * Note: Applications that rely on Spring Security do
- * not use this component directly and should not deploy the filter instead
- * allowing Spring Security to do it. Other custom security layers used in
- * place of Spring Security that also rely on {@code HandlerMappingIntrospector}
- * should deploy this filter ahead of other filters where lookups are
- * performed, and should also make sure the filter is configured to handle
- * all dispatcher types.
- * @return the Filter instance to use
- * @since 6.0.14
- */
- public Filter createCacheFilter() {
- return (request, response, chain) -> {
- CachedResult previous = setCache((HttpServletRequest) request);
- try {
- chain.doFilter(request, response);
- }
- finally {
- resetCache(request, previous);
- }
- };
- }
-
- /**
- * Perform a lookup and save the {@link CachedResult} as a request attribute.
- * This method can be invoked from a filter before subsequent calls to
- * {@link #getMatchableHandlerMapping(HttpServletRequest)} and
- * {@link #getCorsConfiguration(HttpServletRequest)} to avoid repeated lookups.
- * @param request the current request
- * @return the previous {@link CachedResult}, if there is one from a parent dispatch
- * @since 6.0.14
- */
- public @Nullable CachedResult setCache(HttpServletRequest request) {
- CachedResult previous = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
- if (previous == null || !previous.matches(request)) {
- HttpServletRequest wrapped = new AttributesPreservingRequest(request);
- CachedResult result;
- try {
- // Try to get both in one lookup (with ignoringException=false)
- result = doWithHandlerMapping(wrapped, false, (mapping, executionChain) -> {
- MatchableHandlerMapping matchableMapping = createMatchableHandlerMapping(mapping, wrapped);
- CorsConfiguration corsConfig = getCorsConfiguration(executionChain, wrapped);
- return new CachedResult(request, matchableMapping, corsConfig, null, null);
- });
- }
- catch (Exception ex) {
- try {
- // Try CorsConfiguration at least with ignoreException=true
- AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request);
- result = doWithHandlerMapping(requestToUse, true, (mapping, executionChain) -> {
- CorsConfiguration corsConfig = getCorsConfiguration(executionChain, wrapped);
- return new CachedResult(request, null, corsConfig, ex, null);
- });
- }
- catch (Exception ex2) {
- result = new CachedResult(request, null, null, ex, new IllegalStateException(ex2));
- }
- }
- if (result == null) {
- result = new CachedResult(request, null, null, null, null);
- }
- request.setAttribute(CACHED_RESULT_ATTRIBUTE, result);
- }
- return previous;
- }
-
- /**
- * Restore a previous {@link CachedResult}. This method can be invoked from
- * a filter after delegating to the rest of the chain.
- * @since 6.0.14
- */
- public void resetCache(ServletRequest request, @Nullable CachedResult cachedResult) {
- request.setAttribute(CACHED_RESULT_ATTRIBUTE, cachedResult);
- }
-
- /**
- * Find the {@link HandlerMapping} that would handle the given request and
- * return a {@link MatchableHandlerMapping} to use for path matching.
- * @param request the current request
- * @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
- */
- public @Nullable MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
- CachedResult result = CachedResult.getResultFor(request);
- if (result != null) {
- return result.getHandlerMapping();
- }
- this.cacheLogHelper.logHandlerMappingCacheMiss(request);
- HttpServletRequest requestToUse = new AttributesPreservingRequest(request);
- return doWithHandlerMapping(requestToUse, false,
- (mapping, executionChain) -> createMatchableHandlerMapping(mapping, requestToUse));
- }
-
- private MatchableHandlerMapping createMatchableHandlerMapping(HandlerMapping mapping, HttpServletRequest request) {
- if (mapping instanceof MatchableHandlerMapping) {
- PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
- if (pathPatternMapping != null) {
- RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
- return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
- }
- else {
- String lookupPath = (String) request.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
- return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
- }
- }
- throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
- }
-
- @Override
- public @Nullable CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
- CachedResult result = CachedResult.getResultFor(request);
- if (result != null) {
- return result.getCorsConfig();
- }
- this.cacheLogHelper.logCorsConfigCacheMiss(request);
- try {
- boolean ignoreException = true;
- AttributesPreservingRequest requestToUse = new AttributesPreservingRequest(request);
- return doWithHandlerMapping(requestToUse, ignoreException,
- (handlerMapping, executionChain) -> getCorsConfiguration(executionChain, requestToUse));
- }
- catch (Exception ex) {
- // HandlerMapping exceptions are ignored. More basic error like parsing the request path.
- throw new IllegalStateException(ex);
- }
- }
-
- private static @Nullable CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, HttpServletRequest request) {
- for (HandlerInterceptor interceptor : chain.getInterceptorList()) {
- if (interceptor instanceof CorsConfigurationSource source) {
- return source.getCorsConfiguration(request);
- }
- }
- if (chain.getHandler() instanceof CorsConfigurationSource source) {
- return source.getCorsConfiguration(request);
- }
- return null;
- }
-
- private @Nullable T doWithHandlerMapping(
- HttpServletRequest request, boolean ignoreException,
- BiFunction extractor) throws Exception {
-
- Assert.state(this.handlerMappings != null, "HandlerMapping's not initialized");
-
- boolean parsePath = !this.pathPatternMappings.isEmpty();
- RequestPath previousPath = null;
- if (parsePath) {
- previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
- ServletRequestPathUtils.parseAndCache(request);
- }
- try {
- for (HandlerMapping handlerMapping : this.handlerMappings) {
- HandlerExecutionChain chain = null;
- try {
- chain = handlerMapping.getHandler(request);
- }
- catch (Exception ex) {
- if (!ignoreException) {
- throw ex;
- }
- }
- if (chain == null) {
- continue;
- }
- return extractor.apply(handlerMapping, chain);
- }
- }
- finally {
- if (parsePath) {
- ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
- }
- }
- return null;
- }
-
-
- /**
- * Container for a {@link MatchableHandlerMapping} and {@link CorsConfiguration}
- * for a given request matched by dispatcher type and requestURI.
- * @since 6.0.14
- */
- @SuppressWarnings("serial")
- public static final class CachedResult {
-
- private final DispatcherType dispatcherType;
-
- private final String requestURI;
-
- private final @Nullable MatchableHandlerMapping handlerMapping;
-
- private final @Nullable CorsConfiguration corsConfig;
-
- private final @Nullable Exception failure;
-
- private final @Nullable IllegalStateException corsConfigFailure;
-
- private CachedResult(HttpServletRequest request,
- @Nullable MatchableHandlerMapping mapping, @Nullable CorsConfiguration config,
- @Nullable Exception failure, @Nullable IllegalStateException corsConfigFailure) {
-
- this.dispatcherType = request.getDispatcherType();
- this.requestURI = request.getRequestURI();
- this.handlerMapping = mapping;
- this.corsConfig = config;
- this.failure = failure;
- this.corsConfigFailure = corsConfigFailure;
- }
-
- public boolean matches(HttpServletRequest request) {
- return (this.dispatcherType.equals(request.getDispatcherType()) &&
- this.requestURI.equals(request.getRequestURI()));
- }
-
- public @Nullable MatchableHandlerMapping getHandlerMapping() throws Exception {
- if (this.failure != null) {
- throw this.failure;
- }
- return this.handlerMapping;
- }
-
- public @Nullable CorsConfiguration getCorsConfig() {
- if (this.corsConfigFailure != null) {
- throw this.corsConfigFailure;
- }
- return this.corsConfig;
- }
-
- @Override
- public String toString() {
- return "CachedResult for " + this.dispatcherType + " dispatch to '" + this.requestURI + "'";
- }
-
-
- /**
- * Return a {@link CachedResult} that matches the given request.
- */
- public static @Nullable CachedResult getResultFor(HttpServletRequest request) {
- CachedResult result = (CachedResult) request.getAttribute(CACHED_RESULT_ATTRIBUTE);
- return (result != null && result.matches(request) ? result : null);
- }
- }
-
-
- private static class CacheResultLogHelper {
-
- private final Map counters =
- Map.of("MatchableHandlerMapping", new AtomicInteger(), "CorsConfiguration", new AtomicInteger());
-
- public void logHandlerMappingCacheMiss(HttpServletRequest request) {
- logCacheMiss("MatchableHandlerMapping", request);
- }
-
- public void logCorsConfigCacheMiss(HttpServletRequest request) {
- logCacheMiss("CorsConfiguration", request);
- }
-
- private void logCacheMiss(String label, HttpServletRequest request) {
- AtomicInteger counter = this.counters.get(label);
- Assert.notNull(counter, "Expected '" + label + "' counter.");
-
- String message = getLogMessage(label, request);
-
- if (logger.isDebugEnabled() && counter.getAndIncrement() == 0) {
- logger.debug(message + " This is logged once only at DEBUG level, and every time at TRACE.");
- }
- else if (logger.isTraceEnabled()) {
- logger.trace("No CachedResult, performing " + label + " lookup instead.");
- }
- }
-
- private static String getLogMessage(String label, HttpServletRequest request) {
- return "Cache miss for " + request.getDispatcherType() + " dispatch to '" + request.getRequestURI() + "' " +
- "(previous " + request.getAttribute(CACHED_RESULT_ATTRIBUTE) + "). " +
- "Performing " + label + " lookup. If there are repeated lookups per request, " +
- "consider using HandlerMappingIntrospector#createCacheFilter()" +
- "to create a Servlet Filter to set the cache for the request.";
- }
- }
-
-
- /**
- * Request wrapper that buffers request attributes in order protect the
- * underlying request from attribute changes.
- */
- private static class AttributesPreservingRequest extends HttpServletRequestWrapper {
-
- private final Map attributes;
-
- AttributesPreservingRequest(HttpServletRequest request) {
- super(request);
- this.attributes = initAttributes(request);
- this.attributes.put(AbstractHandlerMapping.SUPPRESS_LOGGING_ATTRIBUTE, Boolean.TRUE);
- }
-
- private Map initAttributes(HttpServletRequest request) {
- Map map = new HashMap<>();
- Enumeration names = request.getAttributeNames();
- while (names.hasMoreElements()) {
- String name = names.nextElement();
- map.put(name, request.getAttribute(name));
- }
- return map;
- }
-
- @Override
- public void setAttribute(String name, Object value) {
- this.attributes.put(name, value);
- }
-
- @Override
- public @Nullable Object getAttribute(String name) {
- return this.attributes.get(name);
- }
-
- @Override
- public Enumeration getAttributeNames() {
- return Collections.enumeration(this.attributes.keySet());
- }
-
- @Override
- public void removeAttribute(String name) {
- this.attributes.remove(name);
- }
- }
-
-
- private static class LookupPathMatchableHandlerMapping implements MatchableHandlerMapping {
-
- private final MatchableHandlerMapping delegate;
-
- private final Object lookupPath;
-
- private final String pathAttributeName;
-
- LookupPathMatchableHandlerMapping(MatchableHandlerMapping delegate, Object lookupPath) {
- this.delegate = delegate;
- this.lookupPath = lookupPath;
- this.pathAttributeName = (lookupPath instanceof RequestPath ?
- ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
- }
-
- @Override
- public @Nullable PathPatternParser getPatternParser() {
- return this.delegate.getPatternParser();
- }
-
- @SuppressWarnings("removal")
- @Deprecated(since = "7.0", forRemoval = true)
- @Override
- public @Nullable RequestMatchResult match(HttpServletRequest request, String pattern) {
- pattern = initFullPathPattern(pattern);
- Object previousPath = request.getAttribute(this.pathAttributeName);
- request.setAttribute(this.pathAttributeName, this.lookupPath);
- try {
- return this.delegate.match(request, pattern);
- }
- finally {
- request.setAttribute(this.pathAttributeName, previousPath);
- }
- }
-
- private String initFullPathPattern(String pattern) {
- PathPatternParser parser = (getPatternParser() != null ? getPatternParser() : PathPatternParser.defaultInstance);
- return parser.initFullPathPattern(pattern);
- }
-
- @Override
- public @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- return this.delegate.getHandler(request);
- }
- }
-
-}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java
deleted file mode 100644
index 5466683f35a..00000000000
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MatchableHandlerMapping.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2002-present 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.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.web.servlet.handler;
-
-import jakarta.servlet.http.HttpServletRequest;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.util.PathMatcher;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.util.UrlPathHelper;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-/**
- * Additional interface that a {@link HandlerMapping} can implement to expose
- * a request matching API aligned with its internal request matching
- * configuration and implementation.
- *
- * @author Rossen Stoyanchev
- * @since 4.3.1
- * @deprecated together with {@link HandlerMappingIntrospector} without a replacement.
- */
-@Deprecated(since = "7.0", forRemoval = true)
-public interface MatchableHandlerMapping extends HandlerMapping {
-
- /**
- * Return the parser of this {@code HandlerMapping}, if configured in which
- * case pre-parsed patterns are used.
- * @since 5.3
- */
- default @Nullable PathPatternParser getPatternParser() {
- return null;
- }
-
- /**
- * Determine whether the request matches the given pattern. Use this method
- * when {@link #getPatternParser()} returns {@code null} which means that the
- * {@code HandlerMapping} is using String pattern matching.
- * @param request the current request
- * @param pattern the pattern to match
- * @return the result from request matching, or {@code null} if none
- * @deprecated use of {@link PathMatcher} and {@link UrlPathHelper} is deprecated
- * for use at runtime in web modules in favor of parsed patterns with
- * {@link PathPatternParser}.
- */
- @SuppressWarnings("removal")
- @Deprecated(since = "7.0", forRemoval = true)
- @Nullable RequestMatchResult match(HttpServletRequest request, String pattern);
-
-}
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
deleted file mode 100644
index e44a565b33c..00000000000
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/PathPatternMatchableHandlerMapping.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2002-present 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.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.web.servlet.handler;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import jakarta.servlet.http.HttpServletRequest;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.http.server.PathContainer;
-import org.springframework.util.Assert;
-import org.springframework.web.servlet.HandlerExecutionChain;
-import org.springframework.web.util.ServletRequestPathUtils;
-import org.springframework.web.util.pattern.PathPattern;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-/**
- * Decorate another {@link MatchableHandlerMapping} that's configured with a
- * {@link PathPatternParser} in order to parse and cache String patterns passed
- * into the {@code match} method.
- *
- * @author Rossen Stoyanchev
- * @since 5.3
- * @deprecated together with {@link HandlerMappingIntrospector} without a replacement.
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "7.0", forRemoval = true)
-class PathPatternMatchableHandlerMapping implements MatchableHandlerMapping {
-
- private final MatchableHandlerMapping delegate;
-
- private final PathPatternParser parser;
-
- private final Map pathPatternCache = new ConcurrentHashMap<>();
-
- private final int cacheLimit;
-
-
- 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;
- }
-
- @SuppressWarnings("removal")
- @Deprecated(since = "7.0", forRemoval = true)
- @Override
- public @Nullable RequestMatchResult match(HttpServletRequest request, String pattern) {
- PathPattern pathPattern = this.pathPatternCache.computeIfAbsent(pattern, value -> {
- Assert.state(this.pathPatternCache.size() < this.cacheLimit, "Max size for pattern cache exceeded.");
- return this.parser.parse(pattern);
- });
- PathContainer path = ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();
- return (pathPattern.matches(path) ? new RequestMatchResult(pathPattern, path) : null);
- }
-
- @Override
- public @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- return this.delegate.getHandler(request);
- }
-
-}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java
deleted file mode 100644
index 9aa1f24a923..00000000000
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/RequestMatchResult.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2002-present 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.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.web.servlet.handler;
-
-import java.util.Map;
-
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.http.server.PathContainer;
-import org.springframework.util.Assert;
-import org.springframework.util.PathMatcher;
-import org.springframework.web.util.UrlPathHelper;
-import org.springframework.web.util.pattern.PathPattern;
-import org.springframework.web.util.pattern.PathPatternParser;
-
-/**
- * Container for the result from request pattern matching via
- * {@link MatchableHandlerMapping} with a method to further extract
- * URI template variables from the pattern.
- *
- * @author Rossen Stoyanchev
- * @since 4.3.1
- * @deprecated together with {@link HandlerMappingIntrospector} without a replacement.
- */
-@SuppressWarnings("removal")
-@Deprecated(since = "7.0", forRemoval = true)
-public class RequestMatchResult {
-
- private final @Nullable PathPattern pathPattern;
-
- private final @Nullable PathContainer lookupPathContainer;
-
-
- private final @Nullable String pattern;
-
- private final @Nullable String lookupPath;
-
- private final @Nullable PathMatcher pathMatcher;
-
-
- /**
- * Create an instance with the matched {@code PathPattern}.
- * @param pathPattern the pattern that was matched
- * @param lookupPath the mapping path
- * @since 5.3
- */
- public RequestMatchResult(PathPattern pathPattern, PathContainer lookupPath) {
- Assert.notNull(pathPattern, "PathPattern is required");
- Assert.notNull(lookupPath, "PathContainer is required");
-
- this.pattern = null;
- this.lookupPath = null;
- this.pathMatcher = null;
-
- this.pathPattern = pathPattern;
- this.lookupPathContainer = lookupPath;
-
- }
-
- /**
- * Create an instance with the matched String pattern.
- * @param pattern the pattern that was matched, possibly with a '/' appended
- * @param lookupPath the mapping path
- * @param pathMatcher the PathMatcher instance used for the match
- * @deprecated use of {@link PathMatcher} and {@link UrlPathHelper} is deprecated
- * for use at runtime in web modules in favor of parsed patterns with
- * {@link PathPatternParser}.
- */
- @Deprecated(since = "7.0", forRemoval = true)
- public RequestMatchResult(String pattern, String lookupPath, PathMatcher pathMatcher) {
- Assert.hasText(pattern, "'matchingPattern' is required");
- Assert.hasText(lookupPath, "'lookupPath' is required");
- Assert.notNull(pathMatcher, "PathMatcher is required");
-
- this.pattern = pattern;
- this.lookupPath = lookupPath;
- this.pathMatcher = pathMatcher;
-
- this.pathPattern = null;
- this.lookupPathContainer = null;
- }
-
- /**
- * Extract URI template variables from the matching pattern as defined in
- * {@link PathPattern#matchAndExtract(PathContainer)}.
- * @return a map with URI template variables
- */
- @SuppressWarnings({"ConstantConditions", "NullAway"})
- public Map extractUriTemplateVariables() {
- return (this.pathPattern != null ?
- this.pathPattern.matchAndExtract(this.lookupPathContainer).getUriVariables() :
- this.pathMatcher.extractUriTemplateVariables(this.pattern, this.lookupPath));
- }
-}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
index 79a7ef4fe9c..104cee36432 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
@@ -28,7 +28,6 @@ import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;
-import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.context.EmbeddedValueResolverAware;
@@ -55,15 +54,12 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.service.annotation.HttpExchange;
-import org.springframework.web.servlet.handler.MatchableHandlerMapping;
-import org.springframework.web.servlet.handler.RequestMatchResult;
import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition;
import org.springframework.web.servlet.mvc.condition.CompositeRequestCondition;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
-import org.springframework.web.util.UrlPathHelper;
/**
* Creates {@link RequestMappingInfo} instances from type-level and method-level
@@ -79,7 +75,7 @@ import org.springframework.web.util.UrlPathHelper;
*/
@SuppressWarnings("removal")
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
- implements MatchableHandlerMapping, EmbeddedValueResolverAware {
+ implements EmbeddedValueResolverAware {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
@@ -474,20 +470,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
}
- @SuppressWarnings("removal")
- @Deprecated(since = "7.0", forRemoval = true)
- @Override
- public @Nullable RequestMatchResult match(HttpServletRequest request, String pattern) {
- Assert.state(getPatternParser() == null, "This HandlerMapping uses PathPatterns.");
- RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
- RequestMappingInfo match = info.getMatchingCondition(request);
- return (match != null && match.getPatternsCondition() != null ?
- new RequestMatchResult(
- match.getPatternsCondition().getPatterns().iterator().next(),
- UrlPathHelper.getResolvedLookupPath(request),
- getPathMatcher()) : null);
- }
-
@Override
protected @Nullable CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java
deleted file mode 100644
index c3792e77732..00000000000
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/HandlerMappingIntrospectorTests.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright 2002-present 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.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.web.servlet.handler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpHeaders;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.context.WebApplicationContext;
-import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
-import org.springframework.web.context.support.GenericWebApplicationContext;
-import org.springframework.web.context.support.StaticWebApplicationContext;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.servlet.HandlerExecutionChain;
-import org.springframework.web.servlet.HandlerMapping;
-import org.springframework.web.servlet.NoHandlerFoundException;
-import org.springframework.web.servlet.function.RouterFunction;
-import org.springframework.web.servlet.function.RouterFunctions;
-import org.springframework.web.servlet.function.ServerResponse;
-import org.springframework.web.servlet.function.support.RouterFunctionMapping;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-import org.springframework.web.testfixture.servlet.MockFilterChain;
-import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
-import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
-import org.springframework.web.util.ServletRequestPathUtils;
-import org.springframework.web.util.pattern.PathPattern;
-import org.springframework.web.util.pattern.PathPatternParser;
-import org.springframework.web.util.pattern.PatternParseException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE;
-
-/**
- * Tests for {@link HandlerMappingIntrospector}.
- *
- * @author Rossen Stoyanchev
- * @since 4.3.1
- */
-@SuppressWarnings("removal")
-class HandlerMappingIntrospectorTests {
-
- @Test
- void detectHandlerMappings() {
- StaticWebApplicationContext context = new StaticWebApplicationContext();
- context.registerSingleton("A", SimpleUrlHandlerMapping.class);
- context.registerSingleton("B", SimpleUrlHandlerMapping.class);
- context.registerSingleton("C", SimpleUrlHandlerMapping.class);
- context.refresh();
-
- List> expected = Arrays.asList(context.getBean("A"), context.getBean("B"), context.getBean("C"));
- List actual = initIntrospector(context).getHandlerMappings();
-
- assertThat(actual).isEqualTo(expected);
- }
-
- @Test
- void detectHandlerMappingsOrdered() {
- GenericWebApplicationContext context = new GenericWebApplicationContext();
- context.registerBean("B", SimpleUrlHandlerMapping.class, () -> {
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- mapping.setOrder(2);
- return mapping;
- });
- context.registerBean("C", SimpleUrlHandlerMapping.class, () -> {
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- mapping.setOrder(3);
- return mapping;
- });
- context.registerBean("A", SimpleUrlHandlerMapping.class, () -> {
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- mapping.setOrder(1);
- return mapping;
- });
- context.refresh();
-
- List> expected = Arrays.asList(context.getBean("A"), context.getBean("B"), context.getBean("C"));
- List actual = initIntrospector(context).getHandlerMappings();
-
- assertThat(actual).isEqualTo(expected);
- }
-
- @Test
- void useParsedPatternsOnly() {
- GenericWebApplicationContext context = new GenericWebApplicationContext();
- context.registerBean("A", SimpleUrlHandlerMapping.class);
- context.registerBean("B", SimpleUrlHandlerMapping.class);
- context.registerBean("C", SimpleUrlHandlerMapping.class);
- context.refresh();
-
- assertThat(initIntrospector(context).allHandlerMappingsUsePathPatternParser()).isTrue();
-
- context = new GenericWebApplicationContext();
- context.registerBean("A", SimpleUrlHandlerMapping.class);
- context.registerBean("B", SimpleUrlHandlerMapping.class);
- context.registerBean("C", SimpleUrlHandlerMapping.class, () -> {
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- mapping.setPatternParser(null);
- return mapping;
- });
- context.refresh();
-
- assertThat(initIntrospector(context).allHandlerMappingsUsePathPatternParser()).isFalse();
- }
-
- @SuppressWarnings("removal")
- @ParameterizedTest
- @ValueSource(booleans = {true, false})
- void getMatchable(boolean usePathPatterns) throws Exception {
-
- TestPathPatternParser parser = new TestPathPatternParser();
-
- GenericWebApplicationContext context = new GenericWebApplicationContext();
- context.registerBean("mapping", SimpleUrlHandlerMapping.class, () -> {
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- if (usePathPatterns) {
- mapping.setPatternParser(parser);
- }
- mapping.setUrlMap(Collections.singletonMap("/path/*", new Object()));
- return mapping;
- });
- context.refresh();
-
- MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path/123");
- MatchableHandlerMapping mapping = initIntrospector(context).getMatchableHandlerMapping(request);
-
- assertThat(mapping).isNotNull();
- assertThat(request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE)).as("Attribute changes not ignored").isNull();
- assertThat(request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE)).as("Parsed path not cleaned").isNull();
-
- assertThat(mapping.match(request, "/p*/*")).isNotNull();
- assertThat(mapping.match(request, "/b*/*")).isNull();
-
- if (usePathPatterns) {
- assertThat(parser.getParsedPatterns()).containsExactly("/path/*", "/p*/*", "/b*/*");
- }
- }
-
- @Test
- void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() {
- StaticWebApplicationContext cxt = new StaticWebApplicationContext();
- cxt.registerBean("mapping", HandlerMapping.class, () -> request -> new HandlerExecutionChain(new Object()));
- cxt.refresh();
-
- MockHttpServletRequest request = new MockHttpServletRequest();
- assertThatIllegalStateException().isThrownBy(() -> initIntrospector(cxt).getMatchableHandlerMapping(request));
- }
-
- @Test // gh-26833
- void getMatchablePreservesRequestAttributes() throws Exception {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- context.register(TestConfig.class);
- context.refresh();
-
- MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path");
- request.setAttribute("name", "value");
-
- MatchableHandlerMapping matchable = initIntrospector(context).getMatchableHandlerMapping(request);
- assertThat(matchable).isNotNull();
-
- // RequestPredicates.restoreAttributes clears and re-adds attributes
- assertThat(request.getAttribute("name")).isEqualTo("value");
- }
-
- @Test
- void getCorsConfigurationPreFlight() {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- context.register(TestConfig.class);
- context.refresh();
-
- // PRE-FLIGHT
-
- MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/path");
- request.addHeader("Origin", "http://localhost:9000");
- request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
- CorsConfiguration corsConfig = initIntrospector(context).getCorsConfiguration(request);
-
- assertThat(corsConfig).isNotNull();
- assertThat(corsConfig.getAllowedOrigins()).isEqualTo(Collections.singletonList("http://localhost:9000"));
- assertThat(corsConfig.getAllowedMethods()).isEqualTo(Collections.singletonList("POST"));
- }
-
- @Test
- void getCorsConfigurationActual() {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- context.register(TestConfig.class);
- context.refresh();
-
- MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path");
- request.addHeader("Origin", "http://localhost:9000");
- CorsConfiguration corsConfig = initIntrospector(context).getCorsConfiguration(request);
-
- assertThat(corsConfig).isNotNull();
- assertThat(corsConfig.getAllowedOrigins()).isEqualTo(Collections.singletonList("http://localhost:9000"));
- assertThat(corsConfig.getAllowedMethods()).isEqualTo(Collections.singletonList("POST"));
- }
-
- @Test
- void handlePreFlight() throws Exception {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- context.register(TestConfig.class);
- context.refresh();
-
- MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/path");
- request.addHeader("Origin", "http://localhost:9000");
- request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
- MockHttpServletResponse response = new MockHttpServletResponse();
-
- initIntrospector(context).handlePreFlight(request, response);
-
- assertThat(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("http://localhost:9000");
- assertThat(response.getHeaders(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).containsExactly("POST");
- }
-
- @Test
- void handlePreFlightWithNoHandlerFoundException() {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- context.register(TestConfig.class);
- context.refresh();
-
- MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/unknownPath");
- request.addHeader("Origin", "http://localhost:9000");
- request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
- MockHttpServletResponse response = new MockHttpServletResponse();
-
- assertThatThrownBy(() -> initIntrospector(context).handlePreFlight(request, response))
- .isInstanceOf(NoHandlerFoundException.class);
-
- assertThat(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isNull();
- assertThat(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isNull();
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"/test", "/resource/1234****"}) // gh-31937
- void cacheFilter(String uri) throws Exception {
- CorsConfiguration corsConfig = new CorsConfiguration();
- TestMatchableHandlerMapping mapping = new TestMatchableHandlerMapping();
- mapping.registerHandler("/*", new TestHandler(corsConfig));
-
- HandlerMappingIntrospector introspector = initIntrospector(mapping);
-
- MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
- MockHttpServletResponse response = new MockHttpServletResponse();
-
- MockFilterChain filterChain = new MockFilterChain(
- new TestServlet(), introspector.createCacheFilter(), new AuthFilter(introspector, corsConfig));
-
- filterChain.doFilter(request, response);
-
- assertThat(response.getContentAsString()).isEqualTo("Success");
- assertThat(mapping.getInvocationCount()).isEqualTo(1);
- assertThat(mapping.getMatchCount()).isEqualTo(1);
- }
-
- @Test
- void cacheFilterWithNestedDispatch() throws Exception {
- CorsConfiguration corsConfig1 = new CorsConfiguration();
- CorsConfiguration corsConfig2 = new CorsConfiguration();
-
- TestMatchableHandlerMapping mapping1 = new TestMatchableHandlerMapping();
- TestMatchableHandlerMapping mapping2 = new TestMatchableHandlerMapping();
-
- mapping1.registerHandler("/1", new TestHandler(corsConfig1));
- mapping2.registerHandler("/2", new TestHandler(corsConfig2));
-
- HandlerMappingIntrospector introspector = initIntrospector(mapping1, mapping2);
-
- MockFilterChain filterChain = new MockFilterChain(
- new TestServlet(),
- introspector.createCacheFilter(),
- new AuthFilter(introspector, corsConfig1),
- (req, res, chain) -> chain.doFilter(new MockHttpServletRequest("GET", "/2"), res),
- introspector.createCacheFilter(),
- new AuthFilter(introspector, corsConfig2));
-
- MockHttpServletResponse response = new MockHttpServletResponse();
- filterChain.doFilter(new MockHttpServletRequest("GET", "/1"), response);
-
- assertThat(response.getContentAsString()).isEqualTo("Success");
- assertThat(mapping1.getInvocationCount()).isEqualTo(2);
- assertThat(mapping2.getInvocationCount()).isEqualTo(1);
- assertThat(mapping1.getMatchCount()).isEqualTo(1);
- assertThat(mapping2.getMatchCount()).isEqualTo(1);
- }
-
- private HandlerMappingIntrospector initIntrospector(TestMatchableHandlerMapping... mappings) {
- StaticWebApplicationContext context = new StaticWebApplicationContext();
- int index = 0;
- for (TestMatchableHandlerMapping mapping : mappings) {
- context.registerBean("mapping" + index++, TestMatchableHandlerMapping.class, () -> mapping);
- }
- context.refresh();
- return initIntrospector(context);
- }
-
- private static HandlerMappingIntrospector initIntrospector(WebApplicationContext context) {
- HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
- introspector.setApplicationContext(context);
- introspector.afterPropertiesSet();
- return introspector;
- }
-
-
- @Configuration
- static class TestConfig {
-
- @Bean
- public RouterFunctionMapping routerFunctionMapping() {
- RouterFunctionMapping mapping = new RouterFunctionMapping();
- mapping.setOrder(1);
- return mapping;
- }
-
- @Bean
- public RequestMappingHandlerMapping handlerMapping() {
- RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
- mapping.setOrder(2);
- return mapping;
- }
-
- @Bean
- public TestController testController() {
- return new TestController();
- }
-
- @Bean
- public RouterFunction> routerFunction() {
- return RouterFunctions.route().GET("/fn-path", request -> ServerResponse.ok().build()).build();
- }
- }
-
-
- @CrossOrigin("http://localhost:9000")
- @Controller
- private static class TestController {
-
- @PostMapping("/path")
- void handle() {
- }
- }
-
-
- private static class TestPathPatternParser extends PathPatternParser {
-
- private final List parsedPatterns = new ArrayList<>();
-
-
- public List getParsedPatterns() {
- return this.parsedPatterns;
- }
-
- @Override
- public PathPattern parse(String pathPattern) throws PatternParseException {
- this.parsedPatterns.add(pathPattern);
- return super.parse(pathPattern);
- }
- }
-
-
- private static class TestMatchableHandlerMapping extends SimpleUrlHandlerMapping {
-
- private int invocationCount;
-
- private int matchCount;
-
- public int getInvocationCount() {
- return this.invocationCount;
- }
-
- public int getMatchCount() {
- return this.matchCount;
- }
-
- @Override
- protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
- this.invocationCount++;
- Object handler = super.getHandlerInternal(request);
- if (handler != null) {
- this.matchCount++;
- }
- return handler;
- }
- }
-
-
- private static class TestHandler implements CorsConfigurationSource {
-
- private final CorsConfiguration corsConfig;
-
- private TestHandler(CorsConfiguration corsConfig) {
- this.corsConfig = corsConfig;
- }
-
- @Override
- public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
- return this.corsConfig;
- }
- }
-
-
- private static class AuthFilter implements Filter {
-
- private final HandlerMappingIntrospector introspector;
-
- private final CorsConfiguration corsConfig;
-
- private AuthFilter(HandlerMappingIntrospector introspector, CorsConfiguration corsConfig) {
- this.introspector = introspector;
- this.corsConfig = corsConfig;
- }
-
- @Override
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
- try {
- for (int i = 0; i < 10; i++) {
- HttpServletRequest httpRequest = (HttpServletRequest) req;
- assertThat(introspector.getMatchableHandlerMapping(httpRequest)).isNotNull();
- assertThat(introspector.getCorsConfiguration(httpRequest)).isSameAs(corsConfig);
- }
- }
- catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- chain.doFilter(req, res);
- }
- }
-
-
- @SuppressWarnings("serial")
- private static class TestServlet extends HttpServlet {
-
- @Override
- protected void service(HttpServletRequest req, HttpServletResponse res) {
- try {
- res.getWriter().print("Success");
- }
- catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- }
- }
-
-}