mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Reject non-singletons in Test Bean Override support
Prior to this commit, a non-singleton FactoryBean was silently replaced by a singleton bean. In addition, bean definitions for prototype-scoped and custom-scoped beans were replaced by singleton bean definitions that were incapable of creating the desired bean instance. For example, if the bean type of the original bean definition was a concrete class, an attempt was made to invoke the default constructor which either succeeded with undesirable results or failed with an exception if the bean type did not have a default constructor. If the bean type of the original bean definition was an interface or a FactoryBean that claimed to create a bean of a certain interface type, an attempt was made to instantiate the interface which always failed with a BeanCreationException. To address the aforementioned issues, this commit reworks the logic in BeanOverrideBeanFactoryPostProcessor so that an exception is thrown whenever an attempt is made to override a non-singleton bean. Closes gh-33602
This commit is contained in:
+26
-17
@@ -40,6 +40,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -116,14 +117,15 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
private void replaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
|
||||
OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
|
||||
|
||||
// The following is a "dummy" bean definition which should not be used to
|
||||
// The following is a "pseudo" bean definition which MUST NOT be used to
|
||||
// create an actual bean instance.
|
||||
RootBeanDefinition beanDefinition = createBeanDefinition(overrideMetadata);
|
||||
RootBeanDefinition pseudoBeanDefinition = createPseudoBeanDefinition(overrideMetadata);
|
||||
String beanName = overrideMetadata.getBeanName();
|
||||
String beanNameIncludingFactory;
|
||||
BeanDefinition existingBeanDefinition = null;
|
||||
if (beanName == null) {
|
||||
beanNameIncludingFactory = getBeanNameForType(beanFactory, registry, overrideMetadata, beanDefinition, enforceExistingDefinition);
|
||||
beanNameIncludingFactory = getBeanNameForType(
|
||||
beanFactory, registry, overrideMetadata, pseudoBeanDefinition, enforceExistingDefinition);
|
||||
beanName = BeanFactoryUtils.transformedBeanName(beanNameIncludingFactory);
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
@@ -145,25 +147,24 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
|
||||
// Process existing bean definition.
|
||||
if (existingBeanDefinition != null) {
|
||||
copyBeanDefinitionProperties(existingBeanDefinition, beanDefinition);
|
||||
validateBeanDefinition(beanFactory, beanName);
|
||||
copyBeanDefinitionProperties(existingBeanDefinition, pseudoBeanDefinition);
|
||||
registry.removeBeanDefinition(beanName);
|
||||
}
|
||||
|
||||
// At this point, we either removed an existing bean definition above, or
|
||||
// there was no bean definition to begin with. So, we register the dummy bean
|
||||
// there was no bean definition to begin with. So, we register the pseudo bean
|
||||
// definition to ensure that a bean definition exists for the given bean name.
|
||||
registry.registerBeanDefinition(beanName, beanDefinition);
|
||||
registry.registerBeanDefinition(beanName, pseudoBeanDefinition);
|
||||
|
||||
Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null);
|
||||
overrideMetadata.track(override, beanFactory);
|
||||
this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanNameIncludingFactory);
|
||||
|
||||
if (beanFactory.isSingleton(beanNameIncludingFactory)) {
|
||||
// Now we have an instance (the override) that we can register. At this
|
||||
// stage we don't expect a singleton instance to be present, and this call
|
||||
// will throw an exception if there is such an instance already.
|
||||
beanFactory.registerSingleton(beanName, override);
|
||||
}
|
||||
// Now we have an instance (the override) that we can register. At this stage, we don't
|
||||
// expect a singleton instance to be present. If for some reason a singleton instance
|
||||
// already exists, the following will throw an exception.
|
||||
beanFactory.registerSingleton(beanName, override);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,6 +197,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
.formatted(beanName, overrideMetadata.getBeanType()));
|
||||
}
|
||||
}
|
||||
validateBeanDefinition(beanFactory, beanName);
|
||||
this.overrideRegistrar.markWrapEarly(overrideMetadata, beanName);
|
||||
this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanName);
|
||||
}
|
||||
@@ -276,23 +278,30 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
* definition metadata available in the {@link BeanFactory} — for example,
|
||||
* for autowiring candidate resolution.
|
||||
*/
|
||||
private static RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) {
|
||||
private static RootBeanDefinition createPseudoBeanDefinition(OverrideMetadata metadata) {
|
||||
RootBeanDefinition definition = new RootBeanDefinition(metadata.getBeanType().resolve());
|
||||
definition.setTargetType(metadata.getBeanType());
|
||||
definition.setQualifiedElement(metadata.getField());
|
||||
return definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the {@link BeanDefinition} for the supplied bean name is suitable
|
||||
* for being replaced by a bean override.
|
||||
*/
|
||||
private static void validateBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
|
||||
Assert.state(beanFactory.isSingleton(beanName),
|
||||
() -> "Unable to override bean '" + beanName + "': only singleton beans can be overridden.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the following properties of the source {@link BeanDefinition} to the
|
||||
* target: the {@linkplain BeanDefinition#isPrimary() primary flag}, the
|
||||
* {@linkplain BeanDefinition#isFallback() fallback flag}, and the
|
||||
* {@linkplain BeanDefinition#getScope() scope}.
|
||||
* target: the {@linkplain BeanDefinition#isPrimary() primary flag} and the
|
||||
* {@linkplain BeanDefinition#isFallback() fallback flag}.
|
||||
*/
|
||||
private static void copyBeanDefinitionProperties(BeanDefinition source, RootBeanDefinition target) {
|
||||
target.setPrimary(source.isPrimary());
|
||||
target.setFallback(source.isFallback());
|
||||
target.setScope(source.getScope());
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
@@ -49,6 +49,9 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
|
||||
* instance creation} — for example, based on further processing of the
|
||||
* annotation or the annotated field.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
||||
* Any attempt to override a non-singleton bean will result in an exception.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
|
||||
+3
@@ -93,6 +93,9 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
* }
|
||||
* }</code></pre>
|
||||
*
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
||||
* Any attempt to override a non-singleton bean will result in an exception.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
|
||||
+3
@@ -47,6 +47,9 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
* registered directly}) will not be found, and a mocked bean will be added to
|
||||
* the context alongside the existing dependency.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
||||
* Any attempt to override a non-singleton bean will result in an exception.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean
|
||||
|
||||
+3
@@ -42,6 +42,9 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
|
||||
* registered directly}) will not be found.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
||||
* Any attempt to override a non-singleton bean will result in an exception.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean
|
||||
|
||||
Reference in New Issue
Block a user