Skip to content

Commit

Permalink
Polish ControllerAdvice selectors
Browse files Browse the repository at this point in the history
Issue: SPR-10222
  • Loading branch information
rstoyanchev committed Oct 18, 2013
1 parent c4a8bf9 commit 4f28c77
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,29 +61,44 @@
/**
* 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 {};
Expand All @@ -95,18 +111,9 @@
*
* <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 {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
*/
public class ControllerAdviceBean implements Ordered {

private static final Log logger = LogFactory.getLog(ControllerAdviceBean.class);

private final Object bean;

private final int order;
Expand All @@ -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.
Expand All @@ -70,70 +71,55 @@ public ControllerAdviceBean(String beanName, BeanFactory beanFactory) {
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) {
Order annot = AnnotationUtils.findAnnotation(beanType, Order.class);
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);
}
}
}
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() + "]");
else {
logger.warn("Package [" + pkgName + "] was not found, 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() + "]");
}
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;
}

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
Expand All @@ -142,9 +128,14 @@ public ControllerAdviceBean(Object bean) {
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;
}

Expand Down Expand Up @@ -195,41 +186,40 @@ public Object resolveBean() {
}

/**
* 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;
}
}
}
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());
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;

/**
Expand Down Expand Up @@ -116,12 +116,10 @@ static class BasePackageSupport {}
@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
Expand Down

0 comments on commit 4f28c77

Please sign in to comment.