Detect illegal bean definition override during classpath scanning
Closes gh-25952
This commit is contained in:
parent
c596ff5c38
commit
eaf54b54c3
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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();
|
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.
|
||||||
|
* <p>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,
|
* 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.
|
* i.e. whether there is a local bean or alias registered under this name.
|
||||||
|
|
|
||||||
|
|
@ -1011,7 +1011,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||||
|
|
||||||
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
|
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
|
||||||
if (existingDefinition != null) {
|
if (existingDefinition != null) {
|
||||||
if (!isAllowBeanDefinitionOverriding()) {
|
if (!isBeanDefinitionOverridable(beanName)) {
|
||||||
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
|
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
|
||||||
}
|
}
|
||||||
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
|
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
|
||||||
|
|
@ -1040,8 +1040,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (isAlias(beanName)) {
|
if (isAlias(beanName)) {
|
||||||
if (!isAllowBeanDefinitionOverriding()) {
|
String aliasedName = canonicalName(beanName);
|
||||||
String aliasedName = canonicalName(beanName);
|
if (!isBeanDefinitionOverridable(aliasedName)) {
|
||||||
if (containsBeanDefinition(aliasedName)) { // alias for existing bean definition
|
if (containsBeanDefinition(aliasedName)) { // alias for existing bean definition
|
||||||
throw new BeanDefinitionOverrideException(
|
throw new BeanDefinitionOverrideException(
|
||||||
beanName, beanDefinition, getBeanDefinition(aliasedName));
|
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.
|
* Only allows alias overriding if bean definition overriding is allowed.
|
||||||
|
* @see #setAllowBeanDefinitionOverriding
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean allowAliasOverriding() {
|
protected boolean allowAliasOverriding() {
|
||||||
|
|
@ -1164,7 +1175,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||||
@Override
|
@Override
|
||||||
protected void checkForAliasCircle(String name, String alias) {
|
protected void checkForAliasCircle(String name, String alias) {
|
||||||
super.checkForAliasCircle(name, alias);
|
super.checkForAliasCircle(name, alias);
|
||||||
if (!isAllowBeanDefinitionOverriding() && containsBeanDefinition(alias)) {
|
if (!isBeanDefinitionOverridable(alias) && containsBeanDefinition(alias)) {
|
||||||
throw new IllegalStateException("Cannot register alias '" + alias +
|
throw new IllegalStateException("Cannot register alias '" + alias +
|
||||||
"' for name '" + name + "': Alias would override bean definition '" + alias + "'");
|
"' for name '" + name + "': Alias would override bean definition '" + alias + "'");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -329,21 +329,31 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
|
||||||
* @return {@code true} if the bean can be registered as-is;
|
* @return {@code true} if the bean can be registered as-is;
|
||||||
* {@code false} if it should be skipped because there is an
|
* {@code false} if it should be skipped because there is an
|
||||||
* existing, compatible bean definition for the specified name
|
* existing, compatible bean definition for the specified name
|
||||||
* @throws ConflictingBeanDefinitionException if an existing, incompatible
|
* @throws IllegalStateException if an existing, incompatible bean definition
|
||||||
* bean definition has been found for the specified name
|
* has been found for the specified name
|
||||||
*/
|
*/
|
||||||
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
|
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
|
||||||
if (!this.registry.containsBeanDefinition(beanName)) {
|
if (!this.registry.containsBeanDefinition(beanName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
|
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
|
||||||
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
|
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
|
||||||
if (originatingDef != null) {
|
if (originatingDef != null) {
|
||||||
existingDef = originatingDef;
|
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)) {
|
if (isCompatible(beanDefinition, existingDef)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
|
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
|
||||||
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
|
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
|
||||||
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
|
"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 given existing bean definition.
|
||||||
* <p>The default implementation considers them as compatible when the existing
|
* <p>The default implementation considers them as compatible when the existing
|
||||||
* bean definition comes from the same source or from a non-scanning source.
|
* bean definition comes from the same source or from a non-scanning source.
|
||||||
* @param newDefinition the new bean definition, originated from scanning
|
* @param newDef the new bean definition, originated from scanning
|
||||||
* @param existingDefinition the existing bean definition, potentially an
|
* @param existingDef the existing bean definition, potentially an
|
||||||
* explicitly defined one or a previously generated one from scanning
|
* explicitly defined one or a previously generated one from scanning
|
||||||
* @return whether the definitions are considered as compatible, with the
|
* @return whether the definitions are considered as compatible, with the
|
||||||
* new definition to be skipped in favor of the existing definition
|
* new definition to be skipped in favor of the existing definition
|
||||||
*/
|
*/
|
||||||
protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
|
protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) {
|
||||||
return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
|
return ((newDef.getSource() != null && newDef.getSource().equals(existingDef.getSource())) ||
|
||||||
(newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) || // scanned same file twice
|
newDef.equals(existingDef));
|
||||||
newDefinition.equals(existingDefinition)); // scanned equivalent class twice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||||
|
|
||||||
// At this point, it's a top-level override (probably XML), just having been parsed
|
// At this point, it's a top-level override (probably XML), just having been parsed
|
||||||
// before configuration class processing kicks in...
|
// 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(),
|
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
|
||||||
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
|
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,11 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
|
||||||
return this.beanFactory.getBeanDefinition(beanName);
|
return this.beanFactory.getBeanDefinition(beanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBeanDefinitionOverridable(String beanName) {
|
||||||
|
return this.beanFactory.isBeanDefinitionOverridable(beanName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBeanNameInUse(String beanName) {
|
public boolean isBeanNameInUse(String beanName) {
|
||||||
return this.beanFactory.isBeanNameInUse(beanName);
|
return this.beanFactory.isBeanNameInUse(beanName);
|
||||||
|
|
|
||||||
|
|
@ -197,16 +197,31 @@ public class ClassPathBeanDefinitionScannerTests {
|
||||||
context.registerBeanDefinition("stubFooDao", new RootBeanDefinition(TestBean.class));
|
context.registerBeanDefinition("stubFooDao", new RootBeanDefinition(TestBean.class));
|
||||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||||
scanner.setIncludeAnnotationConfig(false);
|
scanner.setIncludeAnnotationConfig(false);
|
||||||
|
|
||||||
// should not fail!
|
// should not fail!
|
||||||
scanner.scan(BASE_PACKAGE);
|
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
|
@Test
|
||||||
public void testSimpleScanWithDefaultFiltersAndDefaultBeanNameClash() {
|
public void testSimpleScanWithDefaultFiltersAndDefaultBeanNameClash() {
|
||||||
GenericApplicationContext context = new GenericApplicationContext();
|
GenericApplicationContext context = new GenericApplicationContext();
|
||||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||||
scanner.setIncludeAnnotationConfig(false);
|
scanner.setIncludeAnnotationConfig(false);
|
||||||
scanner.scan("org.springframework.context.annotation3");
|
scanner.scan("org.springframework.context.annotation3");
|
||||||
|
|
||||||
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
|
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
|
||||||
.withMessageContaining("stubFooDao")
|
.withMessageContaining("stubFooDao")
|
||||||
.withMessageContaining(StubFooDao.class.getName());
|
.withMessageContaining(StubFooDao.class.getName());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue