Fix @Import with multiple bean registrars

This commit uses a MultiValueMap instead of a Map to store bean
registrars, allowing to support multiple bean registrars imported by
the same configuration class.

Closes gh-35653
This commit is contained in:
Sébastien Deleuze
2025-10-20 17:36:43 +02:00
parent b65390828e
commit 5fd3c9c449
7 changed files with 133 additions and 30 deletions
@@ -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<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<>();
private final Map<String, BeanRegistrar> beanRegistrars = new LinkedHashMap<>();
private final MultiValueMap<String, BeanRegistrar> beanRegistrars = new LinkedMultiValueMap<>();
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> 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<String, BeanRegistrar> getBeanRegistrars() {
public MultiValueMap<String, BeanRegistrar> getBeanRegistrars() {
return this.beanRegistrars;
}
@@ -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<String, BeanRegistrar> registrars) {
private void loadBeanDefinitionsFromBeanRegistrars(MultiValueMap<String, BeanRegistrar> 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)));
}
@@ -199,7 +199,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private List<PropertySourceDescriptor> propertySourceDescriptors = Collections.emptyList();
private final Map<String, BeanRegistrar> beanRegistrars = new LinkedHashMap<>();
private final MultiValueMap<String, BeanRegistrar> 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<String, BeanRegistrar> beanRegistrars;
private final MultiValueMap<String, BeanRegistrar> beanRegistrars;
private final ConfigurableListableBeanFactory beanFactory;
private final AotServices<BeanRegistrationAotProcessor> aotProcessors;
public BeanRegistrarAotContribution(Map<String, BeanRegistrar> beanRegistrars, ConfigurableListableBeanFactory beanFactory) {
public BeanRegistrarAotContribution(MultiValueMap<String, BeanRegistrar> 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<String, BeanRegistrar> 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<String, List<BeanRegistrar>> 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());
}