From a200df6c8d68474be3ad3ebcedeb3c0c397e5e29 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 7 May 2018 22:30:26 +0200 Subject: [PATCH] Explicit coverage of root vs cause exception matching in MVC ref docs Issue: SPR-16743 --- .../AnnotationDrivenBeanDefinitionParser.java | 8 +- .../WebMvcConfigurationSupport.java | 21 +++--- src/docs/asciidoc/web/webflux.adoc | 32 ++++---- src/docs/asciidoc/web/webmvc.adoc | 74 ++++++++++++++++--- 4 files changed, 90 insertions(+), 45 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index 3175b8ffb40..7808f0996fc 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -63,8 +63,6 @@ import org.springframework.util.xml.DomUtils; import org.springframework.web.HttpRequestHandler; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationManagerFactoryBean; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.method.support.CompositeUriComponentsContributor; @@ -115,10 +113,10 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv * *

This class registers the following {@link HandlerExceptionResolver}s: *

diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 7bf0322dcb9..8ce220f0260 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -67,8 +67,6 @@ import org.springframework.validation.Validator; import org.springframework.web.HttpRequestHandler; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.ServletContextAware; import org.springframework.web.cors.CorsConfiguration; @@ -136,10 +134,10 @@ import org.springframework.web.util.UrlPathHelper; *

Registers a {@link HandlerExceptionResolverComposite} with this chain of * exception resolvers: *

@@ -926,12 +924,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv * A method available to subclasses for adding default {@link HandlerExceptionResolver}s. *

Adds the following exception resolvers: *

*/ protected final void addDefaultHandlerExceptionResolvers(List exceptionResolvers) { diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index 69182744f5d..e0046bfc753 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -2406,33 +2406,31 @@ controller-specific ``Formatter``'s: public ResponseEntity handle(IOException ex) { // ... } - } ---- -The annotation can list the exception types to match. Or simply declare the target -exception as a method argument as shown above. When multiple exception methods match, -a root exception match is generally preferred to a cause exception match. More formally -the `ExceptionDepthComparator` is used to sort exceptions based on their depth from the -thrown exception type. +The exception may match against a top-level exception being propagated (i.e. a direct +`IOException` thrown), or against the immediate cause within a top-level wrapper exception +(e.g. an `IOException` wrapped inside an `IllegalStateException`). -In a multi-`@ControllerAdvice` arrangement, please declare your primary root exception -mappings on a `@ControllerAdvice` prioritized with a corresponding order. While a root -exception match is preferred to a cause, this is mainly among the methods of a given -controller or `@ControllerAdvice`. That means a cause match on a higher-priority -`@ControllerAdvice` is preferred to any match (e.g. root) on a lower-priority -`@ControllerAdvice`. +For matching exception types, preferably declare the target exception as a method argument +as shown above. Alternatively, the annotation declaration may narrow the exception types to +match. We generally recommend to be as specific as possible in the argument signature and to +declare your primary root exception mappings on a `@ControllerAdvice` prioritized with a +corresponding order. See <> for details. + +[NOTE] +==== +An `@ExceptionHandler` method in WebFlux supports the same method arguments and +return values as an `@RequestMapping` method, with the exception of request body +and `@ModelAttribute` related method arguments. +==== Support for `@ExceptionHandler` methods in Spring WebFlux is provided by the `HandlerAdapter` for `@RequestMapping` methods. See <> under the `DispatcherHandler` section for more details. -An `@ExceptionHandler` method in WebFlux supports the same method arguments and return -values as an `@RequestMapping` method does with the exception of request body and -`@ModelAttribute` related method arguments. - - [[webflux-ann-rest-exceptions]] ==== REST API exceptions [.small]#<># diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index a7f5f3011b9..2c74b2947ef 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -481,7 +481,6 @@ initialization parameters ( `init-param` elements) to the Servlet declaration in Note that if <> is also configured, then unresolved requests are always forwarded to the default servlet and a 404 would never be raised. - |=== @@ -2830,22 +2829,75 @@ controller-specific ``Formatter``'s: public ResponseEntity handle(IOException ex) { // ... } - } ---- -The annotation can list the exception types to match. Or simply declare the target -exception as a method argument as shown above. When multiple exception methods match, -a root exception match is generally preferred to a cause exception match. More formally -the `ExceptionDepthComparator` is used to sort exceptions based on their depth from the -thrown exception type. +The exception may match against a top-level exception being propagated (i.e. a direct +`IOException` thrown), or against the immediate cause within a top-level wrapper exception +(e.g. an `IOException` wrapped inside an `IllegalStateException`). + +For matching exception types, preferably declare the target exception as a method argument +as shown above. When multiple exception methods match, a root exception match is generally +preferred to a cause exception match. More specifically, the `ExceptionDepthComparator` is +used to sort exceptions based on their depth from the thrown exception type. + +Alternatively, the annotation declaration may narrow the exception types to match: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @ExceptionHandler({FileSystemException.class, RemoteException.class}) + public ResponseEntity handle(IOException ex) { + // ... + } +---- + +Or even a list of specific exception types with a very generic argument signature: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @ExceptionHandler({FileSystemException.class, RemoteException.class}) + public ResponseEntity handle(Exception ex) { + // ... + } +---- + +[NOTE] +==== +The distinction between root and cause exception matching can be surprising: + +In the `IOException` variant above, the method will typically be called with +the actual `FileSystemException` or `RemoteException` instance as the argument +since both of them extend from `IOException`. However, if any such matching +exception is propagated within a wrapper exception which is an `IOException` +itself, the passed-in exception instance will be that wrapper exception. + +The behavior is even simpler in the `handle(Exception)` variant: This will +always be invoked with the wrapper exception in a wrapping scenario, with the +actually matching exception to be found through `ex.getCause()` in that case. +The passed-in exception will only be the actual `FileSystemException` or +`RemoteException` instance when these are thrown as top-level exceptions. +==== + +We generally recommend to be as specific as possible in the argument signature, +reducing the potential for mismatches between root and cause exception types. +Consider breaking a multi-matching method into individual `@ExceptionHandler` +methods, each matching a single specific exception type through its signature. In a multi-`@ControllerAdvice` arrangement, please declare your primary root exception mappings on a `@ControllerAdvice` prioritized with a corresponding order. While a root -exception match is preferred to a cause, this is mainly among the methods of a given -controller or `@ControllerAdvice`. That means a cause match on a higher-priority -`@ControllerAdvice` is preferred to any match (e.g. root) on a lower-priority -`@ControllerAdvice`. +exception match is preferred to a cause, this is defined among the methods of a given +controller or `@ControllerAdvice` class. This means a cause match on a higher-priority +`@ControllerAdvice` bean is preferred to any match (e.g. root) on a lower-priority +`@ControllerAdvice` bean. + +Last but not least, an `@ExceptionHandler` method implementation may choose to back +out of dealing with a given exception instance by rethrowing it in its original form. +This is useful in scenarios where you are only interested in root-level matches or in +matches within a specific context that cannot be statically determined. A rethrown +exception will be propagated through the remaining resolution chain, just like if +the given `@ExceptionHandler` method would not have matched in the first place. Support for `@ExceptionHandler` methods in Spring MVC is built on the `DispatcherServlet` level, <> mechanism.