Relax singleton enforcement for Bean Overrides in the TestContext framework

In gh-33602, we introduced strict singleton enforcement for bean
overrides -- for example, for @⁠MockitoBean, @⁠TestBean, etc. However,
the use of BeanFactory#isSingleton(beanName) can result in a
BeanCreationException for certain beans, such as a Spring Data JPA
FactoryBean for a JpaRepository.

In light of that, this commit relaxes the singleton enforcement in
BeanOverrideBeanFactoryPostProcessor by only checking the result of
BeanDefinition#isSingleton() for existing bean definitions.

This commit also updates the Javadoc and reference documentation to
reflect the status quo.

See gh-33602
Closes gh-33800
This commit is contained in:
Sam Brannen
2024-10-28 11:42:56 +01:00
parent 52e813d0ad
commit 81d89f478a
8 changed files with 69 additions and 27 deletions

View File

@@ -197,8 +197,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// Now we have an instance (the override) that we can manually register as a singleton.
//
// However, we need to remove any existing singleton instance -- for example, a
// manually registered singleton or a singleton that was registered as a side effect
// of the isSingleton() check in validateBeanDefinition().
// manually registered singleton.
//
// As a bonus, by manually registering a singleton during "AOT processing", we allow
// GenericApplicationContext's preDetermineBeanType() method to transparently register
@@ -334,10 +333,18 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
/**
* Validate that the {@link BeanDefinition} for the supplied bean name is suitable
* for being replaced by a bean override.
* <p>If there is no registered {@code BeanDefinition} for the supplied bean name,
* no validation is performed.
*/
private static void validateBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
Assert.state(beanFactory.isSingleton(beanName),
() -> "Unable to override bean '" + beanName + "': only singleton beans can be overridden.");
// Due to https://github.com/spring-projects/spring-framework/issues/33800, we do NOT invoke
// beanFactory.isSingleton(beanName), since doing so can result in a BeanCreationException for
// certain beans -- for example, a Spring Data FactoryBean for a JpaRepository.
if (beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
Assert.state(beanDefinition.isSingleton(),
() -> "Unable to override bean '" + beanName + "': only singleton beans can be overridden.");
}
}
private static void destroySingleton(ConfigurableListableBeanFactory beanFactory, String beanName) {

View File

@@ -98,7 +98,10 @@ 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.
* Any attempt to override a non-singleton bean will result in an exception. When
* overriding a bean created by a {@link org.springframework.beans.factory.FactoryBean
* FactoryBean}, the {@code FactoryBean} will be replaced with a singleton bean
* corresponding to the value returned from the {@code @TestBean} factory method.
*
* @author Simon Baslé
* @author Stephane Nicoll

View File

@@ -52,8 +52,11 @@ 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 mock a non-singleton bean will result in an exception.
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be mocked.
* Any attempt to mock a non-singleton bean will result in an exception. When
* mocking a bean created by a {@link org.springframework.beans.factory.FactoryBean
* FactoryBean}, the {@code FactoryBean} will be replaced with a singleton mock
* of the type of object created by the {@code FactoryBean}.
*
* @author Simon Baslé
* @author Sam Brannen

View File

@@ -43,8 +43,11 @@ import org.springframework.test.context.bean.override.BeanOverride;
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
* registered directly} as resolvable dependencies.
*
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be spied.
* Any attempt to create a spy for a non-singleton bean will result in an exception.
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be spied. Any attempt
* to create a spy for a non-singleton bean will result in an exception. When
* creating a spy for a {@link org.springframework.beans.factory.FactoryBean FactoryBean},
* a spy will be created for the object created by the {@code FactoryBean}, not
* for the {@code FactoryBean} itself.
*
* @author Simon Baslé
* @author Sam Brannen