Skip to content

Commit 7916f74

Browse files
committed
Review TCF cache support for bean override
This commit reviews how bean override support can influence the key of an application context cached by the TCF. OverrideMetadata and its subclasses now implement a proper equals/hashCode pair that is tested in various scenarios. Due to how the TCF operates, OverrideMetadata has to be computed in two locations: 1. In a ContextCustomizerFactory, using the metadata in the enclosing class if any. This determines whether a customizer is needed in the first place. The computed set of unique metadata identifies the customizer and participates in the application context cache's key. 2. In the TestExecutionListener so that it knows the override points it has to process. Parsing of the metadata based on a test class has been greatly simplified and moved to OverrideMetadata proper as we don't need several flavors. 1 and 2 are using the same algorithm with the former wrapping that in a Set to compute a proper key. BeanOverrideContextCustomizerEqualityTests provides a framework for testing edge cases as we only care about whether the created ContextCustomizer behaves correctly against the identity of another. Closes gh-32884
1 parent 02517e5 commit 7916f74

23 files changed

+1239
-331
lines changed

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java

+20-17
Original file line numberDiff line numberDiff line change
@@ -41,38 +41,41 @@
4141
import org.springframework.util.StringUtils;
4242

4343
/**
44-
* A {@link BeanFactoryPostProcessor} implementation that processes test classes
45-
* and adapts the {@link BeanFactory} for any {@link BeanOverride} it may define.
44+
* A {@link BeanFactoryPostProcessor} implementation that processes identified
45+
* use of {@link BeanOverride} and adapts the {@link BeanFactory} accordingly.
4646
*
47-
* <p>A set of classes from which to parse {@link OverrideMetadata} must be
48-
* provided to this processor. Each test class is expected to use any
49-
* annotation meta-annotated with {@link BeanOverride @BeanOverride} to mark
50-
* beans to override. The {@link BeanOverrideParsingUtils#hasBeanOverride(Class)}
51-
* method can be used to check if a class matches the above criteria.
47+
* <p>For each override, the bean factory is prepared according to the chosen
48+
* {@link BeanOverrideStrategy overriding strategy}. The override value is created,
49+
* if necessary, and the necessary infrastructure is updated to allow the value
50+
* to be injected in the corresponding {@linkplain OverrideMetadata#getField() field}
51+
* of the test class.
5252
*
53-
* <p>The provided classes are fully parsed at creation to build a metadata set.
54-
* This processor implements several {@link BeanOverrideStrategy overriding
55-
* strategies} and chooses the correct one according to each override metadata's
56-
* {@link OverrideMetadata#getStrategy()} method. Additionally, it provides
57-
* support for injecting the overridden bean instances into their corresponding
58-
* annotated {@link Field fields}.
53+
* <p>This processor does not work against a particular test class, it only prepares
54+
* the bean factory for the identified, unique, set of bean overrides.
5955
*
6056
* @author Simon Baslé
57+
* @author Stephane Nicoll
6158
* @since 6.2
6259
*/
6360
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
6461

62+
private final Set<OverrideMetadata> metadata;
63+
6564
private final BeanOverrideRegistrar overrideRegistrar;
6665

6766

