diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java
new file mode 100644
index 0000000000..71aeac2110
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java
@@ -0,0 +1,46 @@
+/*
+ * 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.stereotype.Component;
+
+/**
+ * Indicates the annotated class assists a "Controller".
+ *
+ *
Serves as a specialization of {@link Component @Component}, allowing for
+ * implementation classes to be autodetected through classpath scanning.
+ *
+ *
It is typically used to define {@link ExceptionHandler @ExceptionHandler},
+ * {@link InitBinder @InitBinder}, and {@link ModelAttribute @ModelAttribute}
+ * methods that apply across controller class hierarchies.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Component
+public @interface ControllerAdvice {
+
+}
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
deleted file mode 100644
index e0c07c2a6d..0000000000
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionResolver.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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-web/src/main/java/org/springframework/web/bind/support/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/bind/support/ControllerAdviceBean.java
new file mode 100644
index 0000000000..fd1b204ed4
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/bind/support/ControllerAdviceBean.java
@@ -0,0 +1,136 @@
+/*
+ * 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.support;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+
+/**
+ * Encapsulates information about an {@linkplain ControllerAdvice @ControllerAdvice}
+ * bean without requiring the bean to be instantiated.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class ControllerAdviceBean implements Ordered {
+
+ private final Object bean;
+
+ private final int order;
+
+ private final BeanFactory beanFactory;
+
+
+ /**
+ * Create an instance using the given bean name.
+ * @param beanName the name of the bean
+ * @param beanFactory a BeanFactory that can be used later to resolve the bean
+ */
+ public ControllerAdviceBean(String beanName, BeanFactory beanFactory) {
+ Assert.hasText(beanName, "'beanName' must not be null");
+ Assert.notNull(beanFactory, "'beanFactory' must not be null");
+ Assert.isTrue(beanFactory.containsBean(beanName),
+ "Bean factory [" + beanFactory + "] does not contain bean " + "with name [" + beanName + "]");
+ this.bean = beanName;
+ this.beanFactory = beanFactory;
+ this.order = initOrder(this.beanFactory.getType(beanName));
+ }
+
+ /**
+ * Create an instance using the given bean instance.
+ * @param bean the bean
+ */
+ public ControllerAdviceBean(Object bean) {
+ Assert.notNull(bean, "'bean' must not be null");
+ this.bean = bean;
+ this.order = initOrder(bean.getClass());
+ this.beanFactory = null;
+ }
+
+ private static int initOrder(Class> beanType) {
+ Order orderAnnot = AnnotationUtils.findAnnotation(beanType, Order.class);
+ return (orderAnnot != null) ? orderAnnot.value() : Ordered.LOWEST_PRECEDENCE;
+ }
+
+ /**
+ * Find the names of beans annotated with
+ * {@linkplain ControllerAdvice @ControllerAdvice} in the given
+ * ApplicationContext and wrap them as {@code ControllerAdviceBean} instances.
+ */
+ public static List findBeans(ApplicationContext applicationContext) {
+ List beans = new ArrayList();
+ for (String name : applicationContext.getBeanDefinitionNames()) {
+ if (applicationContext.findAnnotationOnBean(name, ControllerAdvice.class) != null) {
+ beans.add(new ControllerAdviceBean(name, applicationContext));
+ }
+ }
+ return beans;
+ }
+
+ /**
+ * Return a bean instance if necessary resolving the bean name through the BeanFactory.
+ */
+ public Object resolveBean() {
+ return (this.bean instanceof String) ? this.beanFactory.getBean((String) this.bean) : this.bean;
+ }
+
+ public int getOrder() {
+ return this.order;
+ }
+
+ /**
+ * Returns the type of the contained bean.
+ * If the bean type is a CGLIB-generated class, the original, user-defined class is returned.
+ */
+ public Class> getBeanType() {
+ Class> clazz = (this.bean instanceof String)
+ ? this.beanFactory.getType((String) this.bean) : this.bean.getClass();
+
+ return ClassUtils.getUserClass(clazz);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o != null && o instanceof ControllerAdviceBean) {
+ ControllerAdviceBean other = (ControllerAdviceBean) o;
+ return this.bean.equals(other.bean);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * this.bean.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return bean.toString();
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java
index 1445c5b695..22dd998229 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java
@@ -22,7 +22,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.ExceptionDepthComparator;
@@ -34,57 +33,47 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.HandlerMethodSelector;
/**
- * Given a set of @{@link ExceptionHandler} methods at initialization, finds
- * the best matching method mapped to an exception at runtime.
- *
- *
Exception mappings are extracted from the method @{@link ExceptionHandler}
- * annotation or by looking for {@link Throwable} method arguments.
- *
+ * Discovers {@linkplain ExceptionHandler @ExceptionHandler} methods in a given class
+ * type, including all super types, and helps to resolve an Exception to the method
+ * its mapped to. Exception mappings are defined through {@code @ExceptionHandler}
+ * annotation or by looking at the signature of an {@code @ExceptionHandler} method.
+ *
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ExceptionHandlerMethodResolver {
private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis");
-
- private final Map, Method> mappedMethods =
+
+ private final Map, Method> mappedMethods =
new ConcurrentHashMap, Method>();
private final Map, Method> exceptionLookupCache =
new ConcurrentHashMap, Method>();
/**
- * A constructor that finds {@link ExceptionHandler} methods in a handler.
- * @param handlerType the handler to inspect for exception handler methods.
- * @throws IllegalStateException
- * If an exception type is mapped to two methods.
- * @throws IllegalArgumentException
- * If an @{@link ExceptionHandler} method is not mapped to any exceptions.
+ * A constructor that finds {@link ExceptionHandler} methods in the given type.
+ * @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class> handlerType) {
- init(HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS));
- }
-
- private void init(Set exceptionHandlerMethods) {
- for (Method method : exceptionHandlerMethods) {
- for (Class extends Throwable> exceptionType : detectMappedExceptions(method)) {
+ for (Method method : HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
+ for (Class extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
/**
- * Detect the exceptions an @{@link ExceptionHandler} method is mapped to.
- * If the method @{@link ExceptionHandler} annotation doesn't have any,
- * scan the method signature for all arguments of type {@link Throwable}.
+ * Extract exception mappings from the {@code @ExceptionHandler} annotation
+ * first and as a fall-back from the method signature.
*/
@SuppressWarnings("unchecked")
- private List> detectMappedExceptions(Method method) {
+ private List> detectExceptionMappings(Method method) {
List> result = new ArrayList>();
+
ExceptionHandler annotation = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
- if (annotation != null) {
- result.addAll(Arrays.asList(annotation.value()));
- }
+ result.addAll(Arrays.asList(annotation.value()));
+
if (result.isEmpty()) {
for (Class> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
@@ -92,7 +81,9 @@ public class ExceptionHandlerMethodResolver {
}
}
}
+
Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
+
return result;
}
@@ -106,10 +97,17 @@ public class ExceptionHandlerMethodResolver {
}
/**
- * Find a method to handle the given exception. If more than one match is
- * found, the best match is selected via {@link ExceptionDepthComparator}.
+ * Whether the contained type has any exception mappings.
+ */
+ public boolean hasExceptionMappings() {
+ return (this.mappedMethods.size() > 0);
+ }
+
+ /**
+ * Find a method to handle the given exception.
+ * Use {@link ExceptionDepthComparator} if more than one match is found.
* @param exception the exception
- * @return an @{@link ExceptionHandler} method, or {@code null}
+ * @return a method to handle the exception or {@code null}
*/
public Method resolveMethod(Exception exception) {
Class extends Exception> exceptionType = exception.getClass();
@@ -122,7 +120,7 @@ public class ExceptionHandlerMethodResolver {
}
/**
- * Return the method mapped to the exception type, or {@code null}.
+ * Return the method mapped to the given exception type or {@code null}.
*/
private Method getMappedMethod(Class extends Exception> exceptionType) {
List> matches = new ArrayList>();
@@ -141,7 +139,7 @@ public class ExceptionHandlerMethodResolver {
}
/**
- * A filter for selecting @{@link ExceptionHandler} methods.
+ * A filter for selecting {@code @ExceptionHandler} methods.
*/
public final static MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java
index 4481118b3c..1f31c9f172 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java
@@ -26,16 +26,16 @@ import org.springframework.web.servlet.ModelAndView;
* Abstract base class for
* {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver}
* implementations that support handling exceptions from handlers of type {@link HandlerMethod}.
- *
+ *
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
/**
- * Checks if the handler is a {@link HandlerMethod} instance and performs the check against the bean
- * instance it contains. If the provided handler is not an instance of {@link HandlerMethod},
- * {@code false} is returned instead.
+ * Checks if the handler is a {@link HandlerMethod} and then delegates to the
+ * base class implementation of {@link #shouldApplyTo(HttpServletRequest, Object)}
+ * passing the bean of the {@code HandlerMethod}. Otherwise returns {@code false}.
*/
@Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
@@ -51,7 +51,7 @@ public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHan
return false;
}
}
-
+
@Override
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response,
@@ -59,7 +59,7 @@ public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHan
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
-
+
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
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 741a48b53a..eca11c04f0 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
@@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -34,16 +33,15 @@ 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.core.OrderComparator;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
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.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.support.ControllerAdviceBean;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
@@ -82,11 +80,11 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
- private final Map, ExceptionHandlerMethodResolver> exceptionHandlersByType =
+ private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap, ExceptionHandlerMethodResolver>();
- private final Map