Restore original override behavior when override allowed

Closes gh-33920
This commit is contained in:
Juergen Hoeller 2024-12-10 16:25:49 +01:00
parent 68d6cb9d35
commit 66da5d7ab9
3 changed files with 58 additions and 24 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
@ -54,6 +54,22 @@ public class BeanDefinitionOverrideException extends BeanDefinitionStoreExceptio
this.existingDefinition = existingDefinition;
}
/**
* Create a new BeanDefinitionOverrideException for the given new and existing definition.
* @param beanName the name of the bean
* @param beanDefinition the newly registered bean definition
* @param existingDefinition the existing bean definition for the same name
* @param msg the detail message to include
* @since 6.2.1
*/
public BeanDefinitionOverrideException(
String beanName, BeanDefinition beanDefinition, BeanDefinition existingDefinition, String msg) {
super(beanDefinition.getResourceDescription(), beanName, msg);
this.beanDefinition = beanDefinition;
this.existingDefinition = existingDefinition;
}
/**
* Return the description of the resource that the bean definition came from.

View File

@ -36,10 +36,10 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.parsing.SourceExtractor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
@ -297,13 +297,21 @@ class ConfigurationClassBeanDefinitionReader {
return false;
}
BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);
ConfigurationClass configClass = beanMethod.getConfigurationClass();
// If the bean method is an overloaded case on the same configuration class,
// preserve the existing bean definition and mark it as overloaded.
if (existingBeanDef instanceof ConfigurationClassBeanDefinition ccbd) {
if (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName()) &&
ccbd.getFactoryMethodMetadata().getMethodName().equals(beanMethod.getMetadata().getMethodName())) {
ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
if (ccbd.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
if (ccbd.getFactoryMethodMetadata().getMethodName().equals(beanMethod.getMetadata().getMethodName())) {
ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
}
else if (!this.registry.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName,
new ConfigurationClassBeanDefinition(configClass, beanMethod.getMetadata(), beanName),
existingBeanDef,
"@Bean method override with same bean name but different method name: " + existingBeanDef);
}
return true;
}
else {
@ -329,9 +337,11 @@ class ConfigurationClassBeanDefinitionReader {
// At this point, it's a top-level override (probably XML), just having been parsed
// before configuration class processing kicks in...
if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
if (!this.registry.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName,
new ConfigurationClassBeanDefinition(configClass, beanMethod.getMetadata(), beanName),
existingBeanDef,
"@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " +

View File

@ -110,7 +110,7 @@ class ConfigurationClassProcessingTests {
private void aliasesAreRespected(Class<?> testClass, Supplier<TestBean> testBeanSupplier, String beanName) {
TestBean testBean = testBeanSupplier.get();
BeanFactory factory = initBeanFactory(testClass);
BeanFactory factory = initBeanFactory(false, testClass);
assertThat(factory.getBean(beanName)).isSameAs(testBean);
Arrays.stream(factory.getAliases(beanName)).map(factory::getBean).forEach(alias -> assertThat(alias).isSameAs(testBean));
@ -141,30 +141,30 @@ class ConfigurationClassProcessingTests {
@Test
void finalBeanMethod() {
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
initBeanFactory(ConfigWithFinalBean.class));
initBeanFactory(false, ConfigWithFinalBean.class));
}
@Test
void finalBeanMethodWithoutProxy() {
initBeanFactory(ConfigWithFinalBeanWithoutProxy.class);
initBeanFactory(false, ConfigWithFinalBeanWithoutProxy.class);
}
@Test // gh-31007
void voidBeanMethod() {
assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() ->
initBeanFactory(ConfigWithVoidBean.class));
initBeanFactory(false, ConfigWithVoidBean.class));
}
@Test
void simplestPossibleConfig() {
BeanFactory factory = initBeanFactory(SimplestPossibleConfig.class);
BeanFactory factory = initBeanFactory(false, SimplestPossibleConfig.class);
String stringBean = factory.getBean("stringBean", String.class);
assertThat(stringBean).isEqualTo("foo");
}
@Test
void configWithObjectReturnType() {
BeanFactory factory = initBeanFactory(ConfigWithNonSpecificReturnTypes.class);
BeanFactory factory = initBeanFactory(false, ConfigWithNonSpecificReturnTypes.class);
assertThat(factory.getType("stringBean")).isEqualTo(Object.class);
assertThat(factory.isTypeMatch("stringBean", String.class)).isFalse();
String stringBean = factory.getBean("stringBean", String.class);
@ -173,7 +173,7 @@ class ConfigurationClassProcessingTests {
@Test
void configWithFactoryBeanReturnType() {
ListableBeanFactory factory = initBeanFactory(ConfigWithNonSpecificReturnTypes.class);
ListableBeanFactory factory = initBeanFactory(false, ConfigWithNonSpecificReturnTypes.class);
assertThat(factory.getType("factoryBean")).isEqualTo(List.class);
assertThat(factory.isTypeMatch("factoryBean", List.class)).isTrue();
assertThat(factory.getType("&factoryBean")).isEqualTo(FactoryBean.class);
@ -201,7 +201,7 @@ class ConfigurationClassProcessingTests {
@Test
void configurationWithPrototypeScopedBeans() {
BeanFactory factory = initBeanFactory(ConfigWithPrototypeBean.class);
BeanFactory factory = initBeanFactory(false, ConfigWithPrototypeBean.class);
TestBean foo = factory.getBean("foo", TestBean.class);
ITestBean bar = factory.getBean("bar", ITestBean.class);
@ -213,7 +213,7 @@ class ConfigurationClassProcessingTests {
@Test
void configurationWithNullReference() {
BeanFactory factory = initBeanFactory(ConfigWithNullReference.class);
BeanFactory factory = initBeanFactory(false, ConfigWithNullReference.class);
TestBean foo = factory.getBean("foo", TestBean.class);
assertThat(factory.getBean("bar")).isEqualTo(null);
@ -223,7 +223,15 @@ class ConfigurationClassProcessingTests {
@Test // gh-33330
void configurationWithMethodNameMismatch() {
assertThatExceptionOfType(BeanDefinitionOverrideException.class)
.isThrownBy(() -> initBeanFactory(ConfigWithMethodNameMismatch.class));
.isThrownBy(() -> initBeanFactory(false, ConfigWithMethodNameMismatch.class));
}
@Test // gh-33920
void configurationWithMethodNameMismatchAndOverridingAllowed() {
BeanFactory factory = initBeanFactory(true, ConfigWithMethodNameMismatch.class);
SpousyTestBean foo = factory.getBean("foo", SpousyTestBean.class);
assertThat(foo.getName()).isEqualTo("foo1");
}
@Test
@ -353,13 +361,13 @@ class ConfigurationClassProcessingTests {
* When complete, the factory is ready to service requests for any {@link Bean} methods
* declared by {@code configClasses}.
*/
private DefaultListableBeanFactory initBeanFactory(Class<?>... configClasses) {
private DefaultListableBeanFactory initBeanFactory(boolean allowOverriding, Class<?>... configClasses) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
for (Class<?> configClass : configClasses) {
String configBeanName = configClass.getName();
factory.registerBeanDefinition(configBeanName, new RootBeanDefinition(configClass));
}
factory.setAllowBeanDefinitionOverriding(false);
factory.setAllowBeanDefinitionOverriding(allowOverriding);
ConfigurationClassPostProcessor ccpp = new ConfigurationClassPostProcessor();
ccpp.postProcessBeanDefinitionRegistry(factory);
ccpp.postProcessBeanFactory(factory);
@ -537,12 +545,12 @@ class ConfigurationClassProcessingTests {
@Configuration
static class ConfigWithMethodNameMismatch {
@Bean(name = "foo") public TestBean foo() {
return new SpousyTestBean("foo");
@Bean(name = "foo") public TestBean foo1() {
return new SpousyTestBean("foo1");
}
@Bean(name = "foo") public TestBean fooX() {
return new SpousyTestBean("fooX");
@Bean(name = "foo") public TestBean foo2() {
return new SpousyTestBean("foo2");
}
}