Improve attribute alias support in @ComponentScan

Prior to this commit, @ComponentScan already had a value/basePackages
alias pair; however, the semantics were not properly enforced.

This commit addresses this issue by refactoring
ComponentScanAnnotationParser to ensure that it is not possible to
declare both of the aliased attributes. In addition, the 'value' and
'basePackages' attributes are now annotated with @AliasFor in order to
make the semantics clearer.

Issue: SPR-11393
This commit is contained in:
Sam Brannen 2015-05-31 15:37:31 +02:00
parent 94aa90646b
commit 845f4f2bb7
2 changed files with 26 additions and 5 deletions

View File

@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter; import org.springframework.core.type.filter.TypeFilter;
/** /**
@ -46,6 +47,7 @@ import org.springframework.core.type.filter.TypeFilter;
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 3.1 * @since 3.1
* @see Configuration * @see Configuration
*/ */
@ -60,6 +62,7 @@ public @interface ComponentScan {
* are needed — for example, {@code @ComponentScan("org.my.pkg")} * are needed — for example, {@code @ComponentScan("org.my.pkg")}
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}. * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/ */
@AliasFor(attribute = "basePackages")
String[] value() default {}; String[] value() default {};
/** /**
@ -69,6 +72,7 @@ public @interface ComponentScan {
* <p>Use {@link #basePackageClasses} for a type-safe alternative to * <p>Use {@link #basePackageClasses} for a type-safe alternative to
* String-based package names. * String-based package names.
*/ */
@AliasFor(attribute = "value")
String[] basePackages() default {}; String[] basePackages() default {};
/** /**

View File

@ -30,6 +30,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
@ -40,6 +41,7 @@ import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter; import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -115,11 +117,7 @@ class ComponentScanAnnotationParser {
} }
Set<String> basePackages = new LinkedHashSet<String>(); Set<String> basePackages = new LinkedHashSet<String>();
Set<String> specifiedPackages = new LinkedHashSet<String>(); for (String pkg : getBasePackages(componentScan, declaringClass)) {
specifiedPackages.addAll(Arrays.asList(componentScan.getStringArray("value")));
specifiedPackages.addAll(Arrays.asList(componentScan.getStringArray("basePackages")));
for (String pkg : specifiedPackages) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
basePackages.addAll(Arrays.asList(tokenized)); basePackages.addAll(Arrays.asList(tokenized));
@ -140,6 +138,25 @@ class ComponentScanAnnotationParser {
return scanner.doScan(StringUtils.toStringArray(basePackages)); return scanner.doScan(StringUtils.toStringArray(basePackages));
} }
private String[] getBasePackages(AnnotationAttributes componentScan, String declaringClass) {
String[] value = componentScan.getStringArray("value");
String[] basePackages = componentScan.getStringArray("basePackages");
boolean valueDeclared = !ObjectUtils.isEmpty(value);
boolean basePackagesDeclared = !ObjectUtils.isEmpty(basePackages);
if (valueDeclared && basePackagesDeclared && !ObjectUtils.nullSafeEquals(value, basePackages)) {
String msg = String.format("In @ComponentScan declared on [%s], attribute [value] "
+ "and its alias [basePackages] are present with values of [%s] and [%s], "
+ "but only one is permitted.", declaringClass, ObjectUtils.nullSafeToString(value),
ObjectUtils.nullSafeToString(basePackages));
throw new AnnotationConfigurationException(msg);
}
if (!basePackagesDeclared) {
basePackages = value;
}
return basePackages;
}
private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) { private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>(); List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
FilterType filterType = filterAttributes.getEnum("type"); FilterType filterType = filterAttributes.getEnum("type");