diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java index b40ada4e5ec..4c8bfeaee7d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java @@ -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. + *

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); 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 a5f64e71afe..e9eb836abe5 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 @@ -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++) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java index 68e88ebfab2..858a02bdb47 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java @@ -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. + * + *

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 diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 1864d8899e3..ae774623fb9 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -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 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 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 {