From 123b90fa64ea112e1b098ec628e647fe7d28a925 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 24 Nov 2014 21:24:51 +0100 Subject: [PATCH] Register all packages where @EnableAutoConfiguration is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, when @EnableAutoConfiguration was used in multiple packages, the last @EnableAutoConfiguration that was processed would win and only its package would be stored as an auto-configuration package. This commit updates AutoConfigurationPackages to allow multiple package name registrations. AutoConfigurationPackages.set(…) has been altered to augment the constructor arguments of the BeanDefinition registered for the initial call to the method so that the packages handed to the method call will be added to the bean definition and not replace the previous ones. The method has been renamed register(…) to reflect the changed behavior. Closes gh-1994 --- .../AutoConfigurationPackages.java | 57 ++++++++++++++----- .../AutoConfigurationPackagesTests.java | 31 ++++++++++ ...TestAutoConfigurationPackageRegistrar.java | 2 +- .../packages/one/FirstConfiguration.java | 27 +++++++++ .../packages/two/SecondConfiguration.java | 27 +++++++++ 5 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/one/FirstConfiguration.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/two/SecondConfiguration.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java index 56e38595d3c..e9cc06fe104 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationPackages.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; @@ -24,6 +25,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -39,6 +42,7 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Dave Syer + * @author Oliver Gierke */ public abstract class AutoConfigurationPackages { @@ -75,21 +79,46 @@ public abstract class AutoConfigurationPackages { } /** - * Programmatically set the auto-configuration package names. You can use this method - * to manually define the base packages that will be used for a given - * {@link BeanDefinitionRegistry}. Generally it's recommended that you don't call this - * method directly, but instead rely on the default convention where the package name - * is set from your {@code @EnableAutoConfiguration} configuration class. + * Programmatically registers the auto-configuration package names. Subsequent + * invocations will add the given package names to those that have already been + * registered. You can use this method to manually define the base packages that will + * be used for a given {@link BeanDefinitionRegistry}. Generally it's recommended that + * you don't call this method directly, but instead rely on the default convention + * where the package name is set from your {@code @EnableAutoConfiguration} + * configuration class or classes. * @param registry the bean definition registry - * @param packageNames the pacakge names to set + * @param packageNames the package names to set */ - public static void set(BeanDefinitionRegistry registry, String... packageNames) { - GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); - beanDefinition.setBeanClass(BasePackages.class); - beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, - packageNames); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(BEAN, beanDefinition); + public static void register(BeanDefinitionRegistry registry, String... packageNames) { + + if (registry.containsBeanDefinition(BEAN)) { + BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); + ConstructorArgumentValues constructorArguments = beanDefinition + .getConstructorArgumentValues(); + constructorArguments.addIndexedArgumentValue(0, + augmentBasePackages(constructorArguments, packageNames)); + } + else { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(BasePackages.class); + beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, + packageNames); + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(BEAN, beanDefinition); + } + } + + private static String[] augmentBasePackages( + ConstructorArgumentValues constructorArguments, String[] packageNames) { + + ValueHolder valueHolder = constructorArguments.getIndexedArgumentValue(0, + List.class); + + List packages = new ArrayList( + Arrays.asList((String[]) valueHolder.getValue())); + packages.addAll(Arrays.asList(packageNames)); + + return packages.toArray(new String[packages.size()]); } /** @@ -102,7 +131,7 @@ public abstract class AutoConfigurationPackages { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { - set(registry, ClassUtils.getPackageName(metadata.getClassName())); + register(registry, ClassUtils.getPackageName(metadata.getClassName())); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java index bef0406decb..f22f52fc97b 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationPackagesTests.java @@ -17,21 +17,28 @@ package org.springframework.boot.autoconfigure; import java.util.Collections; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar; +import org.springframework.boot.autoconfigure.packages.one.FirstConfiguration; +import org.springframework.boot.autoconfigure.packages.two.SecondConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; /** * Tests for {@link AutoConfigurationPackages}. * * @author Phillip Webb + * @author Oliver Gierke */ @SuppressWarnings("resource") public class AutoConfigurationPackagesTests { @@ -57,13 +64,37 @@ public class AutoConfigurationPackagesTests { AutoConfigurationPackages.get(context.getBeanFactory()); } + @Test + public void detectsMultipleAutoConfigurationPackages() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + FirstConfiguration.class, SecondConfiguration.class); + + List packages = AutoConfigurationPackages.get(context.getBeanFactory()); + + assertThat( + packages, + hasItems(FirstConfiguration.class.getPackage().getName(), + SecondConfiguration.class.getPackage().getName())); + assertThat(packages, hasSize(2)); + } + @Configuration @Import(AutoConfigurationPackages.Registrar.class) static class ConfigWithRegistrar { + } @Configuration static class EmptyConfig { + + } + + /** + * Test helper to allow {@link Registrar} to be referenced from other packages. + */ + public static class TestRegistrar extends Registrar { + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationPackageRegistrar.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationPackageRegistrar.java index 31c8bac222e..3e5dcbe3d2a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationPackageRegistrar.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/TestAutoConfigurationPackageRegistrar.java @@ -39,7 +39,7 @@ public class TestAutoConfigurationPackageRegistrar implements AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata .getAnnotationAttributes(TestAutoConfigurationPackage.class.getName(), true)); - AutoConfigurationPackages.set(registry, + AutoConfigurationPackages.register(registry, ClassUtils.getPackageName(attributes.getString("value"))); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/one/FirstConfiguration.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/one/FirstConfiguration.java new file mode 100644 index 00000000000..618c8cedffe --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/one/FirstConfiguration.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2014 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.packages.one; + +import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests.TestRegistrar; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(TestRegistrar.class) +public class FirstConfiguration { + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/two/SecondConfiguration.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/two/SecondConfiguration.java new file mode 100644 index 00000000000..c8c648a4f33 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/packages/two/SecondConfiguration.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2014 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.packages.two; + +import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests.TestRegistrar; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(TestRegistrar.class) +public class SecondConfiguration { + +}