Merge branch '1.5.x'

pull/7793/head
Phillip Webb 8 years ago
commit 86a42e4e44

@ -24,6 +24,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
/** /**
* Import and apply the specified auto-configuration classes. Applies the same ordering * Import and apply the specified auto-configuration classes. Applies the same ordering
@ -46,12 +47,27 @@ import org.springframework.context.annotation.Import;
@Import(ImportAutoConfigurationImportSelector.class) @Import(ImportAutoConfigurationImportSelector.class)
public @interface ImportAutoConfiguration { public @interface ImportAutoConfiguration {
/**
* The auto-configuration classes that should be imported. This is an alias for
* {@link #classes()}.
* @return the classes to import
*/
@AliasFor("classes")
Class<?>[] value() default {};
/** /**
* The auto-configuration classes that should be imported. When empty, the classes are * The auto-configuration classes that should be imported. When empty, the classes are
* specified using an entry in {@code META-INF/spring.factories} where the key is the * specified using an entry in {@code META-INF/spring.factories} where the key is the
* fully-qualified name of the annotated class. * fully-qualified name of the annotated class.
* @return the classes to import * @return the classes to import
*/ */
Class<?>[] value() default {}; @AliasFor("value")
Class<?>[] classes() default {};
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
} }

@ -24,13 +24,18 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/** /**
* Variant of {@link EnableAutoConfigurationImportSelector} for * Variant of {@link EnableAutoConfigurationImportSelector} for
@ -59,47 +64,27 @@ class ImportAutoConfigurationImportSelector
@Override @Override
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) { AnnotationAttributes attributes) {
try { List<String> candidates = new ArrayList<String>();
return getCandidateConfigurations( Map<Class<?>, List<Annotation>> annotations = getAnnotations(metadata);
ClassUtils.forName(metadata.getClassName(), null)); for (Map.Entry<Class<?>, List<Annotation>> entry : annotations.entrySet()) {
} collectCandidateConfigurations(entry.getKey(), entry.getValue(), candidates);
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private List<String> getCandidateConfigurations(Class<?> source) {
Set<String> candidates = new LinkedHashSet<String>();
collectCandidateConfigurations(source, candidates, new HashSet<Class<?>>());
return new ArrayList<String>(candidates);
}
private void collectCandidateConfigurations(Class<?> source, Set<String> candidates,
Set<Class<?>> seen) {
if (source != null && seen.add(source)) {
for (Annotation annotation : source.getDeclaredAnnotations()) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
collectCandidateConfigurations(source, annotation, candidates, seen);
}
}
collectCandidateConfigurations(source.getSuperclass(), candidates, seen);
} }
return candidates;
} }
private void collectCandidateConfigurations(Class<?> source, Annotation annotation, private void collectCandidateConfigurations(Class<?> source,
Set<String> candidates, Set<Class<?>> seen) { List<Annotation> annotations, List<String> candidates) {
if (ANNOTATION_NAMES.contains(annotation.annotationType().getName())) { for (Annotation annotation : annotations) {
candidates.addAll(getConfigurationsForAnnotation(source, annotation)); candidates.addAll(getConfigurationsForAnnotation(source, annotation));
} }
collectCandidateConfigurations(annotation.annotationType(), candidates, seen);
} }
private Collection<String> getConfigurationsForAnnotation(Class<?> source, private Collection<String> getConfigurationsForAnnotation(Class<?> source,
Annotation annotation) { Annotation annotation) {
String[] value = (String[]) AnnotationUtils String[] classes = (String[]) AnnotationUtils
.getAnnotationAttributes(annotation, true).get("value"); .getAnnotationAttributes(annotation, true).get("classes");
if (value.length > 0) { if (classes.length > 0) {
return Arrays.asList(value); return Arrays.asList(classes);
} }
return SpringFactoriesLoader.loadFactoryNames(source, return SpringFactoriesLoader.loadFactoryNames(source,
getClass().getClassLoader()); getClass().getClassLoader());
@ -108,7 +93,52 @@ class ImportAutoConfigurationImportSelector
@Override @Override
protected Set<String> getExclusions(AnnotationMetadata metadata, protected Set<String> getExclusions(AnnotationMetadata metadata,
AnnotationAttributes attributes) { AnnotationAttributes attributes) {
return Collections.emptySet(); Set<String> exclusions = new LinkedHashSet<String>();
Class<?> source = ClassUtils.resolveClassName(metadata.getClassName(), null);
for (String annotationName : ANNOTATION_NAMES) {
AnnotationAttributes merged = AnnotatedElementUtils
.getMergedAnnotationAttributes(source, annotationName);
Class<?>[] exclude = (merged == null ? null
: merged.getClassArray("exclude"));
if (exclude != null) {
for (Class<?> excludeClass : exclude) {
exclusions.add(excludeClass.getName());
}
}
}
for (List<Annotation> annotations : getAnnotations(metadata).values()) {
for (Annotation annotation : annotations) {
String[] exclude = (String[]) AnnotationUtils
.getAnnotationAttributes(annotation, true).get("exclude");
if (!ObjectUtils.isEmpty(exclude)) {
exclusions.addAll(Arrays.asList(exclude));
}
}
}
return exclusions;
}
private Map<Class<?>, List<Annotation>> getAnnotations(AnnotationMetadata metadata) {
MultiValueMap<Class<?>, Annotation> annotations = new LinkedMultiValueMap<Class<?>, Annotation>();
Class<?> source = ClassUtils.resolveClassName(metadata.getClassName(), null);
collectAnnotations(source, annotations, new HashSet<Class<?>>());
return Collections.unmodifiableMap(annotations);
}
private void collectAnnotations(Class<?> source,
MultiValueMap<Class<?>, Annotation> annotations, HashSet<Class<?>> seen) {
if (source != null && seen.add(source)) {
for (Annotation annotation : source.getDeclaredAnnotations()) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
if (ANNOTATION_NAMES
.contains(annotation.annotationType().getName())) {
annotations.add(source, annotation);
}
collectAnnotations(annotation.annotationType(), annotations, seen);
}
}
collectAnnotations(source.getSuperclass(), annotations, seen);
}
} }
@Override @Override

@ -29,6 +29,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
@ -69,6 +70,15 @@ public class ImportAutoConfigurationImportSelectorTests {
assertThat(imports).containsExactly(FreeMarkerAutoConfiguration.class.getName()); assertThat(imports).containsExactly(FreeMarkerAutoConfiguration.class.getName());
} }
@Test
public void importsAreSelectedUsingClassesAttribute() throws Exception {
AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory()
.getMetadataReader(ImportFreeMarkerUsingClassesAttribute.class.getName())
.getAnnotationMetadata();
String[] imports = this.importSelector.selectImports(annotationMetadata);
assertThat(imports).containsExactly(FreeMarkerAutoConfiguration.class.getName());
}
@Test @Test
public void propertyExclusionsAreNotApplied() throws Exception { public void propertyExclusionsAreNotApplied() throws Exception {
AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory() AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory()
@ -97,22 +107,58 @@ public class ImportAutoConfigurationImportSelectorTests {
assertThat(imports).containsOnly(ThymeleafAutoConfiguration.class.getName()); assertThat(imports).containsOnly(ThymeleafAutoConfiguration.class.getName());
} }
@Test
public void exclusionsAreApplied() throws Exception {
AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory()
.getMetadataReader(MultipleImportsWithExclusion.class.getName())
.getAnnotationMetadata();
String[] imports = this.importSelector.selectImports(annotationMetadata);
assertThat(imports).containsOnly(FreeMarkerAutoConfiguration.class.getName());
}
@Test
public void exclusionsAliasesAreApplied() throws Exception {
AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory()
.getMetadataReader(
ImportWithSelfAnnotatingAnnotationExclude.class.getName())
.getAnnotationMetadata();
String[] imports = this.importSelector.selectImports(annotationMetadata);
assertThat(imports).isEmpty();
}
@ImportAutoConfiguration(FreeMarkerAutoConfiguration.class) @ImportAutoConfiguration(FreeMarkerAutoConfiguration.class)
static class ImportFreeMarker { static class ImportFreeMarker {
} }
@ImportAutoConfiguration(classes = FreeMarkerAutoConfiguration.class)
static class ImportFreeMarkerUsingClassesAttribute {
}
@ImportOne @ImportOne
@ImportTwo @ImportTwo
static class MultipleImports { static class MultipleImports {
} }
@ImportOne
@ImportTwo
@ImportAutoConfiguration(exclude = ThymeleafAutoConfiguration.class)
static class MultipleImportsWithExclusion {
}
@SelfAnnotating @SelfAnnotating
static class ImportWithSelfAnnotatingAnnotation { static class ImportWithSelfAnnotatingAnnotation {
} }
@SelfAnnotating(excludeAutoConfiguration = ThymeleafAutoConfiguration.class)
static class ImportWithSelfAnnotatingAnnotationExclude {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ImportAutoConfiguration(FreeMarkerAutoConfiguration.class) @ImportAutoConfiguration(FreeMarkerAutoConfiguration.class)
static @interface ImportOne { static @interface ImportOne {
@ -130,6 +176,9 @@ public class ImportAutoConfigurationImportSelectorTests {
@SelfAnnotating @SelfAnnotating
static @interface SelfAnnotating { static @interface SelfAnnotating {
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
} }
} }

@ -38,11 +38,25 @@ public class ImportAutoConfigurationTests {
@Test @Test
public void multipleAnnotationsShouldMergeCorrectly() { public void multipleAnnotationsShouldMergeCorrectly() {
testConfigImports(Config.class); assertThat(getImportedConfigBeans(Config.class)).containsExactly("ConfigA",
testConfigImports(AnotherConfig.class); "ConfigB", "ConfigC", "ConfigD");
assertThat(getImportedConfigBeans(AnotherConfig.class)).containsExactly("ConfigA",
"ConfigB", "ConfigC", "ConfigD");
} }
private void testConfigImports(Class<?> config) { @Test
public void classesAsAnAlias() throws Exception {
assertThat(getImportedConfigBeans(AnotherConfigUsingClasses.class))
.containsExactly("ConfigA", "ConfigB", "ConfigC", "ConfigD");
}
@Test
public void excluding() throws Exception {
assertThat(getImportedConfigBeans(ExcludingConfig.class))
.containsExactly("ConfigA", "ConfigB", "ConfigD");
}
private List<String> getImportedConfigBeans(Class<?> config) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
config); config);
String shortName = ClassUtils.getShortName(ImportAutoConfigurationTests.class); String shortName = ClassUtils.getShortName(ImportAutoConfigurationTests.class);
@ -54,9 +68,8 @@ public class ImportAutoConfigurationTests {
orderedConfigBeans.add(shortBeanName.substring(beginIndex)); orderedConfigBeans.add(shortBeanName.substring(beginIndex));
} }
} }
assertThat(orderedConfigBeans).containsExactly("ConfigA", "ConfigB", "ConfigC",
"ConfigD");
context.close(); context.close();
return orderedConfigBeans;
} }
@ImportAutoConfiguration({ ConfigD.class, ConfigB.class }) @ImportAutoConfiguration({ ConfigD.class, ConfigB.class })
@ -71,6 +84,19 @@ public class ImportAutoConfigurationTests {
} }
@MetaImportAutoConfiguration
@ImportAutoConfiguration(classes = { ConfigB.class, ConfigD.class })
static class AnotherConfigUsingClasses {
}
@ImportAutoConfiguration(classes = { ConfigD.class,
ConfigB.class }, exclude = ConfigC.class)
@MetaImportAutoConfiguration
static class ExcludingConfig {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ImportAutoConfiguration({ ConfigC.class, ConfigA.class }) @ImportAutoConfiguration({ ConfigC.class, ConfigA.class })
@interface MetaImportAutoConfiguration { @interface MetaImportAutoConfiguration {

@ -31,6 +31,7 @@ import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.BootstrapWith;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -95,4 +96,11 @@ public @interface JdbcTest {
*/ */
ComponentScan.Filter[] excludeFilters() default {}; ComponentScan.Filter[] excludeFilters() default {};
/**
* Auto-configuration exclusions that should be applied for this test.
* @return auto-configuration exclusions to apply
*/
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
} }

