SPR-4677: Allow annotated methods on a Controller to be marked as exception handlers
This commit is contained in:
@@ -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.
|
||||
*
|
||||
* <p>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:
|
||||
* <ul>
|
||||
* <li>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).
|
||||
* <li>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 <code>null</code>.
|
||||
* <i>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.</i>
|
||||
* <li>{@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.
|
||||
* <li>{@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).
|
||||
* <li>{@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.
|
||||
* <li>{@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.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The following return types are supported for handler methods:
|
||||
* <ul>
|
||||
* <li>A <code>ModelAndView</code> object (Servlet MVC or Portlet MVC).
|
||||
* <li>A {@link org.springframework.ui.Model Model} object, with the view name
|
||||
* implicitly determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
|
||||
* <li>A {@link java.util.Map} object for exposing a model,
|
||||
* with the view name implicitly determined through a
|
||||
* {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
|
||||
* <li>A {@link org.springframework.web.servlet.View} object.
|
||||
* <li>A {@link java.lang.String} value which is interpreted as view name.
|
||||
* <li><code>void</code> 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).
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>NOTE: <code>@RequestMapping</code> will only be processed if a
|
||||
* corresponding <code>HandlerMapping</code> (for type level annotations)
|
||||
* and/or <code>HandlerAdapter</code> (for method level annotations) is
|
||||
* present in the dispatcher.</b> This is the case by default in both
|
||||
* <code>DispatcherServlet</code> and <code>DispatcherPortlet</code>.
|
||||
* However, if you are defining custom <code>HandlerMappings</code> or
|
||||
* <code>HandlerAdapters</code>, then you need to make sure that a
|
||||
* corresponding custom <code>DefaultAnnotationHandlerMapping</code>
|
||||
* and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well
|
||||
* - provided that you intend to use <code>@RequestMapping</code>.
|
||||
*
|
||||
* @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 {};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>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 <code>null</code> 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<Class<? extends Throwable>, Method> resolverMethods =
|
||||
new LinkedHashMap<Class<? extends Throwable>, Method>();
|
||||
|
||||
ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) {
|
||||
method = ClassUtils.getMostSpecificMethod(method, handlerType);
|
||||
List<Class<? extends Throwable>> 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.
|
||||
* <p>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<Class<? extends Throwable>> getHandledExceptions(Method method) {
|
||||
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
|
||||
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<Class<? extends Throwable>, Method> resolverMethods) {
|
||||
if (!resolverMethods.isEmpty()) {
|
||||
List<Class<? extends Throwable>> handledExceptions =
|
||||
new ArrayList<Class<? extends Throwable>>(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<Class<? extends Throwable>> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user