Skip to content

Commit dd23b1d

Browse files
committed
Add mappedHandler Predicate to AbstractHandlerExceptionResolver
Closes gh-26772
1 parent 7534090 commit dd23b1d

File tree

2 files changed

+49
-14
lines changed

2 files changed

+49
-14
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java

+34-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.servlet.handler;
1818

1919
import java.util.Set;
20+
import java.util.function.Predicate;
2021

2122
import jakarta.servlet.http.HttpServletRequest;
2223
import jakarta.servlet.http.HttpServletResponse;
@@ -53,6 +54,9 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
5354

5455
private int order = Ordered.LOWEST_PRECEDENCE;
5556

57+
@Nullable
58+
private Predicate<Object> mappedHandlerPredicate;
59+
5660
@Nullable
5761
private Set<?> mappedHandlers;
5862

@@ -74,33 +78,43 @@ public int getOrder() {
7478
return this.order;
7579
}
7680

81+
/**
82+
* Use a {@code Predicate} to determine which handlers this exception
83+
* resolver applies to, including when the request was not mapped in which
84+
* case the handler is {@code null}.
85+
* <p>If no handler predicate, nor handlers, nor handler classes are set,
86+
* the exception resolver applies to all handlers.
87+
* @since 6.1.2
88+
*/
89+
public void setMappedHandlerPredicate(Predicate<Object> predicate) {
90+
this.mappedHandlerPredicate =
91+
(this.mappedHandlerPredicate != null ? this.mappedHandlerPredicate.and(predicate) : predicate);
92+
}
93+
7794
/**
7895
* Specify the set of handlers that this exception resolver should apply to.
79-
* <p>The exception mappings and the default error view will only apply to the specified handlers.
80-
* <p>If no handlers or handler classes are set, the exception mappings and the default error
81-
* view will apply to all handlers. This means that a specified default error view will be used
82-
* as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
83-
* ignored in this case.
96+
* <p>If no handler predicate, nor handlers, nor handler classes are set,
97+
* the exception resolver applies to all handlers.
98+
* @see #setMappedHandlerPredicate(Predicate)
8499
*/
85100
public void setMappedHandlers(Set<?> mappedHandlers) {
86101
this.mappedHandlers = mappedHandlers;
87102
}
88103

89104
/**
90105
* Specify the set of classes that this exception resolver should apply to.
91-
* <p>The exception mappings and the default error view will only apply to handlers of the
92-
* specified types; the specified types may be interfaces or superclasses of handlers as well.
93-
* <p>If no handlers or handler classes are set, the exception mappings and the default error
94-
* view will apply to all handlers. This means that a specified default error view will be used
95-
* as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
96-
* ignored in this case.
106+
* The resolver will only apply to handlers of the specified types; the
107+
* specified types may be interfaces or superclasses of handlers as well.
108+
* <p>If no handler predicate, nor handlers, nor handler classes are set,
109+
* the exception resolver applies to all handlers.
110+
* @see #setMappedHandlerPredicate(Predicate)
97111
*/
98112
public void setMappedHandlerClasses(Class<?>... mappedHandlerClasses) {
99113
this.mappedHandlerClasses = mappedHandlerClasses;
100114
}
101115

102116
/**
103-
* Add a mapped handler class.
117+
* Alternative to {@link #setMappedHandlerClasses(Class[])}.
104118
* @since 6.1
105119
*/
106120
public void addMappedHandlerClass(Class<?> mappedHandlerClass) {
@@ -177,6 +191,7 @@ public ModelAndView resolveException(
177191
/**
178192
* Check whether this resolver is supposed to apply to the given handler.
179193
* <p>The default implementation checks against the configured
194+
* {@linkplain #setMappedHandlerPredicate(Predicate) handlerPredicate}
180195
* {@linkplain #setMappedHandlers handlers} and
181196
* {@linkplain #setMappedHandlerClasses handler classes}, if any.
182197
* @param request current HTTP request
@@ -188,6 +203,9 @@ public ModelAndView resolveException(
188203
* @see #setMappedHandlerClasses
189204
*/
190205
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
206+
if (this.mappedHandlerPredicate != null) {
207+
return this.mappedHandlerPredicate.test(handler);
208+
}
191209
if (handler != null) {
192210
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
193211
return true;
@@ -205,11 +223,13 @@ protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object han
205223

206224
/**
207225
* Whether there are any handler mappings registered via
208-
* {@link #setMappedHandlers(Set)} or {@link #setMappedHandlerClasses(Class[])}.
226+
* {@link #setMappedHandlers(Set)}, {@link #setMappedHandlerClasses(Class[])}, or
227+
* {@link #setMappedHandlerPredicate(Predicate)}.
209228
* @since 5.3
210229
*/
211230
protected boolean hasHandlerMappings() {
212-
return (this.mappedHandlers != null || this.mappedHandlerClasses != null);
231+
return (this.mappedHandlers != null || this.mappedHandlerClasses != null ||
232+
this.mappedHandlerPredicate != null);
213233
}
214234

215235
/**

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java

+15
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,21 @@ void resolveExceptionViaMappedHandler() throws Exception {
385385
assertExceptionHandledAsBody(mav, "DefaultTestExceptionResolver: IllegalStateException");
386386
}
387387

388+
@Test // gh-26772
389+
void resolveExceptionViaMappedHandlerPredicate() throws Exception {
390+
Object handler = new Object();
391+
392+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
393+
this.resolver.setMappedHandlerPredicate(h -> h == handler);
394+
this.resolver.setApplicationContext(ctx);
395+
this.resolver.afterPropertiesSet();
396+
397+
ModelAndView mav = this.resolver.resolveException(
398+
this.request, this.response, handler, new IllegalStateException());
399+
400+
assertExceptionHandledAsBody(mav, "DefaultTestExceptionResolver: IllegalStateException");
401+
}
402+
388403

389404
private void assertMethodProcessorCount(int resolverCount, int handlerCount) {
390405
assertThat(this.resolver.getArgumentResolvers().getResolvers()).hasSize(resolverCount);

0 commit comments

Comments
 (0)