Explicit coverage of root vs cause exception matching in MVC ref docs

Issue: SPR-16743

(cherry picked from commit a200df6)
This commit is contained in:
Juergen Hoeller
2018-05-07 22:30:26 +02:00
parent d9ff109b0f
commit 318d04cd2b
3 changed files with 83 additions and 100 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -25,7 +25,6 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
@@ -53,13 +52,10 @@ import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConvert
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
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;
@@ -110,10 +106,10 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
*
* <p>This class registers the following {@link HandlerExceptionResolver}s:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
* with {@link org.springframework.web.bind.annotation.ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
* </ul>
@@ -155,6 +151,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public static final String CONTENT_NEGOTIATION_MANAGER_BEAN_NAME = "mvcContentNegotiationManager";
private static final boolean javaxValidationPresent =
ClassUtils.isPresent("javax.validation.Validator",
AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
@@ -210,8 +207,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
configurePathMatchingProperties(handlerMappingDef, element, parserContext);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
@@ -285,44 +282,40 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedCsInterceptorDef);
RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
exceptionHandlerExceptionResolver.setSource(source);
exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(exceptionHandlerExceptionResolver);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(methodExceptionResolver);
if (argumentResolvers != null) {
exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);
String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
readerContext.registerWithGeneratedName(responseStatusExceptionResolver);
RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
statusExceptionResolver.setSource(source);
statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
statusExceptionResolver.getPropertyValues().add("order", 1);
String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName =
readerContext.registerWithGeneratedName(defaultExceptionResolver);
String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
parserContext.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
parserContext.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
@@ -380,8 +373,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
}
}
private RuntimeBeanReference getContentNegotiationManager(Element element, Object source,
ParserContext parserContext) {
private RuntimeBeanReference getContentNegotiationManager(
Element element, Object source, ParserContext parserContext) {
RuntimeBeanReference beanRef;
if (element.hasAttribute("content-negotiation-manager")) {
@@ -402,8 +395,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return beanRef;
}
private void configurePathMatchingProperties(RootBeanDefinition handlerMappingDef, Element element,
ParserContext parserContext) {
private void configurePathMatchingProperties(
RootBeanDefinition handlerMappingDef, Element element, ParserContext parserContext) {
Element pathMatchingElement = DomUtils.getChildElementByTagName(element, "path-matching");
if (pathMatchingElement != null) {
@@ -462,15 +455,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
private String getAsyncTimeout(Element element) {
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
return (asyncElement != null) ? asyncElement.getAttribute("default-timeout") : null;
return (asyncElement != null ? asyncElement.getAttribute("default-timeout") : null);
}
private RuntimeBeanReference getAsyncExecutor(Element element) {
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
if (asyncElement != null) {
if (asyncElement.hasAttribute("task-executor")) {
return new RuntimeBeanReference(asyncElement.getAttribute("task-executor"));
}
if (asyncElement != null && asyncElement.hasAttribute("task-executor")) {
return new RuntimeBeanReference(asyncElement.getAttribute("task-executor"));
}
return null;
}
@@ -571,7 +562,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
}
if (jackson2XmlPresent) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
Class<?> type = MappingJackson2XmlHttpMessageConverter.class;
RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
@@ -582,7 +574,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
}
if (jackson2Present) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
Class<?> type = MappingJackson2HttpMessageConverter.class;
RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
messageConverters.add(jacksonConverterDef);
@@ -609,7 +602,6 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return beanDefinition;
}
private ManagedList<Object> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
ManagedList<Object> list = new ManagedList<Object>();
list.setSource(parserContext.extractSource(parentElement));
@@ -620,27 +612,6 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return list;
}
private ManagedList<BeanReference> extractBeanRefSubElements(Element parentElement, ParserContext parserContext){
ManagedList<BeanReference> list = new ManagedList<BeanReference>();
list.setSource(parserContext.extractSource(parentElement));
for (Element refElement : DomUtils.getChildElementsByTagName(parentElement, "ref")) {
BeanReference reference;
if (StringUtils.hasText("bean")) {
reference = new RuntimeBeanReference(refElement.getAttribute("bean"),false);
list.add(reference);
}
else if (StringUtils.hasText("parent")){
reference = new RuntimeBeanReference(refElement.getAttribute("parent"),true);
list.add(reference);
}
else {
parserContext.getReaderContext().error("'bean' or 'parent' attribute is required for <ref> element",
parserContext.extractSource(parentElement));
}
}
return list;
}
/**
* A FactoryBean for a CompositeUriComponentsContributor that obtains the
@@ -671,7 +642,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
}
@Override
public CompositeUriComponentsContributor getObject() throws Exception {
public CompositeUriComponentsContributor getObject() {
return this.uriComponentsContributor;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -63,8 +63,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;
@@ -134,10 +132,10 @@ import org.springframework.web.util.UrlPathHelper;
* <p>Registers a {@link HandlerExceptionResolverComposite} with this chain of
* exception resolvers:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with
* {@link org.springframework.web.bind.annotation.ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
* </ul>
@@ -865,12 +863,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* A method available to subclasses for adding default {@link HandlerExceptionResolver}s.
* <p>Adds the following exception resolvers:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver}
* for handling exceptions through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver}
* for exceptions annotated with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver}
* for resolving known Spring exception types
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with
* {@link org.springframework.web.bind.annotation.ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types
* </ul>
*/
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {

View File

@@ -3975,11 +3975,11 @@ especially when relying on `@ResponseBody` methods rather than on view resolutio
may be more convenient to directly set the status of the response and optionally write
error content to the body of the response.
You can do that with `@ExceptionHandler` methods. When declared within a controller such
methods apply to exceptions raised by `@RequestMapping` methods of that controller (or
any of its subclasses). You can also declare an `@ExceptionHandler` method within an
`@ControllerAdvice` class in which case it handles exceptions from `@RequestMapping`
methods from many controllers. Below is an example of a controller-local
You can do that with `@ExceptionHandler` methods. When declared within a controller,
such methods apply to exceptions raised by `@RequestMapping` methods of that controller
(or any of its subclasses). You can also declare an `@ExceptionHandler` method within
an `@ControllerAdvice` class in which case it handles exceptions from `@RequestMapping`
methods from many controllers. Below is an example for a controller-local
`@ExceptionHandler` method:
[source,java,indent=0]
@@ -3988,21 +3988,28 @@ methods from many controllers. Below is an example of a controller-local
@Controller
public class SimpleController {
// @RequestMapping methods omitted ...
// ...
@ExceptionHandler(IOException.class)
public ResponseEntity<String> handleIOException(IOException ex) {
// prepare responseEntity
return responseEntity;
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
----
The `@ExceptionHandler` value can be set to an array of Exception types. If an exception
is thrown that matches one of the types in the list, then the method annotated with the
matching `@ExceptionHandler` will be invoked. If the annotation value is not set then
the exception types listed as method arguments are used.
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 `@ExceptionHandler` value can be set to an array of exception types.
If an exception is thrown that matches one of the types in the list, then the method
annotated with the matching `@ExceptionHandler` will be invoked. If the annotation value
is not set, then the declared method parameter type will be used for matching.
[TIP]
====
@@ -4014,18 +4021,26 @@ advice bean. As a consequence, when using a multi-advice arrangement, please dec
primary root exception mappings on a prioritized advice bean with a corresponding order!
====
Much like standard controller methods annotated with a `@RequestMapping` annotation, the
method arguments and return values of `@ExceptionHandler` methods can be flexible. For
example, the `HttpServletRequest` can be accessed in Servlet environments and the
Much like standard controller methods annotated with a `@RequestMapping` annotation,
the method arguments and return values of `@ExceptionHandler` methods can be flexible.
For example, the `HttpServletRequest` can be accessed in Servlet environments and the
`PortletRequest` in Portlet environments. The return type can be a `String`, which is
interpreted as a view name, a `ModelAndView` object, a `ResponseEntity`, or you can also
add the `@ResponseBody` to have the method return value converted with message
converters and written to the response stream.
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.
[[mvc-ann-rest-spring-mvc-exceptions]]
=== Handling Standard Spring MVC Exceptions
Spring MVC may raise a number of exceptions while processing a request. The
`SimpleMappingExceptionResolver` can easily map any exception to a default error view as
needed. However, when working with clients that interpret responses in an automated way