diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java index a1f47e53603..f5368ea9e48 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -97,6 +97,19 @@ public interface BeanDefinitionRegistry extends AliasRegistry { */ int getBeanDefinitionCount(); + /** + * Determine whether the bean definition for the given name is overridable, + * i.e. whether {@link #registerBeanDefinition} would successfully return + * against an existing definition of the same name. + *
The default implementation returns {@code true}. + * @param beanName the name to check + * @return whether the definition for the given bean name is overridable + * @since 6.1 + */ + default boolean isBeanDefinitionOverridable(String beanName) { + return true; + } + /** * Determine whether the given bean name is already in use within this registry, * i.e. whether there is a local bean or alias registered under this name. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index c7e52d1031f..ffc402d8bc3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1011,7 +1011,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { - if (!isAllowBeanDefinitionOverriding()) { + if (!isBeanDefinitionOverridable(beanName)) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { @@ -1040,8 +1040,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } else { if (isAlias(beanName)) { - if (!isAllowBeanDefinitionOverriding()) { - String aliasedName = canonicalName(beanName); + String aliasedName = canonicalName(beanName); + if (!isBeanDefinitionOverridable(aliasedName)) { if (containsBeanDefinition(aliasedName)) { // alias for existing bean definition throw new BeanDefinitionOverrideException( beanName, beanDefinition, getBeanDefinition(aliasedName)); @@ -1150,8 +1150,19 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } + /** + * This implementation returns {@code true} if bean definition overriding + * is generally allowed. + * @see #setAllowBeanDefinitionOverriding + */ + @Override + public boolean isBeanDefinitionOverridable(String beanName) { + return isAllowBeanDefinitionOverriding(); + } + /** * Only allows alias overriding if bean definition overriding is allowed. + * @see #setAllowBeanDefinitionOverriding */ @Override protected boolean allowAliasOverriding() { @@ -1164,7 +1175,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override protected void checkForAliasCircle(String name, String alias) { super.checkForAliasCircle(name, alias); - if (!isAllowBeanDefinitionOverriding() && containsBeanDefinition(alias)) { + if (!isBeanDefinitionOverridable(alias) && containsBeanDefinition(alias)) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Alias would override bean definition '" + alias + "'"); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 0b2f1d52a0b..bf299013bd5 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -329,21 +329,31 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * @return {@code true} if the bean can be registered as-is; * {@code false} if it should be skipped because there is an * existing, compatible bean definition for the specified name - * @throws ConflictingBeanDefinitionException if an existing, incompatible - * bean definition has been found for the specified name + * @throws IllegalStateException if an existing, incompatible bean definition + * has been found for the specified name */ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { if (!this.registry.containsBeanDefinition(beanName)) { return true; } + BeanDefinition existingDef = this.registry.getBeanDefinition(beanName); BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition(); if (originatingDef != null) { existingDef = originatingDef; } + + // Explicitly registered overriding bean? + if (!(existingDef instanceof ScannedGenericBeanDefinition) && + this.registry.isBeanDefinitionOverridable(beanName)) { + return false; + } + + // Scanned same file or equivalent class twice? if (isCompatible(beanDefinition, existingDef)) { return false; } + throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName + "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " + "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]"); @@ -354,16 +364,15 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * the given existing bean definition. *
The default implementation considers them as compatible when the existing * bean definition comes from the same source or from a non-scanning source. - * @param newDefinition the new bean definition, originated from scanning - * @param existingDefinition the existing bean definition, potentially an + * @param newDef the new bean definition, originated from scanning + * @param existingDef the existing bean definition, potentially an * explicitly defined one or a previously generated one from scanning * @return whether the definitions are considered as compatible, with the * new definition to be skipped in favor of the existing definition */ - protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) { - return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean - (newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) || // scanned same file twice - newDefinition.equals(existingDefinition)); // scanned equivalent class twice + protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) { + return ((newDef.getSource() != null && newDef.getSource().equals(existingDef.getSource())) || + newDef.equals(existingDef)); } 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 ae75eb1a017..fa47ef6d806 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 @@ -314,7 +314,7 @@ 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.isAllowBeanDefinitionOverriding()) { + 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); } diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index 178287ac485..7faac66dcf4 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -360,6 +360,11 @@ public class GenericApplicationContext extends AbstractApplicationContext implem return this.beanFactory.getBeanDefinition(beanName); } + @Override + public boolean isBeanDefinitionOverridable(String beanName) { + return this.beanFactory.isBeanDefinitionOverridable(beanName); + } + @Override public boolean isBeanNameInUse(String beanName) { return this.beanFactory.isBeanNameInUse(beanName); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java index be96c741f6c..c0b9d2d907d 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java @@ -197,16 +197,31 @@ public class ClassPathBeanDefinitionScannerTests { context.registerBeanDefinition("stubFooDao", new RootBeanDefinition(TestBean.class)); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setIncludeAnnotationConfig(false); + // should not fail! scanner.scan(BASE_PACKAGE); } + @Test + public void testSimpleScanWithDefaultFiltersAndOverridingBeanNotAllowed() { + GenericApplicationContext context = new GenericApplicationContext(); + context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false); + context.registerBeanDefinition("stubFooDao", new RootBeanDefinition(TestBean.class)); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); + scanner.setIncludeAnnotationConfig(false); + + assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE)) + .withMessageContaining("stubFooDao") + .withMessageContaining(StubFooDao.class.getName()); + } + @Test public void testSimpleScanWithDefaultFiltersAndDefaultBeanNameClash() { GenericApplicationContext context = new GenericApplicationContext(); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context); scanner.setIncludeAnnotationConfig(false); scanner.scan("org.springframework.context.annotation3"); + assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE)) .withMessageContaining("stubFooDao") .withMessageContaining(StubFooDao.class.getName());