diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
index d6603e3fad6c..04b13179fb29 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
@@ -44,19 +44,21 @@
/**
* A {@link BeanFactoryPostProcessor} implementation that processes identified
- * use of {@link BeanOverride} and adapts the {@link BeanFactory} accordingly.
+ * use of {@link BeanOverride @BeanOverride} and adapts the {@link BeanFactory}
+ * accordingly.
*
*
For each override, the bean factory is prepared according to the chosen
- * {@link BeanOverrideStrategy overriding strategy}. The override value is created,
+ * {@linkplain BeanOverrideStrategy override strategy}. The override value is created,
* if necessary, and the necessary infrastructure is updated to allow the value
* to be injected in the corresponding {@linkplain OverrideMetadata#getField() field}
* of the test class.
*
- *
This processor does not work against a particular test class, it only prepares
- * the bean factory for the identified, unique set of bean overrides.
+ *
This processor does not work against a particular test class, but rather
+ * only prepares the bean factory for the identified, unique set of bean overrides.
*
* @author Simon Baslé
* @author Stephane Nicoll
+ * @author Sam Brannen
* @since 6.2
*/
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
@@ -69,11 +71,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
/**
- * Create a new {@code BeanOverrideBeanFactoryPostProcessor} instance with
- * the set of {@link OverrideMetadata} to process, using the given
+ * Create a new {@code BeanOverrideBeanFactoryPostProcessor} with the supplied
+ * set of {@link OverrideMetadata} to process, using the given
* {@link BeanOverrideRegistrar}.
* @param metadata the {@link OverrideMetadata} instances to process
- * @param overrideRegistrar the {@link BeanOverrideRegistrar} used to track
+ * @param overrideRegistrar the {@code BeanOverrideRegistrar} used to track
* metadata
*/
public BeanOverrideBeanFactoryPostProcessor(Set metadata,
@@ -84,21 +86,18 @@ public BeanOverrideBeanFactoryPostProcessor(Set metadata,
}
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE - 10;
+ }
+
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof BeanDefinitionRegistry registry)) {
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
"that doesn't implement BeanDefinitionRegistry: " + beanFactory.getClass());
}
- postProcessWithRegistry(beanFactory, registry);
- }
- @Override
- public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE - 10;
- }
-
- private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
for (OverrideMetadata metadata : this.metadata) {
registerBeanOverride(beanFactory, registry, metadata);
}
@@ -108,15 +107,13 @@ private void registerBeanOverride(ConfigurableListableBeanFactory beanFactory, B
OverrideMetadata overrideMetadata) {
switch (overrideMetadata.getStrategy()) {
- case REPLACE_DEFINITION ->
- registerReplaceDefinition(beanFactory, registry, overrideMetadata, true);
- case REPLACE_OR_CREATE_DEFINITION ->
- registerReplaceDefinition(beanFactory, registry, overrideMetadata, false);
- case WRAP_BEAN -> registerWrapBean(beanFactory, overrideMetadata);
+ case REPLACE_DEFINITION -> replaceDefinition(beanFactory, registry, overrideMetadata, true);
+ case REPLACE_OR_CREATE_DEFINITION -> replaceDefinition(beanFactory, registry, overrideMetadata, false);
+ case WRAP_BEAN -> wrapBean(beanFactory, overrideMetadata);
}
}
- private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
+ private void replaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
// The following is a "dummy" bean definition which should not be used to
@@ -169,30 +166,6 @@ else if (enforceExistingDefinition) {
}
}
- private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
- OverrideMetadata overrideMetadata, RootBeanDefinition beanDefinition, boolean enforceExistingDefinition) {
- Set candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
- int candidateCount = candidateNames.size();
- if (candidateCount == 1) {
- return candidateNames.iterator().next();
- }
- else if (candidateCount == 0) {
- if (enforceExistingDefinition) {
- Field field = overrideMetadata.getField();
- throw new IllegalStateException(
- "Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')"
- .formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
- }
- return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
- }
- Field field = overrideMetadata.getField();
- throw new IllegalStateException(String.format(
- "Unable to select a bean definition to override: found %s bean definitions of type %s " +
- "(as required by annotated field '%s.%s'): %s",
- candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(),
- field.getName(), candidateNames));
- }
-
/**
* Check that the expected bean name is registered and matches the type to override.
* If so, put the override metadata in the early tracking map.
@@ -200,65 +173,109 @@ else if (candidateCount == 0) {
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
* phase.
*/
- private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata) {
- String beanName = metadata.getBeanName();
+ private void wrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata) {
+ String beanName = overrideMetadata.getBeanName();
if (beanName == null) {
- Set candidateNames = getExistingBeanNamesByType(beanFactory, metadata, true);
+ Set candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
int candidateCount = candidateNames.size();
if (candidateCount != 1) {
- Field field = metadata.getField();
+ Field field = overrideMetadata.getField();
throw new IllegalStateException("Unable to select a bean to override by wrapping: found " +
- candidateCount + " bean instances of type " + metadata.getBeanType() +
+ candidateCount + " bean instances of type " + overrideMetadata.getBeanType() +
" (as required by annotated field '" + field.getDeclaringClass().getSimpleName() +
"." + field.getName() + "')" + (candidateCount > 0 ? ": " + candidateNames : ""));
}
beanName = BeanFactoryUtils.transformedBeanName(candidateNames.iterator().next());
}
else {
- Set candidates = getExistingBeanNamesByType(beanFactory, metadata, false);
+ Set candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, false);
if (!candidates.contains(beanName)) {
- throw new IllegalStateException("Unable to override bean '" + beanName + "' by wrapping: there is no " +
- "existing bean instance with that name of type " + metadata.getBeanType());
+ throw new IllegalStateException("""
+ Unable to override bean by wrapping: there is no existing bean definition \
+ with name [%s] and type [%s]."""
+ .formatted(beanName, overrideMetadata.getBeanType()));
+ }
+ }
+ this.overrideRegistrar.markWrapEarly(overrideMetadata, beanName);
+ this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanName);
+ }
+
+ private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
+ OverrideMetadata overrideMetadata, RootBeanDefinition beanDefinition, boolean enforceExistingDefinition) {
+
+ Set candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
+ int candidateCount = candidateNames.size();
+ if (candidateCount == 1) {
+ return candidateNames.iterator().next();
+ }
+ else if (candidateCount == 0) {
+ if (enforceExistingDefinition) {
+ Field field = overrideMetadata.getField();
+ throw new IllegalStateException(
+ "Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')"
+ .formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
}
+ return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
}
- this.overrideRegistrar.markWrapEarly(metadata, beanName);
- this.overrideRegistrar.registerNameForMetadata(metadata, beanName);
+
+ Field field = overrideMetadata.getField();
+ throw new IllegalStateException("""
+ Unable to select a bean definition to override: found %s bean definitions of type %s \
+ (as required by annotated field '%s.%s'): %s"""
+ .formatted(candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(),
+ field.getName(), candidateNames));
}
private Set getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata,
boolean checkAutowiredCandidate) {
ResolvableType resolvableType = metadata.getBeanType();
- Set beans = new LinkedHashSet<>(
- Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
Class> type = resolvableType.resolve(Object.class);
+
+ // Start with matching bean names for type, excluding FactoryBeans.
+ Set beanNames = new LinkedHashSet<>(
+ Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
+
+ // Add matching FactoryBeans as well.
for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class, true, false)) {
beanName = BeanFactoryUtils.transformedBeanName(beanName);
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
Object attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
if (resolvableType.equals(attribute) || type.equals(attribute)) {
- beans.add(beanName);
+ beanNames.add(beanName);
}
}
+
+ // Filter out non-matching autowire candidates.
if (checkAutowiredCandidate) {
DependencyDescriptor descriptor = new DependencyDescriptor(metadata.getField(), true);
- beans.removeIf(beanName -> ScopedProxyUtils.isScopedTarget(beanName) ||
- !beanFactory.isAutowireCandidate(beanName, descriptor));
+ beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
}
- else {
- beans.removeIf(ScopedProxyUtils::isScopedTarget);
- }
- // In case of multiple matches, last resort fallback on the field's name
- if (beans.size() > 1) {
+ // Filter out scoped proxy targets.
+ beanNames.removeIf(ScopedProxyUtils::isScopedTarget);
+
+ // In case of multiple matches, fall back on the field's name as a last resort.
+ if (beanNames.size() > 1) {
String fieldName = metadata.getField().getName();
- if (beans.contains(fieldName)) {
+ if (beanNames.contains(fieldName)) {
return Set.of(fieldName);
}
}
- return beans;
+ return beanNames;
}
-
+ /**
+ * Create a pseudo-{@link BeanDefinition} for the supplied {@link OverrideMetadata},
+ * whose {@linkplain RootBeanDefinition#getTargetType() target type} and
+ * {@linkplain RootBeanDefinition#getQualifiedElement() qualified element} are
+ * the {@linkplain OverrideMetadata#getBeanType() bean type} and
+ * the {@linkplain OverrideMetadata#getField() field} of the {@code OverrideMetadata},
+ * respectively.
+ * The returned bean definition should not be used to create
+ * a bean instance but rather only for the purpose of having suitable bean
+ * definition metadata available in the {@link BeanFactory} — for example,
+ * for autowiring candidate resolution.
+ */
private static RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) {
RootBeanDefinition definition = new RootBeanDefinition(metadata.getBeanType().resolve());
definition.setTargetType(metadata.getBeanType());
@@ -286,12 +303,10 @@ static class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPo
private final BeanOverrideRegistrar overrideRegistrar;
-
WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) {
this.overrideRegistrar = registrar;
}
-
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanTests.java
index 75b31761db88..86cdb8ca4747 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanTests.java
@@ -40,9 +40,9 @@ void contextCustomizerCannotBeCreatedWithNoSuchBeanName() {
assertThatIllegalStateException()
.isThrownBy(context::refresh)
.withMessage("""
- Unable to override bean 'beanToSpy' by wrapping: \
- there is no existing bean instance with that name of type %s""".formatted(
- String.class.getName()));
+ Unable to override bean by wrapping: \
+ there is no existing bean definition with name [beanToSpy] and type [%s].""",
+ String.class.getName());
}
@Test