diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java index 7a555528ac..809631fe81 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java @@ -39,7 +39,6 @@ import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; @@ -58,12 +57,12 @@ import org.springframework.util.ClassUtils; * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll - * @see EnableAutoConfiguration * @since 1.3.0 + * @see EnableAutoConfiguration */ -@Order(Ordered.LOWEST_PRECEDENCE - 1) -public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, - BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware { +public class EnableAutoConfigurationImportSelector + implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, + BeanFactoryAware, EnvironmentAware, Ordered { private ConfigurableListableBeanFactory beanFactory; @@ -242,6 +241,11 @@ public class EnableAutoConfigurationImportSelector implements DeferredImportSele return this.resourceLoader; } + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 1; + } + /** * Bindable object used to get excludes. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ImportAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ImportAutoConfiguration.java new file mode 100644 index 0000000000..463d234bca --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ImportAutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * Import and apply the selected auto-configuration classes. Applies the same ordering + * rules as {@code @EnableAutoConfiguration} but restricts the auto-configuration classes + * to the specified set, rather than consulting {@code spring.factories}. + *

+ * Generally, {@code @EnableAutoConfiguration} should used in preference to this + * annotation, however, {@code @ImportAutoConfiguration} can be useful in some situations + * and especially when writing tests. + * + * @author Phillip Webb + * @since 1.3.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@AutoConfigurationPackage +@Import(ImportAutoConfigurationImportSelector.class) +public @interface ImportAutoConfiguration { + + /** + * The auto-configuration classes that should be imported. + * @return the classes to import + */ + Class[] value(); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelector.java new file mode 100644 index 0000000000..b797968a16 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelector.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.ClassUtils; + +/** + * Variant of {@link EnableAutoConfigurationImportSelector} for + * {@link ImportAutoConfiguration}. + * + * @author Phillip Webb + */ +class ImportAutoConfigurationImportSelector + extends EnableAutoConfigurationImportSelector { + + private static final Set ANNOTATION_NAMES; + + static { + Set names = new LinkedHashSet(); + names.add(ImportAutoConfiguration.class.getName()); + names.add("org.springframework.boot.autoconfigure.test.ImportAutoConfiguration"); + ANNOTATION_NAMES = Collections.unmodifiableSet(names); + } + + @Override + protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { + return null; + } + + @Override + protected List getCandidateConfigurations(AnnotationMetadata metadata, + AnnotationAttributes attributes) { + try { + return getCandidateConfigurations( + ClassUtils.forName(metadata.getClassName(), null)); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private List getCandidateConfigurations(Class source) { + Set candidates = new LinkedHashSet(); + collectCandidateConfigurations(source, candidates); + return new ArrayList(candidates); + } + + private void collectCandidateConfigurations(Class source, Set candidates) { + if (source != null) { + for (Annotation annotation : source.getDeclaredAnnotations()) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { + collectCandidateConfigurations(annotation, candidates); + } + } + collectCandidateConfigurations(source.getSuperclass(), candidates); + } + } + + private void collectCandidateConfigurations(Annotation annotation, + Set candidates) { + if (ANNOTATION_NAMES.contains(annotation.annotationType().getName())) { + String[] value = (String[]) AnnotationUtils + .getAnnotationAttributes(annotation, true).get("value"); + candidates.addAll(Arrays.asList(value)); + } + collectCandidateConfigurations(annotation.annotationType(), candidates); + } + + @Override + protected Set getExclusions(AnnotationMetadata metadata, + AnnotationAttributes attributes) { + return Collections.emptySet(); + } + + @Override + public int getOrder() { + return super.getOrder() - 1; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfiguration.java index 7b395fe908..0241205be1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -23,8 +23,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.boot.autoconfigure.AutoConfigurationPackage; -import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.AliasFor; /** * Import and apply the selected auto-configuration classes for testing purposes. Applies @@ -34,19 +33,22 @@ import org.springframework.context.annotation.Import; * * @author Phillip Webb * @since 1.3.0 + * @deprecated since 1.4.0 in favor of + * {@link org.springframework.boot.autoconfigure.ImportAutoConfiguration} */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@AutoConfigurationPackage -@Import(ImportAutoConfigurationImportSelector.class) +@org.springframework.boot.autoconfigure.ImportAutoConfiguration({}) +@Deprecated public @interface ImportAutoConfiguration { /** * The auto-configuration classes that should be imported. * @return the classes to import */ + @AliasFor(annotation = org.springframework.boot.autoconfigure.ImportAutoConfiguration.class, attribute = "value") Class[] value(); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfigurationImportSelector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfigurationImportSelector.java deleted file mode 100644 index 3a46aaa04d..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/ImportAutoConfigurationImportSelector.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2012-2015 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.test; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import org.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelector; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.type.AnnotationMetadata; - -/** - * Variant of {@link EnableAutoConfigurationImportSelector} for - * {@link ImportAutoConfiguration}. - * - * @author Phillip Webb - */ -class ImportAutoConfigurationImportSelector - extends EnableAutoConfigurationImportSelector { - - @Override - protected Class getAnnotationClass() { - return ImportAutoConfiguration.class; - } - - @Override - protected Set getExclusions(AnnotationMetadata metadata, - AnnotationAttributes attributes) { - return Collections.emptySet(); - } - - @Override - protected List getCandidateConfigurations(AnnotationMetadata metadata, - AnnotationAttributes attributes) { - return asList(attributes, "value"); - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/package-info.java index 1f2eb910ae..0bc67cf9d8 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/package-info.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/test/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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,5 +16,6 @@ /** * Test utilities related to auto-configuration. + * @deprecated in 1.4.0 in favor of the {@code spring-test-autoconfigure} module */ package org.springframework.boot.autoconfigure.test; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/test/ImportAutoConfigurationImportSelectorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java similarity index 53% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/test/ImportAutoConfigurationImportSelectorTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java index 4e8f4ea128..3fd9aa5c16 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/test/ImportAutoConfigurationImportSelectorTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.test; +package org.springframework.boot.autoconfigure; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import org.junit.Before; import org.junit.Test; @@ -25,13 +28,13 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; -import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; import org.springframework.core.env.Environment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verifyZeroInteractions; /** @@ -49,12 +52,6 @@ public class ImportAutoConfigurationImportSelectorTests { @Mock private Environment environment; - @Mock - private AnnotationMetadata annotationMetadata; - - @Mock - private AnnotationAttributes annotationAttributes; - @Before public void configureImportSelector() { this.importSelector.setBeanFactory(this.beanFactory); @@ -63,25 +60,54 @@ public class ImportAutoConfigurationImportSelectorTests { } @Test - public void importsAreSelected() { - String[] value = new String[] { FreeMarkerAutoConfiguration.class.getName() }; - configureValue(value); - String[] imports = this.importSelector.selectImports(this.annotationMetadata); - assertThat(imports).isEqualTo(value); + public void importsAreSelected() throws Exception { + AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory() + .getMetadataReader(ImportFreemarker.class.getName()) + .getAnnotationMetadata(); + String[] imports = this.importSelector.selectImports(annotationMetadata); + assertThat(imports).containsExactly(FreeMarkerAutoConfiguration.class.getName()); } @Test - public void propertyExclusionsAreNotApplied() { - configureValue(new String[] { FreeMarkerAutoConfiguration.class.getName() }); - this.importSelector.selectImports(this.annotationMetadata); + public void propertyExclusionsAreNotApplied() throws Exception { + AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory() + .getMetadataReader(ImportFreemarker.class.getName()) + .getAnnotationMetadata(); + this.importSelector.selectImports(annotationMetadata); verifyZeroInteractions(this.environment); } - private void configureValue(String... value) { - String name = ImportAutoConfiguration.class.getName(); - given(this.annotationMetadata.getAnnotationAttributes(name, true)) - .willReturn(this.annotationAttributes); - given(this.annotationAttributes.getStringArray("value")).willReturn(value); + @Test + public void multipleImportsAreFound() throws Exception { + AnnotationMetadata annotationMetadata = new SimpleMetadataReaderFactory() + .getMetadataReader(MultipleImports.class.getName()) + .getAnnotationMetadata(); + String[] imports = this.importSelector.selectImports(annotationMetadata); + assertThat(imports).containsOnly(FreeMarkerAutoConfiguration.class.getName(), + ThymeleafAutoConfiguration.class.getName()); + } + + @ImportAutoConfiguration(FreeMarkerAutoConfiguration.class) + static class ImportFreemarker { + + } + + @ImportOne + @ImportTwo + static class MultipleImports { + + } + + @Retention(RetentionPolicy.RUNTIME) + @ImportAutoConfiguration(FreeMarkerAutoConfiguration.class) + static @interface ImportOne { + + } + + @Retention(RetentionPolicy.RUNTIME) + @ImportAutoConfiguration(ThymeleafAutoConfiguration.class) + static @interface ImportTwo { + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationTests.java new file mode 100644 index 0000000000..29df53aeaa --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ImportAutoConfigurationTests}. + * + * @author Phillip Webb + */ +public class ImportAutoConfigurationTests { + + @Test + public void multipleAnnotationsShouldMergeCorrectly() { + testConfigImports(Config.class); + testConfigImports(AnotherConfig.class); + } + + private void testConfigImports(Class config) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + config); + String shortName = ClassUtils.getShortName(ImportAutoConfigurationTests.class); + int beginIndex = shortName.length() + 1; + List orderdConfigBeans = new ArrayList(); + for (String bean : context.getBeanDefinitionNames()) { + if (bean.contains("$Config")) { + String shortBeanName = ClassUtils.getShortName(bean); + orderdConfigBeans.add(shortBeanName.substring(beginIndex)); + } + } + assertThat(orderdConfigBeans).containsExactly("ConfigA", "ConfigB", "ConfigC", + "ConfigD"); + context.close(); + } + + @ImportAutoConfiguration({ ConfigD.class, ConfigB.class }) + @MetaImportAutoConfiguration + static class Config { + + } + + @MetaImportAutoConfiguration + @ImportAutoConfiguration({ ConfigB.class, ConfigD.class }) + static class AnotherConfig { + + } + + @Retention(RetentionPolicy.RUNTIME) + @ImportAutoConfiguration({ ConfigC.class, ConfigA.class }) + @interface MetaImportAutoConfiguration { + + } + + @Configuration + static class ConfigA { + } + + @Configuration + @AutoConfigureAfter(ConfigA.class) + static class ConfigB { + } + + @Configuration + @AutoConfigureAfter(ConfigB.class) + static class ConfigC { + } + + @Configuration + @AutoConfigureAfter(ConfigC.class) + static class ConfigD { + } + +}