@ -32,6 +32,7 @@ import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.GsonTester;
import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JacksonTester;
import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.BootstrapWith;
/** /**
@ -90,4 +91,11 @@ public @interface JsonTest {
*/ */
Filter[] excludeFilters() default {}; Filter[] excludeFilters() default {};
/**
* Auto-configuration exclusions that should be applied for this test.
* @return auto-configuration exclusions to apply
*/
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
} }

@ -33,6 +33,7 @@ import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.BootstrapWith;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -106,4 +107,11 @@ public @interface DataJpaTest {
*/ */
Filter[] excludeFilters() default {}; Filter[] excludeFilters() default {};
/**
* Auto-configuration exclusions that should be applied for this test.
* @return auto-configuration exclusions to apply
*/
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
} }

@ -115,4 +115,11 @@ public @interface RestClientTest {
*/ */
ComponentScan.Filter[] excludeFilters() default {}; ComponentScan.Filter[] excludeFilters() default {};
/**
* Auto-configuration exclusions that should be applied for this test.
* @return auto-configuration exclusions to apply
*/
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
} }

@ -132,4 +132,11 @@ public @interface WebMvcTest {
@AliasFor(annotation = AutoConfigureMockMvc.class, attribute = "secure") @AliasFor(annotation = AutoConfigureMockMvc.class, attribute = "secure")
boolean secure() default true; boolean secure() default true;
/**
* Auto-configuration exclusions that should be applied for this test.
* @return auto-configuration exclusions to apply
*/
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
Class<?>[] excludeAutoConfiguration() default {};
} }

Loading…
Cancel
Save