diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackage.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackage.java index 0fbe9b9a668..8b4ff1ac717 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackage.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackage.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -26,8 +26,9 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** - * Indicates that the package containing the annotated class should be registered with - * {@link AutoConfigurationPackages}. + * Registers packages with {@link AutoConfigurationPackages}. When no {@link #basePackages + * base packages} or {@link #basePackageClasses base package classes} are specified, the + * package of the annotated class is registered. * * @author Phillip Webb * @since 1.3.0 @@ -40,4 +41,25 @@ import org.springframework.context.annotation.Import; @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { + /** + * Base packages that should be registered with {@link AutoConfigurationPackages}. + *

+ * Use {@link #basePackageClasses} for a type-safe alternative to String-based package + * names. + * @return the back package names + * @since 2.3.0 + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages} for specifying the packages to be + * registered with {@link AutoConfigurationPackages}. + *

+ * Consider creating a special no-op marker class or interface in each package that + * serves no purpose other than being referenced by this attribute. + * @return the base package classes + * @since 2.3.0 + */ + Class[] basePackageClasses() default {}; + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java index 520af8e4617..d37dc97f569 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -34,6 +34,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.context.annotation.DeterminableImports; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -120,12 +121,12 @@ public abstract class AutoConfigurationPackages { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { - register(registry, new PackageImport(metadata).getPackageName()); + register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set determineImports(AnnotationMetadata metadata) { - return Collections.singleton(new PackageImport(metadata)); + return Collections.singleton(new PackageImports(metadata)); } } @@ -133,16 +134,28 @@ public abstract class AutoConfigurationPackages { /** * Wrapper for a package import. */ - private static final class PackageImport { + private static final class PackageImports { - private final String packageName; + private final List packageNames; - PackageImport(AnnotationMetadata metadata) { - this.packageName = ClassUtils.getPackageName(metadata.getClassName()); + PackageImports(AnnotationMetadata metadata) { + AnnotationAttributes attributes = AnnotationAttributes + .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false)); + List packageNames = new ArrayList<>(); + for (String basePackage : attributes.getStringArray("basePackages")) { + packageNames.add(basePackage); + } + for (Class basePackageClass : attributes.getClassArray("basePackageClasses")) { + packageNames.add(basePackageClass.getPackage().getName()); + } + if (packageNames.isEmpty()) { + packageNames.add(ClassUtils.getPackageName(metadata.getClassName())); + } + this.packageNames = Collections.unmodifiableList(packageNames); } - String getPackageName() { - return this.packageName; + List getPackageNames() { + return this.packageNames; } @Override @@ -150,17 +163,17 @@ public abstract class AutoConfigurationPackages { if (obj == null || getClass() != obj.getClass()) { return false; } - return this.packageName.equals(((PackageImport) obj).packageName); + return this.packageNames.equals(((PackageImports) obj).packageNames); } @Override public int hashCode() { - return this.packageName.hashCode(); + return this.packageNames.hashCode(); } @Override public String toString() { - return "Package Import " + this.packageName; + return "Package Imports " + this.packageNames; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java index 433698ca482..d48346fd5d2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -20,12 +20,10 @@ import java.util.List; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar; import org.springframework.boot.autoconfigure.packagestest.one.FirstConfiguration; import org.springframework.boot.autoconfigure.packagestest.two.SecondConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -41,7 +39,8 @@ public class AutoConfigurationPackagesTests { @Test void setAndGet() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithRegistrar.class); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ConfigWithAutoConfigurationPackage.class); assertThat(AutoConfigurationPackages.get(context.getBeanFactory())) .containsExactly(getClass().getPackage().getName()); } @@ -63,21 +62,43 @@ public class AutoConfigurationPackagesTests { assertThat(packages).containsOnly(package1.getName(), package2.getName()); } + @Test + void whenBasePackagesAreSpecifiedThenTheyAreRegistered() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ConfigWithAutoConfigurationBasePackages.class); + List packages = AutoConfigurationPackages.get(context.getBeanFactory()); + assertThat(packages).containsExactly("com.example.alpha", "com.example.bravo"); + } + + @Test + void whenBasePackageClassesAreSpecifiedThenTheirPackagesAreRegistered() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ConfigWithAutoConfigurationBasePackageClasses.class); + List packages = AutoConfigurationPackages.get(context.getBeanFactory()); + assertThat(packages).containsOnly(FirstConfiguration.class.getPackage().getName(), + SecondConfiguration.class.getPackage().getName()); + } + @Configuration(proxyBeanMethods = false) - @Import(AutoConfigurationPackages.Registrar.class) - static class ConfigWithRegistrar { + @AutoConfigurationPackage + static class ConfigWithAutoConfigurationPackage { } @Configuration(proxyBeanMethods = false) - static class EmptyConfig { + @AutoConfigurationPackage(basePackages = { "com.example.alpha", "com.example.bravo" }) + static class ConfigWithAutoConfigurationBasePackages { } - /** - * Test helper to allow {@link Registrar} to be referenced from other packages. - */ - public static class TestRegistrar extends Registrar { + @Configuration(proxyBeanMethods = false) + @AutoConfigurationPackage(basePackageClasses = { FirstConfiguration.class, SecondConfiguration.class }) + static class ConfigWithAutoConfigurationBasePackageClasses { + + } + + @Configuration(proxyBeanMethods = false) + static class EmptyConfig { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java index 2a82907cd30..455ad596ce7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ImportAutoConfigurationImportSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -171,12 +171,12 @@ class ImportAutoConfigurationImportSelectorTests { @Test void determineImportsShouldNotSetPackageImport() throws Exception { - Class packageImportClass = ClassUtils.resolveClassName( - "org.springframework.boot.autoconfigure.AutoConfigurationPackages.PackageImport", null); + Class packageImportsClass = ClassUtils.resolveClassName( + "org.springframework.boot.autoconfigure.AutoConfigurationPackages.PackageImports", null); Set selectedImports = this.importSelector .determineImports(getAnnotationMetadata(ImportMetaAutoConfigurationExcludeWithUnrelatedOne.class)); for (Object selectedImport : selectedImports) { - assertThat(selectedImport).isNotInstanceOf(packageImportClass); + assertThat(selectedImport).isNotInstanceOf(packageImportsClass); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/one/FirstConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/one/FirstConfiguration.java index 5a2286d4bd0..c7b828f0d4d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/one/FirstConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/one/FirstConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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,9 +16,8 @@ package org.springframework.boot.autoconfigure.packagestest.one; -import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests.TestRegistrar; +import org.springframework.boot.autoconfigure.AutoConfigurationPackage; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; /** * Sample configuration used in {@code AutoConfigurationPackagesTests}. @@ -26,7 +25,7 @@ import org.springframework.context.annotation.Import; * @author Oliver Gierke */ @Configuration(proxyBeanMethods = false) -@Import(TestRegistrar.class) +@AutoConfigurationPackage public class FirstConfiguration { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/two/SecondConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/two/SecondConfiguration.java index dbe32ebbbd5..c8c5af4bb96 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/two/SecondConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packagestest/two/SecondConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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,10 +16,9 @@ package org.springframework.boot.autoconfigure.packagestest.two; +import org.springframework.boot.autoconfigure.AutoConfigurationPackage; import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests; -import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests.TestRegistrar; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; /** * Sample configuration used in {@link AutoConfigurationPackagesTests}. @@ -27,7 +26,7 @@ import org.springframework.context.annotation.Import; * @author Oliver Gierke */ @Configuration(proxyBeanMethods = false) -@Import(TestRegistrar.class) +@AutoConfigurationPackage public class SecondConfiguration { }