Skip to content

Commit f2cc70e

Browse files
committed
Explicit coverage of root vs cause exception matching in MVC ref docs
Issue: SPR-16743 (cherry picked from commit a200df6)
1 parent e211559 commit f2cc70e

File tree

4 files changed

+89
-67
lines changed

4 files changed

+89
-67
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.springframework.beans.factory.InitializingBean;
2828
import org.springframework.beans.factory.config.BeanDefinition;
2929
import org.springframework.beans.factory.config.BeanDefinitionHolder;
30-
import org.springframework.beans.factory.config.BeanReference;
3130
import org.springframework.beans.factory.config.RuntimeBeanReference;
3231
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
3332
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
@@ -60,13 +59,10 @@
6059
import org.springframework.lang.Nullable;
6160
import org.springframework.util.Assert;
6261
import org.springframework.util.ClassUtils;
63-
import org.springframework.util.StringUtils;
6462
import org.springframework.util.xml.DomUtils;
6563
import org.springframework.web.HttpRequestHandler;
6664
import org.springframework.web.accept.ContentNegotiationManager;
6765
import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
68-
import org.springframework.web.bind.annotation.ExceptionHandler;
69-
import org.springframework.web.bind.annotation.ResponseStatus;
7066
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
7167
import org.springframework.web.bind.support.WebArgumentResolver;
7268
import org.springframework.web.method.support.CompositeUriComponentsContributor;
@@ -117,10 +113,10 @@
117113
*
118114
* <p>This class registers the following {@link HandlerExceptionResolver}s:
119115
* <ul>
120-
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
121-
* through @{@link ExceptionHandler} methods.
116+
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
117+
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
122118
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
123-
* with @{@link ResponseStatus}.
119+
* with {@link org.springframework.web.bind.annotation.ResponseStatus}.
124120
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
125121
* exception types
126122
* </ul>
@@ -664,27 +660,6 @@ private ManagedList<Object> extractBeanSubElements(Element parentElement, Parser
664660
return list;
665661
}
666662

667-
private ManagedList<BeanReference> extractBeanRefSubElements(Element parentElement, ParserContext parserContext){
668-
ManagedList<BeanReference> list = new ManagedList<>();
669-
list.setSource(parserContext.extractSource(parentElement));
670-
for (Element refElement : DomUtils.getChildElementsByTagName(parentElement, "ref")) {
671-
BeanReference reference;
672-
if (StringUtils.hasText("bean")) {
673-
reference = new RuntimeBeanReference(refElement.getAttribute("bean"),false);
674-
list.add(reference);
675-
}
676-
else if (StringUtils.hasText("parent")){
677-
reference = new RuntimeBeanReference(refElement.getAttribute("parent"),true);
678-
list.add(reference);
679-
}
680-
else {
681-
parserContext.getReaderContext().error("'bean' or 'parent' attribute is required for <ref> element",
682-
parserContext.extractSource(parentElement));
683-
}
684-
}
685-
return list;
686-
}
687-
688663

689664
/**
690665
* A FactoryBean for a CompositeUriComponentsContributor that obtains the

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@
6767
import org.springframework.web.HttpRequestHandler;
6868
import org.springframework.web.accept.ContentNegotiationManager;
6969
import org.springframework.web.bind.WebDataBinder;
70-
import org.springframework.web.bind.annotation.ExceptionHandler;
71-
import org.springframework.web.bind.annotation.ResponseStatus;
7270
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
7371
import org.springframework.web.context.ServletContextAware;
7472
import org.springframework.web.cors.CorsConfiguration;
@@ -136,10 +134,10 @@
136134
* <p>Registers a {@link HandlerExceptionResolverComposite} with this chain of
137135
* exception resolvers:
138136
* <ul>
139-
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
140-
* through @{@link ExceptionHandler} methods.
141-
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
142-
* with @{@link ResponseStatus}.
137+
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
138+
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
139+
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with
140+
* {@link org.springframework.web.bind.annotation.ResponseStatus}.
143141
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
144142
* exception types
145143
* </ul>
@@ -926,12 +924,11 @@ protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> ex
926924
* A method available to subclasses for adding default {@link HandlerExceptionResolver}s.
927925
* <p>Adds the following exception resolvers:
928926
* <ul>
929-
* <li>{@link ExceptionHandlerExceptionResolver}
930-
* for handling exceptions through @{@link ExceptionHandler} methods.
931-
* <li>{@link ResponseStatusExceptionResolver}
932-
* for exceptions annotated with @{@link ResponseStatus}.
933-
* <li>{@link DefaultHandlerExceptionResolver}
934-
* for resolving known Spring exception types
927+
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
928+
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
929+
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with
930+
* {@link org.springframework.web.bind.annotation.ResponseStatus}.
931+
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types
935932
* </ul>
936933
*/
937934
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {

src/docs/asciidoc/web/webflux.adoc

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2406,33 +2406,31 @@ controller-specific ``Formatter``'s:
24062406
public ResponseEntity<String> handle(IOException ex) {
24072407
// ...
24082408
}
2409-
24102409
}
24112410
----
24122411

