From c846198e4697f2ac5d79f0f4f62d25fa7d62fa26 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 11 Jun 2012 14:34:44 -0400 Subject: [PATCH] Add support for global @ExceptionHandler methods Before this change @ExceptionHandler methods could be located in and apply locally within a controller. The change makes it possible to have such methods applicable globally regardless of the controller that raised the exception. The easiest way to do that is to add them to a class annotated with `@ExceptionResolver`, a new annotation that is also an `@Component` annotation (and therefore works with component scanning). It is also possible to register classes containing `@ExceptionHandler` methods directly with the ExceptionHandlerExceptionResolver. When multiple `@ExceptionResolver` classes are detected, or registered directly, the order in which they're used depends on the the `@Order` annotation (if present) or on the value of the order field (if the Ordered interface is implemented). Issue: SPR-9112 --- .../web/bind/annotation/ExceptionHandler.java | 9 ++ .../bind/annotation/ExceptionResolver.java | 57 ++++++++ .../ExceptionHandlerExceptionResolver.java | 101 +++++++++++-- ...xceptionHandlerExceptionResolverTests.java | 106 ++++++++++++-- src/dist/changelog.txt | 2 + src/reference/docbook/mvc.xml | 133 ++++++++++++------ 6 files changed, 334 insertions(+), 74 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionResolver.java diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java index b86ef728b5..806e452bb5 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java @@ -76,6 +76,15 @@ import java.lang.annotation.Target; * {@link org.springframework.web.servlet.RequestToViewNameTranslator}. *
  • A {@link org.springframework.web.servlet.View} object. *
  • A {@link java.lang.String} value which is interpreted as view name. + *
  • {@link ResponseBody @ResponseBody} annotated methods (Servlet-only) + * to set the response content. The return value will be converted to the + * response stream using + * {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}. + *
  • An {@link org.springframework.http.HttpEntity HttpEntity<?>} or + * {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object + * (Servlet-only) to set response headers and content. The ResponseEntity body + * will be converted and written to the response stream using + * {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}. *
  • void if the method handles the response itself (by * writing the response content directly, declaring an argument of type * {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse} diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionResolver.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionResolver.java new file mode 100644 index 0000000000..e0c07c2a6d --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionResolver.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * An {@linkplain Component @Component} annotation that indicates the annotated class + * contains {@linkplain ExceptionHandler @ExceptionHandler} methods. Such methods + * will be used in addition to {@code @ExceptionHandler} methods in + * {@code @Controller}-annotated classes. + * + *

    In order for the the annotation to detected, an instance of + * {@code org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver} + * is configured. + * + *

    Classes with this annotation may use the {@linkplain Order @Order} annotation + * or implement the {@link Ordered} interface to indicate the order in which they + * should be used relative to other such annotated components. However, note that + * the order is only for components registered through {@code @ExceptionResolver}, + * i.e. within an + * {@code org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}. + * + * @author Rossen Stoyanchev + * @since 3.2 + * + * @see org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface ExceptionResolver { + +} \ No newline at end of file diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index c105ba1d82..9f4a90f090 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -18,15 +18,23 @@ package org.springframework.web.servlet.mvc.method.annotation; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Source; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; @@ -34,6 +42,7 @@ import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ExceptionResolver; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver; @@ -49,6 +58,8 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver; +import edu.emory.mathcs.backport.java.util.Collections; + /** * An {@link AbstractHandlerMethodExceptionResolver} that resolves exceptions * through {@code @ExceptionHandler} methods. @@ -62,7 +73,7 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionRes * @since 3.1 */ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements - InitializingBean { + InitializingBean, ApplicationContextAware { private List customArgumentResolvers; @@ -72,13 +83,18 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); - private final Map, ExceptionHandlerMethodResolver> exceptionHandlerMethodResolvers = - new ConcurrentHashMap, ExceptionHandlerMethodResolver>(); + private final Map, ExceptionHandlerMethodResolver> exceptionHandlersByType = + new ConcurrentHashMap, ExceptionHandlerMethodResolver>(); + + private final Map globalExceptionHandlers = + new LinkedHashMap(); private HandlerMethodArgumentResolverComposite argumentResolvers; private HandlerMethodReturnValueHandlerComposite returnValueHandlers; + private ApplicationContext applicationContext; + /** * Default constructor. */ @@ -193,6 +209,22 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce this.contentNegotiationManager = contentNegotiationManager; } + /** + * Provide instances of objects with {@link ExceptionHandler @ExceptionHandler} + * methods to apply globally, i.e. regardless of the selected controller. + *

    {@code @ExceptionHandler} methods in the controller are always looked + * up before {@code @ExceptionHandler} methods in global handlers. + */ + public void setGlobalExceptionHandlers(Object... handlers) { + for (Object handler : handlers) { + this.globalExceptionHandlers.put(handler, new ExceptionHandlerMethodResolver(handler.getClass())); + } + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + public void afterPropertiesSet() { if (this.argumentResolvers == null) { List resolvers = getDefaultArgumentResolvers(); @@ -202,6 +234,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce List handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } + initGlobalExceptionHandlers(); } /** @@ -255,6 +288,36 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce return handlers; } + private void initGlobalExceptionHandlers() { + if (this.applicationContext == null) { + logger.warn("Can't detect @ExceptionResolver components if the ApplicationContext property is not set"); + } + else { + String[] beanNames = this.applicationContext.getBeanNamesForType(Object.class); + for (String name : beanNames) { + Class type = this.applicationContext.getType(name); + if (AnnotationUtils.findAnnotation(type , ExceptionResolver.class) != null) { + Object bean = this.applicationContext.getBean(name); + this.globalExceptionHandlers.put(bean, new ExceptionHandlerMethodResolver(bean.getClass())); + } + } + } + if (this.globalExceptionHandlers.size() > 0) { + sortGlobalExceptionHandlers(); + } + } + + private void sortGlobalExceptionHandlers() { + Map handlersCopy = + new HashMap(this.globalExceptionHandlers); + List handlers = new ArrayList(handlersCopy.keySet()); + Collections.sort(handlers, new AnnotationAwareOrderComparator()); + this.globalExceptionHandlers.clear(); + for (Object handler : handlers) { + this.globalExceptionHandlers.put(handler, handlersCopy.get(handler)); + } + } + /** * Find an @{@link ExceptionHandler} method and invoke it to handle the * raised exception. @@ -307,24 +370,32 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce * @return a method to handle the exception, or {@code null} */ protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { - if (handlerMethod == null) { - return null; + if (handlerMethod != null) { + Class handlerType = handlerMethod.getBeanType(); + ExceptionHandlerMethodResolver resolver = this.exceptionHandlersByType.get(handlerType); + if (resolver == null) { + resolver = new ExceptionHandlerMethodResolver(handlerType); + this.exceptionHandlersByType.put(handlerType, resolver); + } + Method method = resolver.resolveMethod(exception); + if (method != null) { + return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); + } } - Class handlerType = handlerMethod.getBeanType(); - Method method = getExceptionHandlerMethodResolver(handlerType).resolveMethod(exception); - return (method != null ? new ServletInvocableHandlerMethod(handlerMethod.getBean(), method) : null); + return getGlobalExceptionHandlerMethod(exception); } /** - * Return a method resolver for the given handler type, never {@code null}. + * Return a global {@code @ExceptionHandler} method for the given exception or {@code null}. */ - private ExceptionHandlerMethodResolver getExceptionHandlerMethodResolver(Class handlerType) { - ExceptionHandlerMethodResolver resolver = this.exceptionHandlerMethodResolvers.get(handlerType); - if (resolver == null) { - resolver = new ExceptionHandlerMethodResolver(handlerType); - this.exceptionHandlerMethodResolvers.put(handlerType, resolver); + private ServletInvocableHandlerMethod getGlobalExceptionHandlerMethod(Exception exception) { + for (Entry entry : this.globalExceptionHandlers.entrySet()) { + Method method = entry.getValue().resolveMethod(exception); + if (method != null) { + return new ServletInvocableHandlerMethod(entry.getKey(), method); + } } - return resolver; + return null; } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java index bef84ec23b..806cc3cb5d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -30,11 +30,17 @@ import java.util.Arrays; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ExceptionResolver; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.annotation.ModelMethodProcessor; @@ -44,7 +50,7 @@ import org.springframework.web.servlet.ModelAndView; /** * Test fixture with {@link ExceptionHandlerExceptionResolver}. - * + * * @author Rossen Stoyanchev * @author Arjen Poutsma * @since 3.1 @@ -52,7 +58,7 @@ import org.springframework.web.servlet.ModelAndView; public class ExceptionHandlerExceptionResolverTests { private static int RESOLVER_COUNT; - + private static int HANDLER_COUNT; private ExceptionHandlerExceptionResolver resolver; @@ -63,12 +69,12 @@ public class ExceptionHandlerExceptionResolverTests { @BeforeClass public static void setupOnce() { - ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); - resolver.afterPropertiesSet(); - RESOLVER_COUNT = resolver.getArgumentResolvers().getResolvers().size(); - HANDLER_COUNT = resolver.getReturnValueHandlers().getHandlers().size(); + ExceptionHandlerExceptionResolver r = new ExceptionHandlerExceptionResolver(); + r.afterPropertiesSet(); + RESOLVER_COUNT = r.getArgumentResolvers().getResolvers().size(); + HANDLER_COUNT = r.getReturnValueHandlers().getHandlers().size(); } - + @Before public void setUp() throws Exception { this.resolver = new ExceptionHandlerExceptionResolver(); @@ -89,7 +95,7 @@ public class ExceptionHandlerExceptionResolverTests { HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver(); this.resolver.setCustomArgumentResolvers(Arrays.asList(resolver)); this.resolver.afterPropertiesSet(); - + assertTrue(this.resolver.getArgumentResolvers().getResolvers().contains(resolver)); assertMethodProcessorCount(RESOLVER_COUNT + 1, HANDLER_COUNT); } @@ -112,7 +118,7 @@ public class ExceptionHandlerExceptionResolverTests { assertTrue(this.resolver.getReturnValueHandlers().getHandlers().contains(handler)); assertMethodProcessorCount(RESOLVER_COUNT, HANDLER_COUNT + 1); } - + @Test public void setReturnValueHandlers() { HandlerMethodReturnValueHandler handler = new ModelMethodProcessor(); @@ -151,7 +157,7 @@ public class ExceptionHandlerExceptionResolverTests { HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); this.resolver.afterPropertiesSet(); ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); - + assertNotNull(mav); assertTrue(mav.isEmpty()); assertEquals("IllegalArgumentException", this.response.getContentAsString()); @@ -169,7 +175,41 @@ public class ExceptionHandlerExceptionResolverTests { assertEquals("IllegalArgumentException", this.response.getContentAsString()); } - + @Test + public void resolveExceptionGlobalHandler() throws UnsupportedEncodingException, NoSuchMethodException { + AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class); + this.resolver.setApplicationContext(cxt); + this.resolver.setGlobalExceptionHandlers(new GlobalExceptionHandler()); + this.resolver.afterPropertiesSet(); + + IllegalStateException ex = new IllegalStateException(); + HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); + + assertNotNull("Exception was not handled", mav); + assertTrue(mav.isEmpty()); + assertEquals("IllegalStateException", this.response.getContentAsString()); + } + + @Test + public void resolveExceptionGlobalHandlerOrdered() throws UnsupportedEncodingException, NoSuchMethodException { + AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class); + this.resolver.setApplicationContext(cxt); + GlobalExceptionHandler globalHandler = new GlobalExceptionHandler(); + globalHandler.setOrder(2); + this.resolver.setGlobalExceptionHandlers(globalHandler); + this.resolver.afterPropertiesSet(); + + IllegalStateException ex = new IllegalStateException(); + HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); + + assertNotNull("Exception was not handled", mav); + assertTrue(mav.isEmpty()); + assertEquals("@ExceptionResolver: IllegalStateException", this.response.getContentAsString()); + } + + private void assertMethodProcessorCount(int resolverCount, int handlerCount) { assertEquals(resolverCount, this.resolver.getArgumentResolvers().getResolvers().size()); assertEquals(handlerCount, this.resolver.getReturnValueHandlers().getHandlers().size()); @@ -185,7 +225,7 @@ public class ExceptionHandlerExceptionResolverTests { return new ModelAndView("errorView", "detail", ex.getMessage()); } } - + @Controller static class ResponseWriterController { @@ -204,7 +244,7 @@ public class ExceptionHandlerExceptionResolverTests { @ExceptionHandler @ResponseBody - public String handleException(Exception ex) { + public String handleException(IllegalArgumentException ex) { return ClassUtils.getShortName(ex.getClass()); } } @@ -219,4 +259,42 @@ public class ExceptionHandlerExceptionResolverTests { } } + static class GlobalExceptionHandler implements Ordered { + + private int order; + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + @ExceptionHandler + @ResponseBody + public String handleException(IllegalStateException ex) { + return ClassUtils.getShortName(ex.getClass()); + } + } + + @ExceptionResolver + @Order(1) + static class AnnotatedExceptionResolver { + + @ExceptionHandler + @ResponseBody + public String handleException(IllegalStateException ex) { + return "@ExceptionResolver: " + ClassUtils.getShortName(ex.getClass()); + } + } + + @Configuration + static class MyConfig { + + @Bean public AnnotatedExceptionResolver exceptionResolver() { + return new AnnotatedExceptionResolver(); + } + } + } \ No newline at end of file diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 029d5ff4b4..7c9e6dd11b 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -18,6 +18,8 @@ Changes in version 3.2 M2 (2012-08-xx) * added support for the HTTP PATCH method to Spring MVC and to RestTemplate (SPR-7985) * enable smart suffix pattern match in @RequestMapping methods (SPR-7632) * DispatcherPortlet does not forward event exceptions to the render phase by default (SPR-9287) +* add defaultCharset property to StringHttpMessageConverter +* add @ExceptionResolver annotation to detect classes with @ExceptionHandler methods Changes in version 3.2 M1 (2012-05-28) diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 3b58280cb1..e739e677ec 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -3620,18 +3620,91 @@ public String onSubmit(@RequestPart("meta-data") MetaData HandlerExceptionResolver interface, which is only a matter of implementing the resolveException(Exception, Handler) method and - returning a ModelAndView, you may also use the + returning a ModelAndView, you may also use the provided SimpleMappingExceptionResolver. This resolver enables you to take the class name of any exception that might be thrown and map it to a view name. This is functionally equivalent to the exception mapping feature from the Servlet API, but it is also possible to implement more finely grained mappings of exceptions from different handlers. + + +
    + <interfacename>@ExceptionHandler</interfacename> + + The HandlerExceptionResolver interface + and the SimpleMappingExceptionResolver implementations + allow you to map Exceptions to specific views along with some Java logic + before forwarding to those views. However, in some cases, especially when + working with programmatic clients (Ajax or non-browser) it is more + convenient to set the status and optionally write error information to the + response body. + + For that you can use @ExceptionHandler + methods. When present within a controller such methods apply to exceptions + raised by that contoroller or any of its sub-classes. + Or you can also declare @ExceptionHandler + methods in a type annotated with @ExceptionResolver + in which case they apply globally. + The @ExceptionResolver annotation is + a component annotation that can also be used with a component scan. + + + Here is an example with a controller-level + @ExceptionHandler method: + + @Controller +public class SimpleController { + + // other controller method omitted + + @ExceptionHandler(IOException.class) + public ResponseEntity handleIOException(IOException ex) { + // prepare responseEntity + return responseEntity; + } +} + + The @ExceptionHandler value can be set to + an array of Exception types. If an exception is thrown 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. + + 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. + + To better understand how @ExceptionHandler + methods work, consider that in Spring MVC there is only one abstraction + for handling exception and that's the + HandlerExceptionResolver. There is a special + implementation of that interface, + the ExceptionHandlerExceptionResolver, which detects + and invokes @ExceptionHandler methods. +
    + +
    + <classname>DefaultHandlerExceptionResolver</classname> + and <classname>ResponseStatusExceptionResolver</classname> By default, the DispatcherServlet registers the DefaultHandlerExceptionResolver. This resolver handles certain standard Spring MVC exceptions by setting a - specific response status code: + specific response status code. This is useful when responding to programmatic + clients (e.g. Ajax, non-browser) in which the client uses the response code + to interpret the result. + @@ -3697,51 +3770,21 @@ public String onSubmit(@RequestPart("meta-data") MetaData - -
    + + -
    - <interfacename>@ExceptionHandler</interfacename> + The DispatcherServlet also registers the + ResponseStatusExceptionResolver, which handles + exceptions annotated with @ResponseStatus + by setting the response status code to that indicated in the annotation. + Once again this is useful in scenarios with programmatic clients. - An alternative to the - HandlerExceptionResolver interface is the - @ExceptionHandler annotation. You use the - @ExceptionHandler method annotation within a - controller to specify which method is invoked when an exception of a - specific type is thrown during the execution of controller methods. For - example: - - @Controller -public class SimpleController { - - // other controller method omitted - - @ExceptionHandler(IOException.class) - public String handleIOException(IOException ex, HttpServletRequest request) { - return ClassUtils.getShortName(ex.getClass()); - } -} - - will invoke the 'handlerIOException' method when a - java.io.IOException is thrown. - - The @ExceptionHandler value can be set to - an array of Exception types. If an exception is thrown 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. - - Much like standard controller methods annotated with a - @RequestMapping annotation, the method arguments - and return values of @ExceptionHandler methods - are very 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 or a - ModelAndView object. Refer to the API - documentation for more details. + Note however that if you explicitly register one or more + HandlerExceptionResolver instances in your configuration + then the defaults registered by the DispatcherServlet are + cancelled. This is standard behavior with regards to + DispatcherServlet defaults. + See for more details.