Skip to content

Commit ce8e06c

Browse files
committed
Register Bean Override infrastructure beans as manual singletons
Prior to this commit, AOT processing failed for tests that made use of the Bean Override feature, since the Set<OverrideMetadata> constructor argument configured in the bean definition for the BeanOverrideBeanFactoryPostProcessor cannot be properly processed by our AOT support. The reason is that each OverrideMetadata instance is effectively an arbitrary object graph that cannot be automatically converted to a functional bean definition for use at AOT runtime. To address that, this commit registers Bean Override infrastructure beans as manual singletons instead of via bean definitions with the infrastructure role. See gh-32933
1 parent f590511 commit ce8e06c

File tree

2 files changed

+23
-54
lines changed

2 files changed

+23
-54
lines changed

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

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,8 @@
1717
package org.springframework.test.context.bean.override;
1818

1919
import java.util.Set;
20-
import java.util.function.Consumer;
2120

22-
import org.springframework.beans.factory.config.BeanDefinition;
23-
import org.springframework.beans.factory.config.ConstructorArgumentValues;
24-
import org.springframework.beans.factory.config.RuntimeBeanReference;
25-
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
26-
import org.springframework.beans.factory.support.RootBeanDefinition;
21+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
2722
import org.springframework.context.ConfigurableApplicationContext;
2823
import org.springframework.test.context.ContextCustomizer;
2924
import org.springframework.test.context.MergedContextConfiguration;
@@ -34,6 +29,7 @@
3429
*
3530
* @author Simon Baslé
3631
* @author Stephane Nicoll
32+
* @author Sam Brannen
3733
* @since 6.2
3834
*/
3935
class BeanOverrideContextCustomizer implements ContextCustomizer {
@@ -56,43 +52,25 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
5652

5753
@Override
5854
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
59-
if (!(context instanceof BeanDefinitionRegistry registry)) {
60-
throw new IllegalStateException("Cannot process bean overrides with an ApplicationContext " +
61-
"that doesn't implement BeanDefinitionRegistry: " + context.getClass().getName());
62-
}
63-
registerInfrastructure(registry);
55+
ConfigurableBeanFactory beanFactory = context.getBeanFactory();
56+
// Since all three Bean Override infrastructure beans are never injected as
57+
// dependencies into other beans within the ApplicationContext, it is sufficient
58+
// to register them as manual singleton instances. In addition, registration of
59+
// the BeanOverrideBeanFactoryPostProcessor as a singleton is a requirement for
60+
// AOT processing, since a bean definition cannot be generated for the
61+
// Set<OverrideMetadata> argument that it accepts in its constructor.
62+
BeanOverrideRegistrar beanOverrideRegistrar = new BeanOverrideRegistrar(beanFactory);
63+
beanFactory.registerSingleton(REGISTRAR_BEAN_NAME, beanOverrideRegistrar);
64+
beanFactory.registerSingleton(INFRASTRUCTURE_BEAN_NAME,
65+
new BeanOverrideBeanFactoryPostProcessor(this.metadata, beanOverrideRegistrar));
66+
beanFactory.registerSingleton(EARLY_INFRASTRUCTURE_BEAN_NAME,
67+
new WrapEarlyBeanPostProcessor(beanOverrideRegistrar));
6468
}
6569

6670
Set<OverrideMetadata> getMetadata() {
6771
return this.metadata;
6872
}
6973

70-
private void registerInfrastructure(BeanDefinitionRegistry registry) {
71-
addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME,
72-
constructorArgs -> {});
73-
74-
RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME);
75-
addInfrastructureBeanDefinition(registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
76-
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
77-
addInfrastructureBeanDefinition(registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
78-
constructorArgs -> {
79-
constructorArgs.addIndexedArgumentValue(0, this.metadata);
80-
constructorArgs.addIndexedArgumentValue(1, registrarReference);
81-
});
82-
}
83-
84-
private void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
85-
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
86-
87-
if (!registry.containsBeanDefinition(beanName)) {
88-
RootBeanDefinition definition = new RootBeanDefinition(clazz);
89-
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
90-
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
91-
constructorArgumentsConsumer.accept(constructorArguments);
92-
registry.registerBeanDefinition(beanName, definition);
93-
}
94-
}
95-
9674
@Override
9775
public boolean equals(Object other) {
9876
if (other == this) {

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

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@
2222

2323
import org.springframework.beans.BeansException;
2424
import org.springframework.beans.factory.BeanCreationException;
25-
import org.springframework.beans.factory.BeanFactory;
26-
import org.springframework.beans.factory.BeanFactoryAware;
2725
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
28-
import org.springframework.lang.Nullable;
2926
import org.springframework.util.Assert;
3027
import org.springframework.util.ReflectionUtils;
3128
import org.springframework.util.StringUtils;
@@ -36,36 +33,31 @@
3633
* for test execution listeners.
3734
*
3835
* @author Simon Baslé
36+
* @author Sam Brannen
3937
* @since 6.2
4038
*/
41-
class BeanOverrideRegistrar implements BeanFactoryAware {
39+
class BeanOverrideRegistrar {
4240

4341
private final Map<OverrideMetadata, String> beanNameRegistry = new HashMap<>();
4442

4543
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
4644

47-
@Nullable
48-
private ConfigurableBeanFactory beanFactory;
45+
private final ConfigurableBeanFactory beanFactory;
4946

5047

51-
@Override
52-
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
53-
if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
54-
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
55-
"that doesn't implement ConfigurableBeanFactory: " + beanFactory.getClass().getName());
56-
}
57-
this.beanFactory = cbf;
48+
BeanOverrideRegistrar(ConfigurableBeanFactory beanFactory) {
49+
Assert.notNull(beanFactory, "ConfigurableBeanFactory must not be null");
50+
this.beanFactory = beanFactory;
5851
}
5952

6053
/**
61-
* Check {@link #markWrapEarly(OverrideMetadata, String) early override}
54+
* Check {@linkplain #markWrapEarly(OverrideMetadata, String) early override}
6255
* records and use the {@link OverrideMetadata} to create an override
63-
* instance from the provided bean, if relevant.
56+
* instance based on the provided bean, if relevant.
6457
*/
6558
Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
6659
OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
6760
if (metadata != null && metadata.getStrategy() == BeanOverrideStrategy.WRAP_BEAN) {
68-
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
6961
bean = metadata.createOverride(beanName, null, bean);
7062
metadata.track(bean, this.beanFactory);
7163
}
@@ -99,7 +91,6 @@ private void inject(Field field, Object target, String beanName) {
9991
try {
10092
ReflectionUtils.makeAccessible(field);
10193
Object existingValue = ReflectionUtils.getField(field, target);
102-
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
10394
Object bean = this.beanFactory.getBean(beanName, field.getType());
10495
if (existingValue == bean) {
10596
return;

0 commit comments

Comments
 (0)