2413-
The annotation can list the exception types to match. Or simply declare the target
2414-
exception as a method argument as shown above. When multiple exception methods match,
2415-
a root exception match is generally preferred to a cause exception match. More formally
2416-
the `ExceptionDepthComparator` is used to sort exceptions based on their depth from the
2417-
thrown exception type.
2412+
The exception may match against a top-level exception being propagated (i.e. a direct
2413+
`IOException` thrown), or against the immediate cause within a top-level wrapper exception
2414+
(e.g. an `IOException` wrapped inside an `IllegalStateException`).
24182415

2419-
In a multi-`@ControllerAdvice` arrangement, please declare your primary root exception
2420-
mappings on a `@ControllerAdvice` prioritized with a corresponding order. While a root
2421-
exception match is preferred to a cause, this is mainly among the methods of a given
2422-
controller or `@ControllerAdvice`. That means a cause match on a higher-priority
2423-
`@ControllerAdvice` is preferred to any match (e.g. root) on a lower-priority
2424-
`@ControllerAdvice`.
2416+
For matching exception types, preferably declare the target exception as a method argument
2417+
as shown above. Alternatively, the annotation declaration may narrow the exception types to
2418+
match. We generally recommend to be as specific as possible in the argument signature and to
2419+
declare your primary root exception mappings on a `@ControllerAdvice` prioritized with a
2420+
corresponding order. See <<web.adoc#mvc-ann-exceptionhandler,the MVC section>> for details.
2421+
2422+
[NOTE]
2423+
====
2424+
An `@ExceptionHandler` method in WebFlux supports the same method arguments and
2425+
return values as an `@RequestMapping` method, with the exception of request body
2426+
and `@ModelAttribute` related method arguments.
2427+
====
24252428

24262429
Support for `@ExceptionHandler` methods in Spring WebFlux is provided by the
24272430
`HandlerAdapter` for `@RequestMapping` methods. See <<webflux-dispatcher-exceptions>>
24282431
under the `DispatcherHandler` section for more details.
24292432

24302433

2431-
An `@ExceptionHandler` method in WebFlux supports the same method arguments and return
2432-
values as an `@RequestMapping` method does with the exception of request body and
2433-
`@ModelAttribute` related method arguments.
2434-
2435-
24362434
[[webflux-ann-rest-exceptions]]
24372435
==== REST API exceptions
24382436
[.small]#<<web.adoc#mvc-ann-rest-exceptions,Same in Spring MVC>>#

src/docs/asciidoc/web/webmvc.adoc

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,6 @@ initialization parameters ( `init-param` elements) to the Servlet declaration in
481481
Note that if <<mvc-default-servlet-handler,default servlet handling>> is
482482
also configured, then unresolved requests are always forwarded to the default servlet
483483
and a 404 would never be raised.
484-
485484
|===
486485

487486

