diff --git a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java index 4985d68c6e8..808aa64d11a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java @@ -45,17 +45,15 @@ abstract class BeanAnnotationHelper { public static String determineBeanNameFor(Method beanMethod, ConfigurableBeanFactory beanFactory) { String beanName = retrieveBeanNameFor(beanMethod); - if (!beanName.isEmpty()) { - return beanName; + if (beanFactory.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR) + instanceof ConfigurationBeanNameGenerator cbng) { + return cbng.deriveBeanName(MethodMetadata.introspect(beanMethod), (!beanName.isEmpty() ? beanName : null)); } - return (beanFactory.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR) - instanceof ConfigurationBeanNameGenerator cbng ? - cbng.deriveBeanName(MethodMetadata.introspect(beanMethod)) : beanMethod.getName()); + return determineBeanNameFrom(beanName, beanMethod); } public static String determineBeanNameFor(Method beanMethod) { - String beanName = retrieveBeanNameFor(beanMethod); - return (!beanName.isEmpty() ? beanName : beanMethod.getName()); + return determineBeanNameFrom(retrieveBeanNameFor(beanMethod), beanMethod); } private static String retrieveBeanNameFor(Method beanMethod) { @@ -77,6 +75,10 @@ abstract class BeanAnnotationHelper { return beanName; } + private static String determineBeanNameFrom(String derivedBeanName, Method beanMethod) { + return (!derivedBeanName.isEmpty() ? derivedBeanName : beanMethod.getName()); + } + public static boolean isScopedProxy(Method beanMethod) { Boolean scopedProxy = scopedProxyCache.get(beanMethod); if (scopedProxy == null) { @@ -88,4 +90,9 @@ abstract class BeanAnnotationHelper { return scopedProxy; } + static void clearCaches() { + scopedProxyCache.clear(); + beanNameCache.clear(); + } + } 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 52f51affd43..2787c2b8245 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 @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.type.MethodMetadata; @@ -23,10 +25,10 @@ import org.springframework.core.type.MethodMetadata; * Extended variant of {@link BeanNameGenerator} for * {@link Configuration @Configuration} class purposes, not only covering * bean name generation for component and configuration classes themselves - * but also for {@link Bean @Bean} methods without a {@link Bean#name() name} - * attribute specified on the annotation itself. + * but also for {@link Bean @Bean} methods. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 7.0 * @see AnnotationConfigApplicationContext#setBeanNameGenerator * @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR @@ -35,10 +37,11 @@ public interface ConfigurationBeanNameGenerator extends BeanNameGenerator { /** * Derive a default bean name for the given {@link Bean @Bean} method, - * in the absence of a {@link Bean#name() name} attribute specified. + * providing the {@link Bean#name() name} attribute specified. * @param beanMethod the method metadata for the {@link Bean @Bean} method + * @param beanName the {@link Bean#name() name} attribute or {@code null} if non is specified * @return the default bean name to use */ - String deriveBeanName(MethodMetadata beanMethod); + 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 09bdae4bb8a..fe9fbd44ed0 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 @@ -197,22 +197,16 @@ class ConfigurationClassBeanDefinitionReader { // Consider name and any aliases. String[] explicitNames = bean.getStringArray("name"); - String beanName; - String localBeanName; - if (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) { - beanName = explicitNames[0]; - localBeanName = beanName; + String beanName = (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) ? explicitNames[0] : null; + String localBeanName = defaultBeanName(beanName, methodName); + beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ? + cbng.deriveBeanName(metadata, beanName) : defaultBeanName(beanName, methodName)); + if (explicitNames.length > 0) { // Register aliases even when overridden below. for (int i = 1; i < explicitNames.length; i++) { this.registry.registerAlias(beanName, explicitNames[i]); } } - else { - // Default bean name derived from method name. - beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ? - cbng.deriveBeanName(metadata) : methodName); - localBeanName = methodName; - } ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, localBeanName); @@ -306,6 +300,10 @@ class ConfigurationClassBeanDefinitionReader { this.registry.registerBeanDefinition(beanName, beanDefToRegister); } + private static String defaultBeanName(@Nullable String beanName, String methodName) { + return (beanName != null ? beanName : methodName); + } + @SuppressWarnings("NullAway") // Reflection private boolean isOverriddenByExistingDefinition( BeanMethod beanMethod, String beanName, ConfigurationClassBeanDefinition newBeanDef) { 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 677d74dc11c..68e88ebfab2 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 @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import org.jspecify.annotations.Nullable; + import org.springframework.core.type.MethodMetadata; /** @@ -23,8 +25,8 @@ import org.springframework.core.type.MethodMetadata; * {@link Configuration @Configuration} class purposes, not only enforcing * fully-qualified names for component and configuration classes themselves * but also fully-qualified default bean names ("className.methodName") for - * {@link Bean @Bean} methods. This only affects methods without an explicit - * {@link Bean#name() name} attribute specified. + * {@link Bean @Bean} methods. By default, this only affects methods without + * an explicit {@link Bean#name() name} attribute specified. * *

This provides an alternative to the default bean name generation for * {@code @Bean} methods (which uses the plain method name), primarily for use @@ -54,8 +56,8 @@ public class FullyQualifiedConfigurationBeanNameGenerator extends FullyQualified @Override - public String deriveBeanName(MethodMetadata beanMethod) { - return beanMethod.getDeclaringClassName() + "." + beanMethod.getMethodName(); + public String deriveBeanName(MethodMetadata beanMethod, @Nullable String beanName) { + return (beanName != null ? beanName : beanMethod.getDeclaringClassName() + "." + beanMethod.getMethodName()); } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BeanAnnotationHelperTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BeanAnnotationHelperTests.java new file mode 100644 index 00000000000..f6aa54ded51 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/BeanAnnotationHelperTests.java @@ -0,0 +1,199 @@ +/* + * 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.annotation; + +import java.lang.reflect.Method; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.util.ReflectionUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link BeanAnnotationHelper}. + * + * @author Stephane Nicoll + */ +class BeanAnnotationHelperTests { + + @BeforeEach + void clearCache() { + BeanAnnotationHelper.clearCaches(); + } + + @Test + void determineBeanNameWhenNoGeneratorAndNoBeanName() { + String beanName = BeanAnnotationHelper.determineBeanNameFor( + sampleMethod("beanWithoutName"), createBeanFactoryWithBeanNameGenerator(null)); + assertThat(beanName).isEqualTo("beanWithoutName"); + } + + @ParameterizedTest + @ValueSource(strings = { "beanWithName", "beanWithMultipleNames" }) + void determineBeanNameWhenNoGeneratorAndBeanName(String methodName) { + String beanName = BeanAnnotationHelper.determineBeanNameFor( + sampleMethod(methodName), createBeanFactoryWithBeanNameGenerator(null)); + assertThat(beanName).isEqualTo("specificName"); + } + + @Test + void determineBeanNameWhenBeanNameGeneratorAndNoBeanName() { + BeanNameGenerator beanNameGenerator = mock(BeanNameGenerator.class); + String beanName = BeanAnnotationHelper.determineBeanNameFor( + sampleMethod("beanWithoutName"), createBeanFactoryWithBeanNameGenerator(beanNameGenerator)); + assertThat(beanName).isEqualTo("beanWithoutName"); + verifyNoInteractions(beanNameGenerator); + } + + @ParameterizedTest + @ValueSource(strings = { "beanWithName", "beanWithMultipleNames" }) + void determineBeanNameWhenBeanNameGeneratorAndBeanName(String methodName) { + BeanNameGenerator beanNameGenerator = mock(BeanNameGenerator.class); + String beanName = BeanAnnotationHelper.determineBeanNameFor( + sampleMethod(methodName), createBeanFactoryWithBeanNameGenerator(beanNameGenerator)); + assertThat(beanName).isEqualTo("specificName"); + verifyNoInteractions(beanNameGenerator); + } + + @Test + void determineBeanNameWhenConfigurationBeanNameGeneratorAndNoBeanName() { + ConfigurationBeanNameGenerator beanNameGenerator = mock(ConfigurationBeanNameGenerator.class); + when(beanNameGenerator.deriveBeanName(any(), isNull())).thenReturn("generatedBeanName"); + String beanName = BeanAnnotationHelper.determineBeanNameFor( + sampleMethod("beanWithoutName"), createBeanFactoryWithBeanNameGenerator(beanNameGenerator)); + assertThat(beanName).isEqualTo("generatedBeanName"); + verify(beanNameGenerator).deriveBeanName(any(), isNull()); + } + + @ParameterizedTest + @ValueSource(strings = { "beanWithName", "beanWithMultipleNames" }) + void determineBeanNameWhenConfigurationBeanNameGeneratorAndBeanName(String methodName) { + ConfigurationBeanNameGenerator beanNameGenerator = mock(ConfigurationBeanNameGenerator.class); + given(beanNameGenerator.deriveBeanName(any(), eq("specificName"))).willReturn("generatedBeanName"); + String beanName = BeanAnnotationHelper.determineBeanNameFor( + sampleMethod(methodName), createBeanFactoryWithBeanNameGenerator(beanNameGenerator)); + assertThat(beanName).isEqualTo("generatedBeanName"); + verify(beanNameGenerator).deriveBeanName(any(), eq("specificName")); + } + + @Test + void determineBeanNameInCacheWhenNoGeneratorAndNoBeanName() { + Method method = sampleMethod("beanWithoutName"); + ConfigurableBeanFactory beanFactory = createBeanFactoryWithBeanNameGenerator(null); + String beanName = BeanAnnotationHelper.determineBeanNameFor(method, beanFactory); + assertThat(BeanAnnotationHelper.determineBeanNameFor(method, beanFactory)).isEqualTo(beanName); + } + + @ParameterizedTest + @ValueSource(strings = { "beanWithName", "beanWithMultipleNames" }) + void determineBeanNameInCacheWhenNoGeneratorAndBeanName(String methodName) { + Method method = sampleMethod(methodName); + ConfigurableBeanFactory beanFactory = createBeanFactoryWithBeanNameGenerator(null); + String beanName = BeanAnnotationHelper.determineBeanNameFor(method, beanFactory); + assertThat(BeanAnnotationHelper.determineBeanNameFor(method, beanFactory)).isEqualTo(beanName); + } + + @Test + void determineBeanNameInCacheWhenBeanNameGeneratorAndNoBeanName() { + BeanNameGenerator beanNameGenerator = mock(BeanNameGenerator.class); + Method method = sampleMethod("beanWithoutName"); + ConfigurableBeanFactory beanFactory = createBeanFactoryWithBeanNameGenerator(beanNameGenerator); + String beanName = BeanAnnotationHelper.determineBeanNameFor(method, beanFactory); + assertThat(BeanAnnotationHelper.determineBeanNameFor(method, beanFactory)).isEqualTo(beanName); + verifyNoInteractions(beanNameGenerator); + } + + @ParameterizedTest + @ValueSource(strings = { "beanWithName", "beanWithMultipleNames" }) + void determineBeanNameInCacheWhenBeanNameGeneratorAndBeanName(String methodName) { + BeanNameGenerator beanNameGenerator = mock(BeanNameGenerator.class); + Method method = sampleMethod(methodName); + ConfigurableBeanFactory beanFactory = createBeanFactoryWithBeanNameGenerator(beanNameGenerator); + String beanName = BeanAnnotationHelper.determineBeanNameFor(method, beanFactory); + assertThat(BeanAnnotationHelper.determineBeanNameFor(method, beanFactory)).isEqualTo(beanName); + verifyNoInteractions(beanNameGenerator); + } + + @Test + void determineBeanNameInCacheWhenConfigurationBeanNameGeneratorAndNoBeanName() { + ConfigurationBeanNameGenerator beanNameGenerator = mock(ConfigurationBeanNameGenerator.class); + when(beanNameGenerator.deriveBeanName(any(), isNull())) + .thenReturn("generatedBeanName").thenReturn("generatedBeanName"); + Method method = sampleMethod("beanWithoutName"); + ConfigurableBeanFactory beanFactory = createBeanFactoryWithBeanNameGenerator(beanNameGenerator); + String beanName = BeanAnnotationHelper.determineBeanNameFor(method, beanFactory); + assertThat(BeanAnnotationHelper.determineBeanNameFor(method, beanFactory)).isEqualTo(beanName); + verify(beanNameGenerator, times(2)).deriveBeanName(any(), isNull()); + } + + @ParameterizedTest + @ValueSource(strings = { "beanWithName", "beanWithMultipleNames" }) + void determineBeanNameInCacheWhenConfigurationBeanNameGeneratorAndBeanName(String methodName) { + ConfigurationBeanNameGenerator beanNameGenerator = mock(ConfigurationBeanNameGenerator.class); + given(beanNameGenerator.deriveBeanName(any(), eq("specificName"))) + .willReturn("generatedBeanName").willReturn("generatedBeanName"); + Method method = sampleMethod(methodName); + ConfigurableBeanFactory beanFactory = createBeanFactoryWithBeanNameGenerator(beanNameGenerator); + String beanName = BeanAnnotationHelper.determineBeanNameFor(method, beanFactory); + assertThat(BeanAnnotationHelper.determineBeanNameFor(method, beanFactory)).isEqualTo(beanName); + verify(beanNameGenerator, times(2)).deriveBeanName(any(), eq("specificName")); + } + + private static Method sampleMethod(String name) { + return Objects.requireNonNull(ReflectionUtils.findMethod(Samples.class, name)); + } + + private static ConfigurableBeanFactory createBeanFactoryWithBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) { + ConfigurableBeanFactory beanFactory = new DefaultListableBeanFactory(); + if (beanNameGenerator != null) { + beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator); + } + return beanFactory; + } + + + static class Samples { + + @Bean + private void beanWithoutName() {} + + @Bean(name = "specificName") + private void beanWithName() {} + + @Bean(name = { "specificName", "specificName2", "specificName3" }) + private void beanWithMultipleNames() {} + + } +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 353c1586957..0e0f877b735 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -26,6 +26,8 @@ import java.util.Map; import jakarta.annotation.PostConstruct; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.stubbing.Answer; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.interceptor.SimpleTraceInterceptor; @@ -50,6 +52,7 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.ChildBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -65,6 +68,7 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DescriptiveResource; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.type.MethodMetadata; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -72,11 +76,20 @@ import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; /** + * Tests for {@link ConfigurationClassPostProcessor}. + * * @author Chris Beams * @author Juergen Hoeller * @author Sam Brannen + * @author Stephane Nicoll */ class ConfigurationClassPostProcessorTests { @@ -536,6 +549,67 @@ class ConfigurationClassPostProcessorTests { pp.postProcessBeanFactory(bf2)); // second invocation for bf2 -- should throw } + @Test + void beanDefinitionsFromBeanMethodWithoutBeanNameGenerator() { + beanFactory.registerBeanDefinition("config", new RootBeanDefinition(BeanNamesConfig.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); + assertThat(beanFactory.getBeanDefinitionNames()) + .containsOnly("config", "beanWithoutName", "specificName", "specificNames"); + assertThat(beanFactory.getBean("beanWithoutName")).isEqualTo("beanWithoutName"); + assertThat(beanFactory.getBean("specificName")).isEqualTo("beanWithName"); + assertThat(beanFactory.getBean("specificNames")).isEqualTo("beanWithMultipleNames"); + assertThat(beanFactory.getBean("specificNames2")).isEqualTo("beanWithMultipleNames"); + assertThat(beanFactory.getBean("specificNames3")).isEqualTo("beanWithMultipleNames"); + } + + @Test + void beanDefinitionsFromBeanMethodWithBeanNameGenerator() { + BeanNameGenerator beanNameGenerator = mock(BeanNameGenerator.class); + beanFactory.registerBeanDefinition("config", new RootBeanDefinition(BeanNamesConfig.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.setBeanNameGenerator(beanNameGenerator); + pp.postProcessBeanFactory(beanFactory); + assertThat(beanFactory.getBeanDefinitionNames()) + .containsOnly("config", "beanWithoutName", "specificName", "specificNames"); + assertThat(beanFactory.getBean("beanWithoutName")).isEqualTo("beanWithoutName"); + assertThat(beanFactory.getBean("specificName")).isEqualTo("beanWithName"); + assertThat(beanFactory.getBean("specificNames")).isEqualTo("beanWithMultipleNames"); + assertThat(beanFactory.getBean("specificNames2")).isEqualTo("beanWithMultipleNames"); + assertThat(beanFactory.getBean("specificNames3")).isEqualTo("beanWithMultipleNames"); + verifyNoInteractions(beanNameGenerator); + } + + @Test + void beanDefinitionsFromBeanMethodWithConfigurationBeanNameGenerator() { + ConfigurationBeanNameGenerator beanNameGenerator = mock(ConfigurationBeanNameGenerator.class); + Answer answer = invocation -> { + MethodMetadata methodMetadata = invocation.getArgument(0); + String providedBeanName = invocation.getArgument(1); + return (providedBeanName != null) ? "test.fromBean." + providedBeanName : "test." + methodMetadata.getMethodName(); + }; + given(beanNameGenerator.deriveBeanName(any(), any())).willAnswer(answer).willAnswer(answer).willAnswer(answer); + beanFactory.registerBeanDefinition("config", new RootBeanDefinition(BeanNamesConfig.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.setBeanNameGenerator(beanNameGenerator); + pp.postProcessBeanFactory(beanFactory); + assertThat(beanFactory.getBeanDefinitionNames()) + .containsOnly("config", "test.beanWithoutName", "test.fromBean.specificName", "test.fromBean.specificNames"); + assertThat(beanFactory.getBean("test.beanWithoutName")).isEqualTo("beanWithoutName"); + assertThat(beanFactory.getBean("test.fromBean.specificName")).isEqualTo("beanWithName"); + assertThat(beanFactory.getBean("test.fromBean.specificNames")).isEqualTo("beanWithMultipleNames"); + assertThat(beanFactory.getBean("specificNames2")).isEqualTo("beanWithMultipleNames"); + assertThat(beanFactory.getBean("specificNames3")).isEqualTo("beanWithMultipleNames"); + ArgumentCaptor methodMetadataCaptor = ArgumentCaptor.forClass(MethodMetadata.class); + ArgumentCaptor beanNameCaptor = ArgumentCaptor.forClass(String.class); + verify(beanNameGenerator, times(3)).deriveBeanName(methodMetadataCaptor.capture(), beanNameCaptor.capture()); + List beansMethodMetadata = methodMetadataCaptor.getAllValues(); + assertThat(beansMethodMetadata).map(MethodMetadata::getMethodName) + .containsExactly("beanWithoutName", "beanWithName", "beanWithMultipleNames"); + List beanNames = beanNameCaptor.getAllValues(); + assertThat(beanNames).containsExactly(null, "specificName", "specificNames"); + } + @Test void genericsBasedInjection() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); @@ -1384,6 +1458,26 @@ class ConfigurationClassPostProcessorTests { } } + @Configuration(proxyBeanMethods = false) + public static class BeanNamesConfig { + + @Bean + public String beanWithoutName() { + return "beanWithoutName"; + } + + @Bean(name = "specificName") + public String beanWithName() { + return "beanWithName"; + } + + @Bean(name = { "specificNames", "specificNames2", "specificNames3" }) + public String beanWithMultipleNames() { + return "beanWithMultipleNames"; + } + + } + public interface RepositoryInterface { @Override