diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionOverrideException.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionOverrideException.java index f894298b15..a815db479f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionOverrideException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionOverrideException.java @@ -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. 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 bf0887ed9c..8385b9ef34 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 @@ -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' " + diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/OptionalValidatorFactoryBean.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/OptionalValidatorFactoryBean.java index a2991e5563..b1097f213c 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/OptionalValidatorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/OptionalValidatorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -17,6 +17,7 @@ package org.springframework.validation.beanvalidation; import jakarta.validation.ValidationException; +import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** @@ -39,7 +40,13 @@ public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean { super.afterPropertiesSet(); } catch (ValidationException ex) { - LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex); + Log logger = LogFactory.getLog(getClass()); + if (logger.isDebugEnabled()) { + logger.debug("Failed to set up a Bean Validation provider", ex); + } + else if (logger.isInfoEnabled()) { + logger.info("Failed to set up a Bean Validation provider: " + ex); + } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 033fe6d2ec..2e2ef91732 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -110,7 +110,7 @@ class ConfigurationClassProcessingTests { private void aliasesAreRespected(Class testClass, Supplier 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"); } }