@@ -2830,22 +2829,75 @@ controller-specific ``Formatter``'s:
28302829
public ResponseEntity<String> handle(IOException ex) {
28312830
// ...
28322831
}
2832+
}
2833+
----
2834+
2835+
The exception may match against a top-level exception being propagated (i.e. a direct
2836+
`IOException` thrown), or against the immediate cause within a top-level wrapper exception
2837+
(e.g. an `IOException` wrapped inside an `IllegalStateException`).
2838+
2839+
For matching exception types, preferably declare the target exception as a method argument
2840+
as shown above. When multiple exception methods match, a root exception match is generally
2841+
preferred to a cause exception match. More specifically, the `ExceptionDepthComparator` is
2842+
used to sort exceptions based on their depth from the thrown exception type.
2843+
2844+
Alternatively, the annotation declaration may narrow the exception types to match:
2845+
2846+
[source,java,indent=0]
2847+
[subs="verbatim,quotes"]
2848+
----
2849+
@ExceptionHandler({FileSystemException.class, RemoteException.class})
2850+
public ResponseEntity<String> handle(IOException ex) {
2851+
// ...
2852+
}
2853+
----
2854+
2855+
Or even a list of specific exception types with a very generic argument signature:
28332856

2857+
[source,java,indent=0]
2858+
[subs="verbatim,quotes"]
2859+
----
2860+
@ExceptionHandler({FileSystemException.class, RemoteException.class})
2861+
public ResponseEntity<String> handle(Exception ex) {
2862+
// ...
28342863
}
28352864
----
28362865

2837-
The annotation can list the exception types to match. Or simply declare the target
2838-
exception as a method argument as shown above. When multiple exception methods match,
2839-
a root exception match is generally preferred to a cause exception match. More formally
2840-
the `ExceptionDepthComparator` is used to sort exceptions based on their depth from the
2841-
thrown exception type.
2866+
[NOTE]
2867+
====
2868+
The distinction between root and cause exception matching can be surprising:
2869+
2870+
In the `IOException` variant above, the method will typically be called with
2871+
the actual `FileSystemException` or `RemoteException` instance as the argument
2872+
since both of them extend from `IOException`. However, if any such matching
2873+
exception is propagated within a wrapper exception which is an `IOException`
2874+
itself, the passed-in exception instance will be that wrapper exception.
2875+
2876+
The behavior is even simpler in the `handle(Exception)` variant: This will
2877+
always be invoked with the wrapper exception in a wrapping scenario, with the
2878+
actually matching exception to be found through `ex.getCause()` in that case.
2879+
The passed-in exception will only be the actual `FileSystemException` or
2880+
`RemoteException` instance when these are thrown as top-level exceptions.
2881+
====
2882+
2883+
We generally recommend to be as specific as possible in the argument signature,
2884+
reducing the potential for mismatches between root and cause exception types.
2885+
Consider breaking a multi-matching method into individual `@ExceptionHandler`
2886+
methods, each matching a single specific exception type through its signature.
28422887

28432888
In a multi-`@ControllerAdvice` arrangement, please declare your primary root exception
28442889
mappings on a `@ControllerAdvice` prioritized with a corresponding order. While a root
2845-
exception match is preferred to a cause, this is mainly among the methods of a given
2846-
controller or `@ControllerAdvice`. That means a cause match on a higher-priority
2847-
`@ControllerAdvice` is preferred to any match (e.g. root) on a lower-priority
2848-
`@ControllerAdvice`.
2890+
exception match is preferred to a cause, this is defined among the methods of a given
2891+
controller or `@ControllerAdvice` class. This means a cause match on a higher-priority
2892+
`@ControllerAdvice` bean is preferred to any match (e.g. root) on a lower-priority
2893+
`@ControllerAdvice` bean.
2894+
2895+
Last but not least, an `@ExceptionHandler` method implementation may choose to back
2896+
out of dealing with a given exception instance by rethrowing it in its original form.
2897+
This is useful in scenarios where you are only interested in root-level matches or in
2898+
matches within a specific context that cannot be statically determined. A rethrown
2899+
exception will be propagated through the remaining resolution chain, just like if
2900+
the given `@ExceptionHandler` method would not have matched in the first place.
28492901

28502902
Support for `@ExceptionHandler` methods in Spring MVC is built on the `DispatcherServlet`
28512903
level, <<mvc-exceptionhandlers,HandlerExceptionResolver>> mechanism.

0 commit comments

Comments
 (0)