Register original bean name as alias if not taken already

Closes gh-37038
This commit is contained in:
Juergen Hoeller
2026-07-16 10:45:16 +02:00
parent c4c0a84f83
commit 40f7d56ed4
4 changed files with 45 additions and 6 deletions
@@ -38,9 +38,11 @@ public interface ConfigurationBeanNameGenerator extends BeanNameGenerator {
/**
* Derive a default bean name for the given {@link Bean @Bean} method,
* taking into account the specified {@link Bean#name() name} attribute.
* <p>As of 7.1, the original {@code @Bean} name (typically the method name)
* will be registered as an alias if that name has not been taken already.
* @param beanMethod the method metadata for the {@link Bean @Bean} method
* @param beanName the {@link Bean#name() name} attribute or {@code null} if
* none is specified
* @param beanName the {@link Bean#name() name} attribute or {@code null}
* if none is specified
* @return the default bean name to use
*/
String deriveBeanName(MethodMetadata beanMethod, @Nullable String beanName);
@@ -210,6 +210,11 @@ class ConfigurationClassBeanDefinitionReader {
String localBeanName = defaultBeanName(beanName, methodName);
beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ?
cbng.deriveBeanName(metadata, beanName) : localBeanName);
if (!localBeanName.equals(beanName) && !this.registry.containsBeanDefinition(localBeanName) &&
!this.registry.isAlias(localBeanName)) {
// Register original name as alias unless registered already.
this.registry.registerAlias(beanName, localBeanName);
}
if (explicitNames.length > 0) {
// Register aliases even when overridden below.
for (int i = 1; i < explicitNames.length; i++) {
@@ -32,10 +32,13 @@ import org.springframework.core.type.MethodMetadata;
* {@code @Bean} methods (which uses the plain method name), primarily for use
* in large applications with potential bean name overlaps. Favor this bean
* naming strategy over {@code FullyQualifiedAnnotationBeanNameGenerator} if
* you expect such naming conflicts for {@code @Bean} methods, as long as the
* application does not depend on {@code @Bean} method names as bean names.
* Where the name does matter, make sure to declare {@code @Bean("myBeanName")}
* in such a scenario, even if it repeats the method name as the bean name.
* you expect such naming conflicts for {@code @Bean} methods.
*
* <p>As of 7.1, the original {@code @Bean} method name will be registered
* as an alias if that name has not been taken already: effectively on first
* occurrence, whereas any later {@code @Bean} methods of the same name will
* not have aliases applied. This preserves the availability of common beans
* under the original bean names for retrieval and injection purposes.
*
* @author Juergen Hoeller
* @since 7.0
@@ -105,10 +105,31 @@ class AnnotationConfigApplicationContextTests {
context.getBean(Config.class.getName() + ".testBean");
assertThat(context.getBean(NameConfig.class.getName() + ".name")).isEqualTo("foo");
assertThat(context.getBean(NameConfig.class.getName() + ".prefixName")).isEqualTo("barfoo");
assertThat(context.getBean("name")).isEqualTo("foo");
assertThat(context.getBean("prefixName")).isEqualTo("barfoo");
Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
assertThat(beans).hasSize(2);
}
@Test
void registerAndRefreshWithOverlappingFullyQualifiedBeanNames() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setBeanNameGenerator(FullyQualifiedConfigurationBeanNameGenerator.INSTANCE);
context.setAllowBeanDefinitionOverriding(false);
context.register(Config.class, NameConfig.class, OtherNameConfig.class);
context.refresh();
context.getBean(Config.class.getName() + ".testBean");
assertThat(context.getBean(NameConfig.class.getName() + ".name")).isEqualTo("foo");
assertThat(context.getBean(NameConfig.class.getName() + ".prefixName")).isEqualTo("barfoo");
assertThat(context.getBean(OtherNameConfig.class.getName() + ".name")).isEqualTo("fooX");
assertThat(context.getBean(OtherNameConfig.class.getName() + ".prefixName")).isEqualTo("barXfooX");
assertThat(context.getBean("name")).isEqualTo("foo");
assertThat(context.getBean("prefixName")).isEqualTo("barfoo");
Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
assertThat(beans).hasSize(3);
}
@Test
void getBeansWithAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@@ -632,6 +653,14 @@ class AnnotationConfigApplicationContextTests {
@Bean(autowireCandidate = false) String prefixName() { return "bar" + name(); }
}
@Configuration
static class OtherNameConfig {
@Bean String name() { return "fooX"; }
@Bean(autowireCandidate = false) String prefixName() { return "barX" + name(); }
}
@Configuration
@Import(NameConfig.class)
static class AutowiredConfig {