diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java index 1ce334a9be1e..d4804f4cc149 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java @@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable; +import org.springframework.http.HttpMethod; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.PathMatcher; @@ -48,6 +49,10 @@ public class InterceptorRegistration { private @Nullable List excludePatterns; + private @Nullable List includeHttpMethods; + + private @Nullable List excludeHttpMethods; + private @Nullable PathMatcher pathMatcher; private int order = 0; @@ -106,6 +111,46 @@ public InterceptorRegistration excludePathPatterns(List patterns) { return this; } + /** + * Add HTTP methods the interceptor should be included for. + *

Only requests with these HTTP methods will be intercepted. + * @since 7.0.x + */ + public InterceptorRegistration includeHttpMethods(HttpMethod... httpMethods) { + return includeHttpMethods(Arrays.asList(httpMethods)); + } + + /** + * List-based variant of {@link #includeHttpMethods(HttpMethod...)}. + * @since 7.0.x + */ + public InterceptorRegistration includeHttpMethods(List httpMethods) { + this.includeHttpMethods = (this.includeHttpMethods != null ? + this.includeHttpMethods : new ArrayList<>(httpMethods.size())); + this.includeHttpMethods.addAll(httpMethods); + return this; + } + + /** + * Add HTTP methods the interceptor should be excluded from. + *

Requests with these HTTP methods will be ignored by the interceptor. + * @since 7.0.x + */ + public InterceptorRegistration excludeHttpMethods(HttpMethod... httpMethods){ + return this.excludeHttpMethods(Arrays.asList(httpMethods)); + } + + /** + * List-based variant of {@link #excludeHttpMethods(HttpMethod...)}. + * @since 7.0.x + */ + public InterceptorRegistration excludeHttpMethods(List httpMethods){ + this.excludeHttpMethods = (this.excludeHttpMethods != null ? + this.excludeHttpMethods : new ArrayList<>(httpMethods.size())); + this.excludeHttpMethods.addAll(httpMethods); + return this; + } + /** * Configure the PathMatcher to use to match URL paths with against include * and exclude patterns. @@ -143,19 +188,32 @@ protected int getOrder() { } /** - * Build the underlying interceptor. If URL patterns are provided, the returned + * Build the underlying interceptor. If URL patterns or HTTP methods are provided, the returned * type is {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}. */ @SuppressWarnings("removal") protected Object getInterceptor() { - if (this.includePatterns == null && this.excludePatterns == null) { + if (this.includePatterns == null && this.excludePatterns == null && this.includeHttpMethods == null && this.excludeHttpMethods == null) { return this.interceptor; } + HttpMethod[] includeMethodsArray = (this.includeHttpMethods != null) ? + this.includeHttpMethods.toArray(new HttpMethod[0]) : null; + + HttpMethod[] excludeMethodsArray = (this.excludeHttpMethods != null) ? + this.excludeHttpMethods.toArray(new HttpMethod[0]) : null; + + String[] includePattersArray = StringUtils.toStringArray(this.includePatterns); + + String[] excludePattersArray = StringUtils.toStringArray(this.excludePatterns); + + MappedInterceptor mappedInterceptor = new MappedInterceptor( - StringUtils.toStringArray(this.includePatterns), - StringUtils.toStringArray(this.excludePatterns), + includePattersArray, + excludePattersArray, + includeMethodsArray, + excludeMethodsArray, this.interceptor); if (this.pathMatcher != null) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java index 5071a495383a..31a05bdfc4ee 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java @@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.jspecify.annotations.Nullable; +import org.springframework.http.HttpMethod; import org.springframework.http.server.PathContainer; import org.springframework.util.AntPathMatcher; import org.springframework.util.ObjectUtils; @@ -67,6 +68,10 @@ public final class MappedInterceptor implements HandlerInterceptor { private final PatternAdapter @Nullable [] excludePatterns; + private final MethodAdapter @Nullable [] includeHttpMethods; + + private final MethodAdapter @Nullable [] excludeHttpMethods; + private PathMatcher pathMatcher = defaultPathMatcher; private final HandlerInterceptor interceptor; @@ -78,58 +83,79 @@ public final class MappedInterceptor implements HandlerInterceptor { * @param includePatterns patterns to which requests must match, or null to * match all paths * @param excludePatterns patterns to which requests must not match + * @param includeHttpMethods http methods to which request must match, or null to match all paths + * @param excludeHttpMethods http methods to which request must not match * @param interceptor the target interceptor * @param parser a parser to use to pre-parse patterns into {@link PathPattern}; * when not provided, {@link PathPatternParser#defaultInstance} is used. * @since 5.3 */ - public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, + public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, HttpMethod @Nullable [] includeHttpMethods, HttpMethod @Nullable [] excludeHttpMethods, HandlerInterceptor interceptor, @Nullable PathPatternParser parser) { this.includePatterns = PatternAdapter.initPatterns(includePatterns, parser); this.excludePatterns = PatternAdapter.initPatterns(excludePatterns, parser); + this.includeHttpMethods = MethodAdapter.initHttpMethods(includeHttpMethods); + this.excludeHttpMethods = MethodAdapter.initHttpMethods(excludeHttpMethods); this.interceptor = interceptor; } /** * Variant of - * {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)} + * {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)} * with include patterns only. */ public MappedInterceptor(String @Nullable [] includePatterns, HandlerInterceptor interceptor) { - this(includePatterns, null, interceptor); + this(includePatterns, null, null, null, interceptor); } /** * Variant of - * {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)} + * {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)} + * with include methods only. + */ + public MappedInterceptor(HttpMethod @Nullable [] includeHttpMethods, HandlerInterceptor interceptor) { + this(null, null, includeHttpMethods, null, interceptor); + } + + /** + * Variant of + * {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)} * without a provided parser. */ - public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, + public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, HttpMethod @Nullable [] includeHttpMethods, HttpMethod @Nullable [] excludeHttpMethods, HandlerInterceptor interceptor) { - this(includePatterns, excludePatterns, interceptor, null); + this(includePatterns, excludePatterns,includeHttpMethods,excludeHttpMethods, interceptor, null); } /** * Variant of - * {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)} + * {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)} * with a {@link WebRequestInterceptor} as the target. */ public MappedInterceptor(String @Nullable [] includePatterns, WebRequestInterceptor interceptor) { - this(includePatterns, null, interceptor); + this(includePatterns, null,null,null, interceptor); + } + /** + * Variant of + * {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[], HandlerInterceptor, PathPatternParser)} + * with a {@link WebRequestInterceptor} as the target. + */ + public MappedInterceptor(HttpMethod @Nullable [] includeHttpMethods, WebRequestInterceptor interceptor) { + this(null, null,includeHttpMethods ,null, interceptor); } /** * Variant of - * {@link #MappedInterceptor(String[], String[], HandlerInterceptor, PathPatternParser)} + * {@link #MappedInterceptor(String[], String[], HttpMethod[], HttpMethod[] , HandlerInterceptor, PathPatternParser)} * with a {@link WebRequestInterceptor} as the target. */ - public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, + public MappedInterceptor(String @Nullable [] includePatterns, String @Nullable [] excludePatterns, HttpMethod @Nullable [] includeHttpMethods, HttpMethod @Nullable [] excludeHttpMethods, WebRequestInterceptor interceptor) { - this(includePatterns, excludePatterns, new WebRequestHandlerInterceptorAdapter(interceptor)); + this(includePatterns, excludePatterns,includeHttpMethods,excludeHttpMethods, new WebRequestHandlerInterceptorAdapter(interceptor)); } @@ -202,6 +228,7 @@ public PathMatcher getPathMatcher() { */ public boolean matches(HttpServletRequest request) { Object path = ServletRequestPathUtils.getCachedPath(request); + HttpMethod method = HttpMethod.valueOf(request.getMethod()); if (this.pathMatcher != defaultPathMatcher) { path = path.toString(); } @@ -213,12 +240,45 @@ public boolean matches(HttpServletRequest request) { } } } - if (ObjectUtils.isEmpty(this.includePatterns)) { + if (!ObjectUtils.isEmpty(this.excludeHttpMethods)) { + for (MethodAdapter adapter : this.excludeHttpMethods) { + if (adapter.match(method)){ + return false; + } + } + } + if (ObjectUtils.isEmpty(this.includePatterns) && ObjectUtils.isEmpty(this.includeHttpMethods)) { return true; } - for (PatternAdapter adapter : this.includePatterns) { - if (adapter.match(path, isPathContainer, this.pathMatcher)) { - return true; + if (!ObjectUtils.isEmpty(this.includePatterns) && ObjectUtils.isEmpty(this.includeHttpMethods)) { + for (PatternAdapter adapter : this.includePatterns) { + if (adapter.match(path, isPathContainer, this.pathMatcher)) { + return true; + } + } + } + if (!ObjectUtils.isEmpty(this.includeHttpMethods) && ObjectUtils.isEmpty(this.includePatterns)) { + for (MethodAdapter adapter : this.includeHttpMethods) { + if (adapter.match(method)) { + return true; + } + } + } + if (!ObjectUtils.isEmpty(this.includePatterns) && !ObjectUtils.isEmpty(this.includeHttpMethods)) { + boolean match = false; + for (MethodAdapter methodAdapter : this.includeHttpMethods) { + if (methodAdapter.match(method)) { + match = true; + break; + } + } + if (!match) { + return false; + } + for (PatternAdapter pathAdapter : this.includePatterns) { + if (pathAdapter.match(path, isPathContainer, pathMatcher)) { + return true; + } } } return false; @@ -305,4 +365,40 @@ public boolean match(Object path, boolean isPathContainer, PathMatcher pathMatch } } + /** + * Adapts {@link HttpMethod} instances for internal matching purposes. + * + *

Encapsulates an {@link HttpMethod} and provides matching functionality. + * Also provides a utility method to initialize arrays of {@code MethodAdapter} + * instances from arrays of {@link HttpMethod}.

+ * + * @since 7.0.x + */ + private static class MethodAdapter { + + private final @Nullable HttpMethod httpMethod; + + public MethodAdapter(@Nullable HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public boolean match(HttpMethod method) { + return this.httpMethod == method; + } + + public @Nullable HttpMethod getHttpMethod() { + return this.httpMethod; + } + + private static MethodAdapter @Nullable [] initHttpMethods(HttpMethod @Nullable [] methods) { + if (ObjectUtils.isEmpty(methods)) { + return null; + } + return Arrays.stream(methods) + .map(MethodAdapter::new) + .toArray(MethodAdapter[]::new); + } + + } + }