diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
new file mode 100644
index 00000000000..69586992c2a
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2009 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;
+
+/**
+ * Annotation for handling exceptions in specific handler classes and/or
+ * handler methods. Provides consistent style between Servlet and Portlet
+ * environments, with the semantics adapting to the concrete environment.
+ *
+ *
Handler methods which are annotated with this annotation are allowed
+ * to have very flexible signatures. They may have arguments of the following
+ * types, in arbitrary order:
+ *
+ * - Request and/or response objects (Servlet API or Portlet API).
+ * You may choose any specific request/response type, e.g.
+ * {@link javax.servlet.ServletRequest} / {@link javax.servlet.http.HttpServletRequest}
+ * or {@link javax.portlet.PortletRequest} / {@link javax.portlet.ActionRequest} /
+ * {@link javax.portlet.RenderRequest}. Note that in the Portlet case,
+ * an explicitly declared action/render argument is also used for mapping
+ * specific request types onto a handler method (in case of no other
+ * information given that differentiates between action and render requests).
+ *
- Session object (Servlet API or Portlet API): either
+ * {@link javax.servlet.http.HttpSession} or {@link javax.portlet.PortletSession}.
+ * An argument of this type will enforce the presence of a corresponding session.
+ * As a consequence, such an argument will never be
null.
+ * Note that session access may not be thread-safe, in particular in a
+ * Servlet environment: Consider switching the
+ * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#setSynchronizeOnSession "synchronizeOnSession"}
+ * flag to "true" if multiple requests are allowed to access a session concurrently.
+ * - {@link org.springframework.web.context.request.WebRequest} or
+ * {@link org.springframework.web.context.request.NativeWebRequest}.
+ * Allows for generic request parameter access as well as request/session
+ * attribute access, without ties to the native Servlet/Portlet API.
+ *
- {@link java.util.Locale} for the current request locale
+ * (determined by the most specific locale resolver available,
+ * i.e. the configured {@link org.springframework.web.servlet.LocaleResolver}
+ * in a Servlet environment and the portal locale in a Portlet environment).
+ *
- {@link java.io.InputStream} / {@link java.io.Reader} for access
+ * to the request's content. This will be the raw InputStream/Reader as
+ * exposed by the Servlet/Portlet API.
+ *
- {@link java.io.OutputStream} / {@link java.io.Writer} for generating
+ * the response's content. This will be the raw OutputStream/Writer as
+ * exposed by the Servlet/Portlet API.
+ *
+ *
+ * The following return types are supported for handler methods:
+ *
+ * - A
ModelAndView object (Servlet MVC or Portlet MVC).
+ * - A {@link org.springframework.ui.Model Model} object, with the view name
+ * implicitly determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
+ *
- A {@link java.util.Map} object for exposing a model,
+ * with the view name implicitly determined through a
+ * {@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.
+ *
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}
+ * / {@link javax.portlet.RenderResponse} for that purpose)
+ * or if the view name is supposed to be implicitly determined through a
+ * {@link org.springframework.web.servlet.RequestToViewNameTranslator}
+ * (not declaring a response argument in the handler method signature;
+ * only applicable in a Servlet environment).
+ *
+ *
+ * NOTE: @RequestMapping will only be processed if a
+ * corresponding HandlerMapping (for type level annotations)
+ * and/or HandlerAdapter (for method level annotations) is
+ * present in the dispatcher. This is the case by default in both
+ * DispatcherServlet and DispatcherPortlet.
+ * However, if you are defining custom HandlerMappings or
+ * HandlerAdapters, then you need to make sure that a
+ * corresponding custom DefaultAnnotationHandlerMapping
+ * and/or AnnotationMethodHandlerAdapter is defined as well
+ * - provided that you intend to use @RequestMapping.
+ *
+ * @author Arjen Poutsma
+ * @see org.springframework.web.context.request.WebRequest
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver
+ * @since 3.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ExceptionHandler {
+
+ /**
+ * Exceptions handled by the annotation method. If empty, will default to any exceptions listed in the method
+ * argument list.
+ */
+ Class extends Throwable>[] value() default {};
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties
index 634ec163e3a..c3c46327538 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties
@@ -13,8 +13,9 @@ org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.m
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
-org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.handler.DefaultHandlerExceptionResolver,\
- org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
+org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
+ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
+ org.springframework.web.servlet.handler.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
new file mode 100644
index 00000000000..bbbaec44b3c
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2002-2009 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.servlet.mvc.annotation;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.ui.Model;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
+import org.springframework.web.servlet.support.RequestContextUtils;
+
+/**
+ * Implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver} interface that handles
+ * exceptions through the {@link ExceptionHandler} annotation.
+ *
This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ */
+public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
+
+ private WebArgumentResolver[] customArgumentResolvers;
+
+ /**
+ * Set a custom ArgumentResolvers to use for special method parameter types. Such a custom ArgumentResolver will kick
+ * in first, having a chance to resolve an argument value before the standard argument handling kicks in.
+ */
+ public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
+ this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
+ }
+
+ /**
+ * Set one or more custom ArgumentResolvers to use for special method parameter types. Any such custom ArgumentResolver
+ * will kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
+ */
+ public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
+ this.customArgumentResolvers = argumentResolvers;
+ }
+
+ @Override
+ protected ModelAndView doResolveException(HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler,
+ Exception ex) {
+ if (handler != null) {
+ Method handlerMethod = findBestExceptionHandlerMethod(handler, ex);
+ if (handlerMethod != null) {
+ NativeWebRequest webRequest = new ServletWebRequest(request, response);
+ try {
+ Object[] args = resolveHandlerArguments(handlerMethod, handler, webRequest, ex);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Invoking request handler method: " + handlerMethod);
+ }
+ Object retVal = doInvokeMethod(handlerMethod, handler, args);
+ return getModelAndView(retVal);
+ }
+ catch (Exception invocationEx) {
+ logger.error("Invoking request method resulted in exception : " + handlerMethod, invocationEx);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds the handler method that matches the thrown exception best.
+ *
+ * @param handler the handler object
+ * @param thrownException the exception to be handled
+ * @return the best matching method; or null if none is found
+ */
+ private Method findBestExceptionHandlerMethod(Object handler, final Exception thrownException) {
+ final Class> handlerType = handler.getClass();
+ final Class extends Throwable> thrownExceptionType = thrownException.getClass();
+
+ final Map, Method> resolverMethods =
+ new LinkedHashMap, Method>();
+
+ ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
+ public void doWith(Method method) {
+ method = ClassUtils.getMostSpecificMethod(method, handlerType);
+ List> handledExceptions = getHandledExceptions(method);
+ for (Class extends Throwable> handledException : handledExceptions) {
+ if (handledException.isAssignableFrom(thrownExceptionType)) {
+ if (!resolverMethods.containsKey(handledException)) {
+ resolverMethods.put(handledException, method);
+ }
+ else {
+ Method oldMappedMethod = resolverMethods.get(handledException);
+ throw new IllegalStateException(
+ "Ambiguous exception handler mapped for " + handledException + "]: {" +
+ oldMappedMethod + ", " + method + "}.");
+
+ }
+ }
+ }
+ }
+ });
+ return getBestMatchingMethod(thrownException, resolverMethods);
+ }
+
+ /**
+ * Returns all the exception classes handled by the given method.
+ * Default implementation looks for exceptions in the {@linkplain ExceptionHandler#value() annotation}, or -
+ * if that annotation element is empty - any exceptions listed in the method parameters if the method is annotated
+ * with {@code @ExceptionHandler}.
+ *
+ * @param method the method
+ * @return the handled exceptions
+ */
+ @SuppressWarnings("unchecked")
+ protected List> getHandledExceptions(Method method) {
+ List> result = new ArrayList>();
+ ExceptionHandler exceptionHandler = method.getAnnotation(ExceptionHandler.class);
+ if (exceptionHandler != null) {
+ if (!ObjectUtils.isEmpty(exceptionHandler.value())) {
+ result.addAll(Arrays.asList(exceptionHandler.value()));
+ }
+ else {
+ for (Class> param : method.getParameterTypes()) {
+ if (Throwable.class.isAssignableFrom(param)) {
+ result.add((Class extends Throwable>) param);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /** Returns the best matching method. Uses the {@link DepthComparator}. */
+ private Method getBestMatchingMethod(Exception thrownException,
+ Map, Method> resolverMethods) {
+ if (!resolverMethods.isEmpty()) {
+ List> handledExceptions =
+ new ArrayList>(resolverMethods.keySet());
+ Collections.sort(handledExceptions, new DepthComparator(thrownException));
+ Class extends Throwable> bestMatchMethod = handledExceptions.get(0);
+ return resolverMethods.get(bestMatchMethod);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /** Resolves the arguments for the given method. Delegates to {@link #resolveCommonArgument}. */
+ private Object[] resolveHandlerArguments(Method handlerMethod,
+ Object handler,
+ NativeWebRequest webRequest,
+ Exception thrownException) throws Exception {
+
+ Class[] paramTypes = handlerMethod.getParameterTypes();
+ Object[] args = new Object[paramTypes.length];
+
+ Class> handlerType = handler.getClass();
+
+ for (int i = 0; i < args.length; i++) {
+ MethodParameter methodParam = new MethodParameter(handlerMethod, i);
+ GenericTypeResolver.resolveParameterType(methodParam, handlerType);
+
+ Class paramType = methodParam.getParameterType();
+
+ Object argValue = resolveCommonArgument(methodParam, webRequest, thrownException);
+ if (argValue != WebArgumentResolver.UNRESOLVED) {
+ args[i] = argValue;
+ }
+ else {
+ throw new IllegalStateException(
+ "Unsupported argument [" + paramType.getName() + "] for @ExceptionHandler method: " +
+ handlerMethod);
+ }
+ }
+ return args;
+ }
+
+ /**
+ * Resolves common method arguments. Delegates to registered {@link #setCustomArgumentResolver(WebArgumentResolver) argumentResolvers} first,
+ * then checking {@link #resolveStandardArgument}.
+ *
+ * @param methodParameter the method parameter
+ * @param webRequest the request
+ * @param thrownException the exception thrown
+ * @return the argument value, or {@link WebArgumentResolver#UNRESOLVED}
+ */
+ protected Object resolveCommonArgument(MethodParameter methodParameter,
+ NativeWebRequest webRequest,
+ Exception thrownException) throws Exception {
+ // Invoke custom argument resolvers if present...
+ if (this.customArgumentResolvers != null) {
+ for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {
+ Object value = argumentResolver.resolveArgument(methodParameter, webRequest);
+ if (value != WebArgumentResolver.UNRESOLVED) {
+ return value;
+ }
+ }
+ }
+
+ // Resolution of standard parameter types...
+ Class paramType = methodParameter.getParameterType();
+ Object value = resolveStandardArgument(paramType, webRequest, thrownException);
+ if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) {
+ throw new IllegalStateException(
+ "Standard argument type [" + paramType.getName() + "] resolved to incompatible value of type [" +
+ (value != null ? value.getClass() : null) +
+ "]. Consider declaring the argument type in a less specific fashion.");
+ }
+ return value;
+ }
+
+ /**
+ * Resolves standard method arguments. Default implementation handles {@link NativeWebRequest},
+ * {@link ServletRequest}, {@link ServletResponse}, {@link HttpSession}, {@link Principal}, {@link Locale},
+ * request {@link InputStream}, request {@link Reader}, response {@link OutputStream}, response {@link Writer},
+ * and the given {@code thrownException}.
+ *
+ * @param parameterType the method parameter type
+ * @param webRequest the request
+ * @param thrownException the exception thrown
+ * @return the argument value, or {@link WebArgumentResolver#UNRESOLVED}
+ */
+ protected Object resolveStandardArgument(Class parameterType,
+ NativeWebRequest webRequest,
+ Exception thrownException) throws Exception {
+ if (WebRequest.class.isAssignableFrom(parameterType)) {
+ return webRequest;
+ }
+
+ HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
+ HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();
+
+ if (ServletRequest.class.isAssignableFrom(parameterType)) {
+ return request;
+ }
+ else if (ServletResponse.class.isAssignableFrom(parameterType)) {
+ return response;
+ }
+ else if (HttpSession.class.isAssignableFrom(parameterType)) {
+ return request.getSession();
+ }
+ else if (Principal.class.isAssignableFrom(parameterType)) {
+ return request.getUserPrincipal();
+ }
+ else if (Locale.class.equals(parameterType)) {
+ return RequestContextUtils.getLocale(request);
+ }
+ else if (InputStream.class.isAssignableFrom(parameterType)) {
+ return request.getInputStream();
+ }
+ else if (Reader.class.isAssignableFrom(parameterType)) {
+ return request.getReader();
+ }
+ else if (OutputStream.class.isAssignableFrom(parameterType)) {
+ return response.getOutputStream();
+ }
+ else if (Writer.class.isAssignableFrom(parameterType)) {
+ return response.getWriter();
+ }
+ else if (parameterType.isInstance(thrownException)) {
+ return thrownException;
+ }
+ else {
+ return WebArgumentResolver.UNRESOLVED;
+
+ }
+ }
+
+ private Object doInvokeMethod(Method method, Object target, Object[] args) throws Exception {
+ ReflectionUtils.makeAccessible(method);
+ try {
+ return method.invoke(target, args);
+ }
+ catch (InvocationTargetException ex) {
+ ReflectionUtils.rethrowException(ex.getTargetException());
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ @SuppressWarnings("unchecked")
+ private ModelAndView getModelAndView(Object returnValue) {
+ if (returnValue instanceof ModelAndView) {
+ return (ModelAndView) returnValue;
+ }
+ else if (returnValue instanceof Model) {
+ return new ModelAndView().addAllObjects(((Model) returnValue).asMap());
+ }
+ else if (returnValue instanceof Map) {
+ return new ModelAndView().addAllObjects((Map) returnValue);
+ }
+ else if (returnValue instanceof View) {
+ return new ModelAndView((View) returnValue);
+ }
+ else if (returnValue instanceof String) {
+ return new ModelAndView((String) returnValue);
+ }
+ else if (returnValue == null) {
+ return null;
+ }
+ else {
+ throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
+ }
+ }
+
+ /** Comparator capable of sorting exceptions based on their depth from the thrown exception type. */
+ private static class DepthComparator implements Comparator> {
+
+ private final Class extends Throwable> handlerExceptionType;
+
+ private DepthComparator(Exception handlerException) {
+ this.handlerExceptionType = handlerException.getClass();
+ }
+
+ public int compare(Class extends Throwable> o1, Class extends Throwable> o2) {
+ int depth1 = getDepth(o1, 0);
+ int depth2 = getDepth(o2, 0);
+
+ return depth2 - depth1;
+ }
+
+ private int getDepth(Class exceptionType, int depth) {
+ if (exceptionType.equals(handlerExceptionType)) {
+ // Found it!
+ return depth;
+ }
+ // If we've gone as far as we can go and haven't found it...
+ if (Throwable.class.equals(exceptionType)) {
+ return -1;
+ }
+ return getDepth(exceptionType.getSuperclass(), depth + 1);
+ }
+
+ }
+}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java
new file mode 100644
index 00000000000..9c8fcc3ee9f
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2009 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.servlet.mvc.annotation;
+
+import java.io.IOException;
+import java.net.BindException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+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.servlet.ModelAndView;
+
+/** @author Arjen Poutsma */
+public class AnnotationMethodHandlerExceptionResolverTests {
+
+ private AnnotationMethodHandlerExceptionResolver exceptionResolver;
+
+ private MockHttpServletRequest request;
+
+ private MockHttpServletResponse response;
+
+ @Before
+ public void setUp() {
+ exceptionResolver = new AnnotationMethodHandlerExceptionResolver();
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ request.setMethod("GET");
+ }
+
+ @Test
+ public void simple() {
+ BindException ex = new BindException();
+ SimpleController controller = new SimpleController();
+ ModelAndView mav = exceptionResolver.resolveException(request, response, controller, ex);
+ assertNotNull("No ModelAndView returned", mav);
+ assertEquals("Invalid view name returned", "BindException", mav.getViewName());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void ambiguous() {
+ IllegalArgumentException ex = new IllegalArgumentException();
+ AmbiguousController controller = new AmbiguousController();
+ exceptionResolver.resolveException(request, response, controller, ex);
+ }
+
+ @Controller
+ private static class SimpleController {
+
+ @ExceptionHandler(IOException.class)
+ public String handleIOException(IOException ex, HttpServletRequest request) {
+ return ClassUtils.getShortName(ex.getClass());
+ }
+
+ @ExceptionHandler(BindException.class)
+ public String handleBindException(Exception ex, HttpServletResponse response) {
+ return ClassUtils.getShortName(ex.getClass());
+ }
+
+ @ExceptionHandler(IllegalArgumentException.class)
+ public String handleIllegalArgumentException(Exception ex) {
+ return ClassUtils.getShortName(ex.getClass());
+ }
+
+ }
+
+ @Controller
+ private static class AmbiguousController {
+
+ @ExceptionHandler({BindException.class, IllegalArgumentException.class})
+ public String handle1(Exception ex, HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ return ClassUtils.getShortName(ex.getClass());
+ }
+
+ @ExceptionHandler
+ public String handle2(IllegalArgumentException ex) {
+ return ClassUtils.getShortName(ex.getClass());
+ }
+
+ }
+}