6867
/**
6968
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} instance with
70-
* the given {@link BeanOverrideRegistrar}, which contains a set of parsed
71-
* {@link OverrideMetadata}.
69+
* the set of {@link OverrideMetadata} to process, using the given
70+
* {@link BeanOverrideRegistrar}.
71+
* @param metadata the {@link OverrideMetadata} instances to process
7272
* @param overrideRegistrar the {@link BeanOverrideRegistrar} used to track
7373
* metadata
7474
*/
75-
public BeanOverrideBeanFactoryPostProcessor(BeanOverrideRegistrar overrideRegistrar) {
75+
public BeanOverrideBeanFactoryPostProcessor(Set<OverrideMetadata> metadata,
76+
BeanOverrideRegistrar overrideRegistrar) {
77+
78+
this.metadata = metadata;
7679
this.overrideRegistrar = overrideRegistrar;
7780
}
7881

@@ -92,7 +95,7 @@ public int getOrder() {
9295
}
9396

9497
private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
95-
for (OverrideMetadata metadata : this.overrideRegistrar.getOverrideMetadata()) {
98+
for (OverrideMetadata metadata : this.metadata) {
9699
registerBeanOverride(beanFactory, registry, metadata);
97100
}
98101
}

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java

+19-13
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
4949
"org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor";
5050

5151

52-
private final Set<Class<?>> detectedClasses;
52+
private final Set<OverrideMetadata> metadata;
5353

54-
BeanOverrideContextCustomizer(Set<Class<?>> detectedClasses) {
55-
this.detectedClasses = detectedClasses;
54+
BeanOverrideContextCustomizer(Set<OverrideMetadata> metadata) {
55+
this.metadata = metadata;
5656
}
5757

5858
@Override
@@ -61,19 +61,25 @@ public void customizeContext(ConfigurableApplicationContext context, MergedConte
6161
throw new IllegalStateException("Cannot process bean overrides with an ApplicationContext " +
6262
"that doesn't implement BeanDefinitionRegistry: " + context.getClass());
6363
}
64-
registerInfrastructure(registry, this.detectedClasses);
64+
registerInfrastructure(registry);
6565
}
6666

67-
private void registerInfrastructure(BeanDefinitionRegistry registry, Set<Class<?>> detectedClasses) {
67+
Set<OverrideMetadata> getMetadata() {
68+
return this.metadata;
69+
}
70+
71+
private void registerInfrastructure(BeanDefinitionRegistry registry) {
6872
addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME,
69-
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, detectedClasses));
73+
constructorArgs -> {});
74+
7075
RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME);
71-
addInfrastructureBeanDefinition(
72-
registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
73-
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
74-
addInfrastructureBeanDefinition(
75-
registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
76+
addInfrastructureBeanDefinition(registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
7677
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
78+
addInfrastructureBeanDefinition(registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
79+
constructorArgs -> {
80+
constructorArgs.addIndexedArgumentValue(0, this.metadata);
81+
constructorArgs.addIndexedArgumentValue(1, registrarReference);
82+
});
7783
}
7884

7985
private void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
@@ -97,12 +103,12 @@ public boolean equals(Object other) {
97103
return false;
98104
}
99105
BeanOverrideContextCustomizer that = (BeanOverrideContextCustomizer) other;
100-
return this.detectedClasses.equals(that.detectedClasses);
106+
return this.metadata.equals(that.metadata);
101107
}
102108

103109
@Override
104110
public int hashCode() {
105-
return this.detectedClasses.hashCode();
111+
return this.metadata.hashCode();
106112
}
107113

108114
}

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java

+11-13
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616

1717
package org.springframework.test.context.bean.override;
1818

19-
import java.util.LinkedHashSet;
19+
import java.util.HashSet;
2020
import java.util.List;
2121
import java.util.Set;
2222

2323
import org.springframework.lang.Nullable;
2424
import org.springframework.test.context.ContextConfigurationAttributes;
25-
import org.springframework.test.context.ContextCustomizer;
2625
import org.springframework.test.context.ContextCustomizerFactory;
2726
import org.springframework.test.context.TestContextAnnotationUtils;
2827

@@ -31,31 +30,30 @@
3130
* Bean Overriding.
3231
*
3332
* @author Simon Baslé
33+
* @author Stephane Nicoll
3434
* @since 6.2
3535
* @see BeanOverride
3636
*/
3737
class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
3838

