Polish ControllerAdvice selectors

Issue: SPR-10222
This commit is contained in:
Rossen Stoyanchev 2013-10-18 12:24:55 -04:00
parent c4a8bf9c4d
commit 4f28c77db7
3 changed files with 80 additions and 85 deletions

View File

@ -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;
@ -60,29 +61,44 @@ public @interface ControllerAdvice {
/**
* Alias for the {@link #basePackages()} attribute.
* Allows for more concise annotation declarations e.g.:
* {@code @ControllerAdvice("org.my.pkg")} instead of
* {@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 selected
* to be assisted by the annotated class, e.g.:
* {@code @ControllerAdvice(basePackages="org.my.pkg")}
* 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"})}
*
* <p>{@link #value()} is an alias for this attribute.
* <p>Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
* <p>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.
*
* <p>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 {};
@ -95,18 +111,9 @@ public @interface ControllerAdvice {
*
* <p>Consider creating a special annotation or use a predefined one,
* like {@link RestController @RestController}.
* @since 4.0
*/
Class<?>[] annotations() default {};
/**
* Type-safe alternative to {@link #value()} for specifying the packages
* to select Controllers to be assisted by the {@code @ControllerAdvice}
* annotated class.
*
* <p>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 {};
Class<? extends Annotation>[] annotations() default {};
}

View File

@ -46,6 +46,8 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
*/
public class ControllerAdviceBean implements Ordered {
private static final Log logger = LogFactory.getLog(ControllerAdviceBean.class);
private final Object bean;
private final int order;
@ -54,11 +56,10 @@ public class ControllerAdviceBean implements Ordered {
private final List<Package> basePackages = new ArrayList<Package>();
private final List<Class<Annotation>> annotations = new ArrayList<Class<Annotation>>();
private final List<Class<? extends Annotation>> annotations = new ArrayList<Class<? extends Annotation>>();
private final List<Class<?>> assignableTypes = new ArrayList<Class<?>>();
private static final Log logger = LogFactory.getLog(ControllerAdviceBean.class);
/**
* Create an instance using the given bean name.
@ -70,13 +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;
Class<?> beanType = this.beanFactory.getType(beanName);
this.order = initOrderFromBeanType(beanType);
this.basePackages.addAll(initBasePackagesFromBeanType(beanType));
this.annotations.addAll(initAnnotationsFromBeanType(beanType));
this.assignableTypes.addAll(initAssignableTypesFromBeanType(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) {
@ -84,56 +91,35 @@ public class ControllerAdviceBean implements Ordered {
return (annot != null) ? annot.value() : Ordered.LOWEST_PRECEDENCE;
}
private static List<Package> initBasePackagesFromBeanType(Class<?> beanType) {
private static List<Package> initBasePackagesFromBeanType(Class<?> beanType, ControllerAdvice annotation) {
List<Package> basePackages = new ArrayList<Package>();
ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType,ControllerAdvice.class);
Assert.notNull(annotation,"BeanType ["+beanType.getName()+"] is not annotated @ControllerAdvice");
for (String pkgName : (String[])AnnotationUtils.getValue(annotation)) {
List<String> basePackageNames = new ArrayList<String>();
basePackageNames.addAll(Arrays.asList(annotation.value()));
basePackageNames.addAll(Arrays.asList(annotation.basePackages()));
for (String pkgName : basePackageNames) {
if (StringUtils.hasText(pkgName)) {
Package pack = Package.getPackage(pkgName);
if(pack != null) {
basePackages.add(pack);
} else {
logger.warn("Package [" + pkgName + "] was not found, see ["
+ beanType.getName() + "]");
Package pkg = Package.getPackage(pkgName);
if(pkg != null) {
basePackages.add(pkg);
}
else {
logger.warn("Package [" + pkgName + "] was not found, see [" + beanType.getName() + "]");
}
}
}
for (String pkgName : (String[])AnnotationUtils.getValue(annotation,"basePackages")) {
if (StringUtils.hasText(pkgName)) {
Package pack = Package.getPackage(pkgName);
if(pack != null) {
basePackages.add(pack);
} 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() + "]");
}
}
for (Class<?> markerClass : (Class<?>[])AnnotationUtils.getValue(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;
}
private static List<Class<Annotation>> initAnnotationsFromBeanType(Class<?> beanType) {
ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType,ControllerAdvice.class);
Class<Annotation>[] annotations = (Class<Annotation>[])AnnotationUtils.getValue(annotation,"annotations");
return Arrays.asList(annotations);
}
private static List<Class<?>> initAssignableTypesFromBeanType(Class<?> beanType) {
ControllerAdvice annotation = AnnotationUtils.findAnnotation(beanType,ControllerAdvice.class);
Class<?>[] assignableTypes = (Class<?>[])AnnotationUtils.getValue(annotation,"assignableTypes");
return Arrays.asList(assignableTypes);
}
/**
* Create an instance using the given bean instance.
* @param bean the bean
@ -142,9 +128,14 @@ public class ControllerAdviceBean implements Ordered {
Assert.notNull(bean, "'bean' must not be null");
this.bean = bean;
this.order = initOrderFromBean(bean);
this.basePackages.addAll(initBasePackagesFromBeanType(bean.getClass()));
this.annotations.addAll(initAnnotationsFromBeanType(bean.getClass()));
this.assignableTypes.addAll(initAssignableTypesFromBeanType(bean.getClass()));
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;
}
@ -195,31 +186,31 @@ public class ControllerAdviceBean implements Ordered {
}
/**
* Checks whether the bean type given as a parameter should be assisted by
* the current {@code @ControllerAdvice} annotated bean.
* Checks whether the given bean type should be assisted by this
* {@code @ControllerAdvice} instance.
*
* @param beanType the type of the bean
* @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(hasNoSelector()) {
if(!hasSelectors()) {
return true;
}
else if(beanType != null) {
String packageName = beanType.getPackage().getName();
for(Package basePackage : this.basePackages) {
if(packageName.startsWith(basePackage.getName())) {
else if (beanType != null) {
for (Class<?> clazz : this.assignableTypes) {
if(ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for(Class<Annotation> annotationClass : this.annotations) {
for (Class<? extends Annotation> annotationClass : this.annotations) {
if(AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
for(Class<?> clazz : this.assignableTypes) {
if(ClassUtils.isAssignable(clazz, beanType)) {
String packageName = beanType.getPackage().getName();
for (Package basePackage : this.basePackages) {
if(packageName.startsWith(basePackage.getName())) {
return true;
}
}
@ -227,9 +218,8 @@ public class ControllerAdviceBean implements Ordered {
return false;
}
private boolean hasNoSelector() {
return this.basePackages.isEmpty() && this.annotations.isEmpty()
&& this.assignableTypes.isEmpty();
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.annotations.isEmpty() || !this.assignableTypes.isEmpty());
}

View File

@ -1,12 +1,12 @@
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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import static org.junit.Assert.*;
/**
@ -116,12 +116,10 @@ public class ControllerAdviceBeanTests {
@ControllerAdvice("org.springframework.web.method")
static class BasePackageValueSupport {}
@ControllerAdvice(annotations = ControllerAnnotation.class,
assignableTypes = ControllerInterface.class)
@ControllerAdvice(annotations = ControllerAnnotation.class, assignableTypes = ControllerInterface.class)
static class MultipleSelectorsSupport {}
@ControllerAdvice(basePackages = "java.util",
annotations = RestController.class)
@ControllerAdvice(basePackages = "java.util", annotations = {RestController.class})
static class ShouldNotMatch {}
// Support classes