mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-06 23:19:03 +00:00
Always invoke ConfigurationBeanNameGenerator
Previously, if a ConfigurationBeanNameGenerator is used to parse configuration classes, it is not invoked for `Bean` methods that provide a specific bean name. This doesn't give a chance to an implementation to tune such a bean, if required. This commit updates the signature of ConfigurationBeanNameGenerator to provide the identified bean name on the `@Bean` declaration,if any, and to always invoke it. Closes gh-35505
This commit is contained in:
+199
@@ -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() {}
|
||||
|
||||
}
|
||||
}
|
||||
+94
@@ -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<MethodMetadata> methodMetadataCaptor = ArgumentCaptor.forClass(MethodMetadata.class);
|
||||
ArgumentCaptor<String> beanNameCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(beanNameGenerator, times(3)).deriveBeanName(methodMetadataCaptor.capture(), beanNameCaptor.capture());
|
||||
List<MethodMetadata> beansMethodMetadata = methodMetadataCaptor.getAllValues();
|
||||
assertThat(beansMethodMetadata).map(MethodMetadata::getMethodName)
|
||||
.containsExactly("beanWithoutName", "beanWithName", "beanWithMultipleNames");
|
||||
List<String> 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<T> {
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user