diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index 4bda6a0c19b..c6b5841a9ae 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -36,6 +36,8 @@ import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * Represents a user-defined {@link Configuration @Configuration} class. @@ -66,7 +68,7 @@ final class ConfigurationClass { private final Map> importedResources = new LinkedHashMap<>(); - private final Map beanRegistrars = new LinkedHashMap<>(); + private final MultiValueMap beanRegistrars = new LinkedMultiValueMap<>(); private final Map importBeanDefinitionRegistrars = new LinkedHashMap<>(); @@ -224,10 +226,10 @@ final class ConfigurationClass { } void addBeanRegistrar(String sourceClassName, BeanRegistrar beanRegistrar) { - this.beanRegistrars.put(sourceClassName, beanRegistrar); + this.beanRegistrars.add(sourceClassName, beanRegistrar); } - public Map getBeanRegistrars() { + public MultiValueMap getBeanRegistrars() { return this.beanRegistrars; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index d221a529bf7..86784364c77 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -56,6 +56,7 @@ import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** @@ -421,13 +422,13 @@ class ConfigurationClassBeanDefinitionReader { registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator)); } - private void loadBeanDefinitionsFromBeanRegistrars(Map registrars) { + private void loadBeanDefinitionsFromBeanRegistrars(MultiValueMap registrars) { if (!(this.registry instanceof ListableBeanFactory beanFactory)) { throw new IllegalStateException("Cannot support bean registrars since " + this.registry.getClass().getName() + " does not implement ListableBeanFactory"); } - registrars.values().forEach(registrar -> registrar.register(new BeanRegistryAdapter( - this.registry, beanFactory, this.environment, registrar.getClass()), this.environment)); + registrars.values().forEach(registrarList -> registrarList.forEach(registrar -> registrar.register(new BeanRegistryAdapter( + this.registry, beanFactory, this.environment, registrar.getClass()), this.environment))); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 91812cc9e0e..756b4b888a0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -199,7 +199,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private List propertySourceDescriptors = Collections.emptyList(); - private final Map beanRegistrars = new LinkedHashMap<>(); + private final MultiValueMap beanRegistrars = new LinkedMultiValueMap<>(); @Override @@ -453,7 +453,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } this.reader.loadBeanDefinitions(configClasses); for (ConfigurationClass configClass : configClasses) { - this.beanRegistrars.putAll(configClass.getBeanRegistrars()); + this.beanRegistrars.addAll(configClass.getBeanRegistrars()); } alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); @@ -857,13 +857,13 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo private static final String ENVIRONMENT_VARIABLE = "environment"; - private final Map beanRegistrars; + private final MultiValueMap beanRegistrars; private final ConfigurableListableBeanFactory beanFactory; private final AotServices aotProcessors; - public BeanRegistrarAotContribution(Map beanRegistrars, ConfigurableListableBeanFactory beanFactory) { + public BeanRegistrarAotContribution(MultiValueMap beanRegistrars, ConfigurableListableBeanFactory beanFactory) { this.beanRegistrars = beanRegistrars; this.beanFactory = beanFactory; this.aotProcessors = AotServices.factoriesAndBeans(this.beanFactory).load(BeanRegistrationAotProcessor.class); @@ -948,28 +948,29 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo Builder code = CodeBlock.builder(); Builder metadataReaderFactoryCode = null; NameAllocator nameAllocator = new NameAllocator(); - for (Map.Entry beanRegistrarEntry : this.beanRegistrars.entrySet()) { - BeanRegistrar beanRegistrar = beanRegistrarEntry.getValue(); - String beanRegistrarName = nameAllocator.newName(StringUtils.uncapitalize(beanRegistrar.getClass().getSimpleName())); - code.addStatement("$T $L = new $T()", beanRegistrar.getClass(), beanRegistrarName, beanRegistrar.getClass()); - if (beanRegistrar instanceof ImportAware) { - if (metadataReaderFactoryCode == null) { - metadataReaderFactoryCode = CodeBlock.builder(); - metadataReaderFactoryCode.addStatement("$T metadataReaderFactory = new $T()", - MetadataReaderFactory.class, CachingMetadataReaderFactory.class); + for (Map.Entry> beanRegistrarEntry : this.beanRegistrars.entrySet()) { + for (BeanRegistrar beanRegistrar : beanRegistrarEntry.getValue()) { + String beanRegistrarName = nameAllocator.newName(StringUtils.uncapitalize(beanRegistrar.getClass().getSimpleName())); + code.addStatement("$T $L = new $T()", beanRegistrar.getClass(), beanRegistrarName, beanRegistrar.getClass()); + if (beanRegistrar instanceof ImportAware) { + if (metadataReaderFactoryCode == null) { + metadataReaderFactoryCode = CodeBlock.builder(); + metadataReaderFactoryCode.addStatement("$T metadataReaderFactory = new $T()", + MetadataReaderFactory.class, CachingMetadataReaderFactory.class); + } + code.beginControlFlow("try") + .addStatement("$L.setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())", + beanRegistrarName, beanRegistrarEntry.getKey()) + .nextControlFlow("catch ($T ex)", IOException.class) + .addStatement("throw new $T(\"Failed to read metadata for '$L'\", ex)", + IllegalStateException.class, beanRegistrarEntry.getKey()) + .endControlFlow(); } - code.beginControlFlow("try") - .addStatement("$L.setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())", - beanRegistrarName, beanRegistrarEntry.getKey()) - .nextControlFlow("catch ($T ex)", IOException.class) - .addStatement("throw new $T(\"Failed to read metadata for '$L'\", ex)", - IllegalStateException.class, beanRegistrarEntry.getKey()) - .endControlFlow(); + code.addStatement("$L.register(new $T(($T)$L, $L, $L, $T.class, $L), $L)", beanRegistrarName, + BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, + BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, ENVIRONMENT_VARIABLE, beanRegistrar.getClass(), + CUSTOMIZER_MAP_VARIABLE, ENVIRONMENT_VARIABLE); } - code.addStatement("$L.register(new $T(($T)$L, $L, $L, $T.class, $L), $L)", beanRegistrarName, - BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, - BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, ENVIRONMENT_VARIABLE, beanRegistrar.getClass(), - CUSTOMIZER_MAP_VARIABLE, ENVIRONMENT_VARIABLE); } return (metadataReaderFactoryCode == null ? code.build() : metadataReaderFactoryCode.add(code.build()).build()); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java index 0bd91a6009b..58e00f7c858 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java @@ -23,6 +23,8 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.testfixture.beans.factory.BarRegistrar; +import org.springframework.context.testfixture.beans.factory.FooRegistrar; import org.springframework.context.testfixture.beans.factory.GenericBeanRegistrar; import org.springframework.context.testfixture.beans.factory.ImportAwareBeanRegistrar; import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Bar; @@ -32,6 +34,7 @@ import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar import org.springframework.context.testfixture.context.annotation.registrar.BeanRegistrarConfiguration; import org.springframework.context.testfixture.context.annotation.registrar.GenericBeanRegistrarConfiguration; import org.springframework.context.testfixture.context.annotation.registrar.ImportAwareBeanRegistrarConfiguration; +import org.springframework.context.testfixture.context.annotation.registrar.MultipleBeanRegistrarsConfiguration; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -93,4 +96,13 @@ public class BeanRegistrarConfigurationTests { .isEqualTo(ImportAwareBeanRegistrarConfiguration.class.getName()); } + @Test + void multipleBeanRegistrars() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(MultipleBeanRegistrarsConfiguration.class); + context.refresh(); + assertThat(context.getBean(FooRegistrar.Foo.class)).isNotNull(); + assertThat(context.getBean(BarRegistrar.Bar.class)).isNotNull(); + } + } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/BarRegistrar.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/BarRegistrar.java new file mode 100644 index 00000000000..caa03890352 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/BarRegistrar.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-present 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 + * + * https://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.context.testfixture.beans.factory; + +import org.springframework.beans.factory.BeanRegistrar; +import org.springframework.beans.factory.BeanRegistry; +import org.springframework.core.env.Environment; + +public class BarRegistrar implements BeanRegistrar { + + @Override + public void register(BeanRegistry registry, Environment env) { + registry.registerBean(Bar.class); + } + + public record Bar() {} +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/FooRegistrar.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/FooRegistrar.java new file mode 100644 index 00000000000..9a546ea25f2 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/FooRegistrar.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-present 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 + * + * https://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.context.testfixture.beans.factory; + +import org.springframework.beans.factory.BeanRegistrar; +import org.springframework.beans.factory.BeanRegistry; +import org.springframework.core.env.Environment; + +public class FooRegistrar implements BeanRegistrar { + + @Override + public void register(BeanRegistry registry, Environment env) { + registry.registerBean(Foo.class); + } + + public record Foo() {} +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/MultipleBeanRegistrarsConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/MultipleBeanRegistrarsConfiguration.java new file mode 100644 index 00000000000..34c298cae12 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/MultipleBeanRegistrarsConfiguration.java @@ -0,0 +1,25 @@ +/* + * Copyright 2002-present 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 + * + * https://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.context.testfixture.context.annotation.registrar; + +import org.springframework.context.annotation.Import; +import org.springframework.context.testfixture.beans.factory.BarRegistrar; +import org.springframework.context.testfixture.beans.factory.FooRegistrar; + +@Import({FooRegistrar.class, BarRegistrar.class}) +public class MultipleBeanRegistrarsConfiguration { +}