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
index 6201383ffb..e5bac276c2 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -16,6 +16,7 @@
package org.springframework.web.bind.annotation;
+import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -34,7 +35,21 @@ import org.springframework.stereotype.Component;
* {@link InitBinder @InitBinder}, and {@link ModelAttribute @ModelAttribute}
* methods that apply to all {@link RequestMapping @RequestMapping} methods.
*
+ *
One of {@link #annotations()}, {@link #basePackageClasses()},
+ * {@link #basePackages()} or its alias {@link #value()}
+ * may be specified to define specific subsets of Controllers
+ * to assist. When multiple selectors are applied, OR logic is applied -
+ * meaning selected Controllers should match at least one selector.
+ *
+ *
The default behavior (i.e. if used without any selector),
+ * the {@code @ControllerAdvice} annotated class will
+ * assist all known Controllers.
+ *
+ *
Note that those checks are done at runtime, so adding many attributes and using
+ * multiple strategies may have negative impacts (complexity, performance).
+ *
* @author Rossen Stoyanchev
+ * @author Brian Clozel
* @since 3.2
*/
@Target(ElementType.TYPE)
@@ -43,4 +58,62 @@ import org.springframework.stereotype.Component;
@Component
public @interface ControllerAdvice {
+ /**
+ * Alias for the {@link #basePackages()} attribute.
+ * Allows for more concise annotation declarations e.g.:
+ * {@code @ControllerAdvice("org.my.pkg")} is equivalent to
+ * {@code @ControllerAdvice(basePackages="org.my.pkg")}.
+ *
+ * @since 4.0
+ */
+ String[] value() default {};
+
+ /**
+ * Array of base packages.
+ * Controllers that belong to those base packages will be included, e.g.:
+ * {@code @ControllerAdvice(basePackages="org.my.pkg")} or
+ * {@code @ControllerAdvice(basePackages={"org.my.pkg","org.my.other.pkg"})}
+ *
+ *
{@link #value()} is an alias for this attribute.
+ *
Also consider using {@link #basePackageClasses()} as a type-safe
+ * alternative to String-based package names.
+ *
+ * @since 4.0
+ */
+ String[] basePackages() default {};
+
+ /**
+ * Type-safe alternative to {@link #value()} for specifying the packages
+ * to select Controllers to be assisted by the {@code @ControllerAdvice}
+ * annotated class.
+ *
+ *
Consider creating a special no-op marker class or interface in each package
+ * that serves no purpose other than being referenced by this attribute.
+ *
+ * @since 4.0
+ */
+ Class>[] basePackageClasses() default {};
+
+ /**
+ * Array of classes.
+ * Controllers that are assignable to at least one of the given types
+ * will be assisted by the {@code @ControllerAdvice} annotated class.
+ *
+ * @since 4.0
+ */
+ Class>[] assignableTypes() default {};
+
+
+ /**
+ * Array of annotations.
+ * Controllers that are annotated with this/one of those annotation(s)
+ * will be assisted by the {@code @ControllerAdvice} annotated class.
+ *
+ *
Consider creating a special annotation or use a predefined one,
+ * like {@link RestController @RestController}.
+ *
+ * @since 4.0
+ */
+ Class extends Annotation>[] annotations() default {};
+
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
index eeefa75044..b82f31bd36 100644
--- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
+++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -15,9 +15,13 @@
*/
package org.springframework.web.method;
+import java.lang.annotation.Annotation;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
@@ -25,6 +29,7 @@ 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.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
/**
@@ -36,16 +41,25 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
* any object, including ones without an {@code @ControllerAdvice}.
*
* @author Rossen Stoyanchev
+ * @author Brian Clozel
* @since 3.2
*/
public class ControllerAdviceBean implements Ordered {
+ private static final Log logger = LogFactory.getLog(ControllerAdviceBean.class);
+
private final Object bean;
private final int order;
private final BeanFactory beanFactory;
+ private final List basePackages = new ArrayList();
+
+ private final List> annotations = new ArrayList>();
+
+ private final List> assignableTypes = new ArrayList>();
+
/**
* Create an instance using the given bean name.
@@ -57,9 +71,19 @@ public class ControllerAdviceBean implements Ordered {
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 = initOrderFromBeanType(this.beanFactory.getType(beanName));
+
+ Class> beanType = this.beanFactory.getType(beanName);
+ this.order = initOrderFromBeanType(beanType);
+
+ ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType,ControllerAdvice.class);
+ Assert.notNull(annotation, "BeanType [" + beanType.getName() + "] is not annotated @ControllerAdvice");
+
+ this.basePackages.addAll(initBasePackagesFromBeanType(beanType, annotation));
+ this.annotations.addAll(Arrays.asList(annotation.annotations()));
+ this.assignableTypes.addAll(Arrays.asList(annotation.assignableTypes()));
}
private static int initOrderFromBeanType(Class> beanType) {
@@ -67,6 +91,35 @@ public class ControllerAdviceBean implements Ordered {
return (annot != null) ? annot.value() : Ordered.LOWEST_PRECEDENCE;
}
+ private static List initBasePackagesFromBeanType(Class> beanType, ControllerAdvice annotation) {
+ List basePackages = new ArrayList();
+ List basePackageNames = new ArrayList();
+ basePackageNames.addAll(Arrays.asList(annotation.value()));
+ basePackageNames.addAll(Arrays.asList(annotation.basePackages()));
+ for (String pkgName : basePackageNames) {
+ if (StringUtils.hasText(pkgName)) {
+ Package pkg = Package.getPackage(pkgName);
+ if(pkg != null) {
+ basePackages.add(pkg);
+ }
+ else {
+ logger.warn("Package [" + pkgName + "] was not found, see [" + beanType.getName() + "]");
+ }
+ }
+ }
+ for (Class> markerClass : annotation.basePackageClasses()) {
+ Package pack = markerClass.getPackage();
+ if (pack != null) {
+ basePackages.add(pack);
+ }
+ else {
+ logger.warn("Package was not found for class [" + markerClass.getName()
+ + "], see [" + beanType.getName() + "]");
+ }
+ }
+ return basePackages;
+ }
+
/**
* Create an instance using the given bean instance.
* @param bean the bean
@@ -75,6 +128,14 @@ public class ControllerAdviceBean implements Ordered {
Assert.notNull(bean, "'bean' must not be null");
this.bean = bean;
this.order = initOrderFromBean(bean);
+
+ Class extends Object> beanType = bean.getClass();
+ ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType,ControllerAdvice.class);
+ Assert.notNull(annotation, "BeanType [" + beanType.getName() + "] is not annotated @ControllerAdvice");
+
+ this.basePackages.addAll(initBasePackagesFromBeanType(beanType, annotation));
+ this.annotations.addAll(Arrays.asList(annotation.annotations()));
+ this.assignableTypes.addAll(Arrays.asList(annotation.assignableTypes()));
this.beanFactory = null;
}
@@ -124,6 +185,44 @@ public class ControllerAdviceBean implements Ordered {
return (this.bean instanceof String) ? this.beanFactory.getBean((String) this.bean) : this.bean;
}
+ /**
+ * Checks whether the given bean type should be assisted by this
+ * {@code @ControllerAdvice} instance.
+ *
+ * @param beanType the type of the bean to check
+ * @see org.springframework.web.bind.annotation.ControllerAdvice
+ * @since 4.0
+ */
+ public boolean isApplicableToBeanType(Class> beanType) {
+ if(!hasSelectors()) {
+ return true;
+ }
+ else if (beanType != null) {
+ for (Class> clazz : this.assignableTypes) {
+ if(ClassUtils.isAssignable(clazz, beanType)) {
+ return true;
+ }
+ }
+ for (Class extends Annotation> annotationClass : this.annotations) {
+ if(AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
+ return true;
+ }
+ }
+ String packageName = beanType.getPackage().getName();
+ for (Package basePackage : this.basePackages) {
+ if(packageName.startsWith(basePackage.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean hasSelectors() {
+ return (!this.basePackages.isEmpty() || !this.annotations.isEmpty() || !this.assignableTypes.isEmpty());
+ }
+
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java b/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java
new file mode 100644
index 0000000000..25f0ccefde
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java
@@ -0,0 +1,142 @@
+package org.springframework.web.method;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.junit.Test;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Brian Clozel
+ */
+public class ControllerAdviceBeanTests {
+
+ @Test
+ public void shouldMatchAll() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new SimpleControllerAdvice());
+ assertApplicable("should match all", bean, AnnotatedController.class);
+ assertApplicable("should match all", bean, ImplementationController.class);
+ assertApplicable("should match all", bean, InheritanceController.class);
+ assertApplicable("should match all", bean, String.class);
+ }
+
+ @Test
+ public void basePackageSupport() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new BasePackageSupport());
+ assertApplicable("base package support", bean, AnnotatedController.class);
+ assertApplicable("base package support", bean, ImplementationController.class);
+ assertApplicable("base package support", bean, InheritanceController.class);
+ assertNotApplicable("bean not in package", bean, String.class);
+ }
+
+ @Test
+ public void basePackageValueSupport() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new BasePackageValueSupport());
+ assertApplicable("base package support", bean, AnnotatedController.class);
+ assertApplicable("base package support", bean, ImplementationController.class);
+ assertApplicable("base package support", bean, InheritanceController.class);
+ assertNotApplicable("bean not in package", bean, String.class);
+ }
+
+ @Test
+ public void annotationSupport() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new AnnotationSupport());
+ assertApplicable("annotation support", bean, AnnotatedController.class);
+ assertNotApplicable("this bean is not annotated", bean, InheritanceController.class);
+ }
+
+ @Test
+ public void markerClassSupport() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new MarkerClassSupport());
+ assertApplicable("base package class support", bean, AnnotatedController.class);
+ assertApplicable("base package class support", bean, ImplementationController.class);
+ assertApplicable("base package class support", bean, InheritanceController.class);
+ assertNotApplicable("bean not in package", bean, String.class);
+ }
+
+ @Test
+ public void shouldNotMatch() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new ShouldNotMatch());
+ assertNotApplicable("should not match", bean, AnnotatedController.class);
+ assertNotApplicable("should not match", bean, ImplementationController.class);
+ assertNotApplicable("should not match", bean, InheritanceController.class);
+ assertNotApplicable("should not match", bean, String.class);
+ }
+
+ @Test
+ public void assignableTypesSupport() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new AssignableTypesSupport());
+ assertApplicable("controller implements assignable", bean, ImplementationController.class);
+ assertApplicable("controller inherits assignable", bean, InheritanceController.class);
+ assertNotApplicable("not assignable", bean, AnnotatedController.class);
+ assertNotApplicable("not assignable", bean, String.class);
+ }
+
+ @Test
+ public void multipleMatch() {
+ ControllerAdviceBean bean = new ControllerAdviceBean(new MultipleSelectorsSupport());
+ assertApplicable("controller implements assignable", bean, ImplementationController.class);
+ assertApplicable("controller is annotated", bean, AnnotatedController.class);
+ assertNotApplicable("should not match", bean, InheritanceController.class);
+ }
+
+ private void assertApplicable(String message, ControllerAdviceBean controllerAdvice,
+ Class> controllerBeanType) {
+ assertNotNull(controllerAdvice);
+ assertTrue(message,controllerAdvice.isApplicableToBeanType(controllerBeanType));
+ }
+
+ private void assertNotApplicable(String message, ControllerAdviceBean controllerAdvice,
+ Class> controllerBeanType) {
+ assertNotNull(controllerAdvice);
+ assertFalse(message,controllerAdvice.isApplicableToBeanType(controllerBeanType));
+ }
+
+ // ControllerAdvice classes
+
+ @ControllerAdvice
+ static class SimpleControllerAdvice {}
+
+ @ControllerAdvice(annotations = ControllerAnnotation.class)
+ static class AnnotationSupport {}
+
+ @ControllerAdvice(basePackageClasses = MarkerClass.class)
+ static class MarkerClassSupport {}
+
+ @ControllerAdvice(assignableTypes = {ControllerInterface.class,
+ AbstractController.class})
+ static class AssignableTypesSupport {}
+
+ @ControllerAdvice(basePackages = "org.springframework.web.method")
+ static class BasePackageSupport {}
+
+ @ControllerAdvice("org.springframework.web.method")
+ static class BasePackageValueSupport {}
+
+ @ControllerAdvice(annotations = ControllerAnnotation.class, assignableTypes = ControllerInterface.class)
+ static class MultipleSelectorsSupport {}
+
+ @ControllerAdvice(basePackages = "java.util", annotations = {RestController.class})
+ static class ShouldNotMatch {}
+
+ // Support classes
+
+ static class MarkerClass {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ static @interface ControllerAnnotation {}
+
+ @ControllerAnnotation
+ public static class AnnotatedController {}
+
+ static interface ControllerInterface {}
+
+ static class ImplementationController implements ControllerInterface {}
+
+ static abstract class AbstractController {}
+
+ static class InheritanceController extends AbstractController {}
+}
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 8a62923a42..2dc94d55c9 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -351,8 +351,8 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
* @return a method to handle the exception, or {@code null}
*/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
+ Class> handlerType = (handlerMethod != null) ? handlerMethod.getBeanType() : null;
if (handlerMethod != null) {
- Class> handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
@@ -364,9 +364,11 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
}
}
for (Entry entry : this.exceptionHandlerAdviceCache.entrySet()) {
- Method method = entry.getValue().resolveMethod(exception);
- if (method != null) {
- return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
+ if(entry.getKey().isApplicableToBeanType(handlerType)) {
+ Method method = entry.getValue().resolveMethod(exception);
+ if (method != null) {
+ return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
+ }
}
}
return null;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
index 4d2de70c26..bf5a17505e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
@@ -776,9 +776,11 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
List attrMethods = new ArrayList();
// Global methods first
for (Entry> entry : this.modelAttributeAdviceCache.entrySet()) {
- Object bean = entry.getKey().resolveBean();
- for (Method method : entry.getValue()) {
- attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
+ if(entry.getKey().isApplicableToBeanType(handlerType)) {
+ Object bean = entry.getKey().resolveBean();
+ for (Method method : entry.getValue()) {
+ attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
+ }
}
}
for (Method method : methods) {
@@ -806,9 +808,11 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
List initBinderMethods = new ArrayList();
// Global methods first
for (Entry> entry : this.initBinderAdviceCache .entrySet()) {
- Object bean = entry.getKey().resolveBean();
- for (Method method : entry.getValue()) {
- initBinderMethods.add(createInitBinderMethod(bean, method));
+ if(entry.getKey().isApplicableToBeanType(handlerType)) {
+ Object bean = entry.getKey().resolveBean();
+ for (Method method : entry.getValue()) {
+ initBinderMethods.add(createInitBinderMethod(bean, method));
+ }
}
}
for (Method method : methods) {
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 4c93cac51f..4fab407102 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-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -204,6 +204,35 @@ public class ExceptionHandlerExceptionResolverTests {
assertEquals("TestExceptionResolver: IllegalStateException", this.response.getContentAsString());
}
+ @Test
+ public void resolveExceptionControllerAdviceHandler() throws Exception {
+ AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
+ this.resolver.setApplicationContext(cxt);
+ 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("BasePackageTestExceptionResolver: IllegalStateException", this.response.getContentAsString());
+ }
+
+ @Test
+ public void resolveExceptionControllerAdviceNoHandler() throws Exception {
+ AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
+ this.resolver.setApplicationContext(cxt);
+ this.resolver.afterPropertiesSet();
+
+ IllegalStateException ex = new IllegalStateException();
+ ModelAndView mav = this.resolver.resolveException(this.request, this.response, null, ex);
+
+ assertNotNull("Exception was not handled", mav);
+ assertTrue(mav.isEmpty());
+ assertEquals("DefaultTestExceptionResolver: IllegalStateException", this.response.getContentAsString());
+ }
+
private void assertMethodProcessorCount(int resolverCount, int handlerCount) {
assertEquals(resolverCount, this.resolver.getArgumentResolvers().getResolvers().size());
@@ -288,4 +317,51 @@ public class ExceptionHandlerExceptionResolverTests {
}
}
+ @ControllerAdvice("java.lang")
+ @Order(1)
+ static class NotCalledTestExceptionResolver {
+
+ @ExceptionHandler
+ @ResponseBody
+ public String handleException(IllegalStateException ex) {
+ return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
+ }
+ }
+
+ @ControllerAdvice("org.springframework.web.servlet.mvc.method.annotation")
+ @Order(2)
+ static class BasePackageTestExceptionResolver {
+
+ @ExceptionHandler
+ @ResponseBody
+ public String handleException(IllegalStateException ex) {
+ return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
+ }
+ }
+
+ @ControllerAdvice
+ @Order(3)
+ static class DefaultTestExceptionResolver {
+
+ @ExceptionHandler
+ @ResponseBody
+ public String handleException(IllegalStateException ex) {
+ return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
+ }
+ }
+
+ @Configuration
+ static class MyControllerAdviceConfig {
+ @Bean public NotCalledTestExceptionResolver notCalledTestExceptionResolver() {
+ return new NotCalledTestExceptionResolver();
+ }
+
+ @Bean public BasePackageTestExceptionResolver basePackageTestExceptionResolver() {
+ return new BasePackageTestExceptionResolver();
+ }
+
+ @Bean public DefaultTestExceptionResolver defaultTestExceptionResolver() {
+ return new DefaultTestExceptionResolver();
+ }
+ }
}
\ No newline at end of file
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java
index d08c795602..a5acd05a69 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java
@@ -25,7 +25,9 @@ import org.junit.Test;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.context.support.StaticWebApplicationContext;
@@ -184,6 +186,21 @@ public class RequestMappingHandlerAdapterTests {
assertEquals("gAttr2", mav.getModel().get("attr2"));
}
+ @Test
+ public void modelAttributePackageNameAdvice() throws Exception {
+ this.webAppContext.registerSingleton("mapa", ModelAttributePackageAdvice.class);
+ this.webAppContext.registerSingleton("manupa", ModelAttributeNotUsedPackageAdvice.class);
+ this.webAppContext.refresh();
+
+ HandlerMethod handlerMethod = handlerMethod(new SimpleController(), "handle");
+ this.handlerAdapter.afterPropertiesSet();
+ ModelAndView mav = this.handlerAdapter.handle(this.request, this.response, handlerMethod);
+
+ assertEquals("lAttr1", mav.getModel().get("attr1"));
+ assertEquals("gAttr2", mav.getModel().get("attr2"));
+ assertEquals(null,mav.getModel().get("attr3"));
+ }
+
private HandlerMethod handlerMethod(Object handler, String methodName, Class>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
@@ -237,4 +254,21 @@ public class RequestMappingHandlerAdapterTests {
}
}
+ @ControllerAdvice({"org.springframework.web.servlet.mvc.method.annotation","java.lang"})
+ private static class ModelAttributePackageAdvice {
+
+ @ModelAttribute
+ public void addAttributes(Model model) {
+ model.addAttribute("attr2", "gAttr2");
+ }
+ }
+
+ @ControllerAdvice("java.lang")
+ private static class ModelAttributeNotUsedPackageAdvice {
+
+ @ModelAttribute
+ public void addAttributes(Model model) {
+ model.addAttribute("attr3", "gAttr3");
+ }
+ }
}
\ No newline at end of file