3939
@Override
4040
@Nullable
41-
public ContextCustomizer createContextCustomizer(Class<?> testClass,
41+
public BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass,
4242
List<ContextConfigurationAttributes> configAttributes) {
4343

44-
Set<Class<?>> detectedClasses = new LinkedHashSet<>();
45-
findClassesWithBeanOverride(testClass, detectedClasses);
46-
if (detectedClasses.isEmpty()) {
44+
Set<OverrideMetadata> metadata = new HashSet<>();
45+
findOverrideMetadata(testClass, metadata);
46+
if (metadata.isEmpty()) {
4747
return null;
4848
}
49-
50-
return new BeanOverrideContextCustomizer(detectedClasses);
49+
return new BeanOverrideContextCustomizer(metadata);
5150
}
5251

53-
private void findClassesWithBeanOverride(Class<?> testClass, Set<Class<?>> detectedClasses) {
54-
if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) {
55-
detectedClasses.add(testClass);
56-
}
52+
private void findOverrideMetadata(Class<?> testClass, Set<OverrideMetadata> metadata) {
53+
List<OverrideMetadata> overrideMetadata = OverrideMetadata.forTestClass(testClass);
54+
metadata.addAll(overrideMetadata);
5755
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
58-
findClassesWithBeanOverride(testClass.getEnclosingClass(), detectedClasses);
56+
findOverrideMetadata(testClass.getEnclosingClass(), metadata);
5957
}
6058
}
6159

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java

-101
This file was deleted.

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java

-19
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.lang.reflect.Field;
2020
import java.util.HashMap;
2121
import java.util.Map;
22-
import java.util.Set;
2322

2423
import org.springframework.beans.BeansException;
2524
import org.springframework.beans.factory.BeanCreationException;
@@ -45,21 +44,10 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
4544

4645
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
4746

48-
private final Set<OverrideMetadata> overrideMetadata;
49-
5047
@Nullable
5148
private ConfigurableBeanFactory beanFactory;
5249

5350

54-
/**
55-
* Create a new registrar and immediately parse the provided classes.
56-
* @param classesToParse the initial set of classes that have been
57-
* detected to contain bean overriding annotations
58-
*/
59-
BeanOverrideRegistrar(Set<Class<?>> classesToParse) {
60-
this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse);
61-
}
62-
6351
@Override
6452
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
6553
if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
@@ -69,13 +57,6 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
6957
this.beanFactory = cbf;
7058
}
7159

72-
/**
73-
* Get the detected {@link OverrideMetadata} instances.
74-
*/
75-
Set<OverrideMetadata> getOverrideMetadata() {
76-
return this.overrideMetadata;
77-
}
78-
7960
/**
8061
* Check {@link #markWrapEarly(OverrideMetadata, String) early override}
8162
* records and use the {@link OverrideMetadata} to create an override

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java

+6-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.test.context.bean.override;
1818

1919
import java.lang.reflect.Field;
20+
import java.util.List;
2021
import java.util.function.BiConsumer;
2122

2223
import org.springframework.test.context.TestContext;
@@ -74,12 +75,12 @@ protected void reinjectFieldsIfConfigured(TestContext testContext) throws Except
7475
if (Boolean.TRUE.equals(
7576
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
7677

77-
postProcessFields(testContext, (testMetadata, postProcessor) -> {
78+
postProcessFields(testContext, (testMetadata, registrar) -> {
7879
Object testInstance = testMetadata.testInstance;
7980
Field field = testMetadata.overrideMetadata.getField();
8081
ReflectionUtils.makeAccessible(field);
8182
ReflectionUtils.setField(field, testInstance, null);
82-
postProcessor.inject(testInstance, testMetadata.overrideMetadata);
83+
registrar.inject(testInstance, testMetadata.overrideMetadata);
8384
});
8485
}
8586
}
@@ -90,13 +91,11 @@ private void postProcessFields(TestContext testContext, BiConsumer<TestContextOv
9091
Class<?> testClass = testContext.getTestClass();
9192
Object testInstance = testContext.getTestInstance();
9293

93-
if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) {
94+
List<OverrideMetadata> metadataForFields = OverrideMetadata.forTestClass(testClass);
95+
if (!metadataForFields.isEmpty()) {
9496
BeanOverrideRegistrar registrar =
9597
testContext.getApplicationContext().getBean(BeanOverrideRegistrar.class);
96-
for (OverrideMetadata metadata : registrar.getOverrideMetadata()) {
97-
if (!metadata.getField().getDeclaringClass().isAssignableFrom(testClass)) {
98-
continue;
99-
}
98+
for (OverrideMetadata metadata : metadataForFields) {
10099
consumer.accept(new TestContextOverrideMetadata(testInstance, metadata), registrar);
101100
}
102101
}

0 commit comments

Comments
 (0)