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
This commit is contained in:
Sam Brannen
2024-10-06 16:02:41 +02:00
parent f590511112
commit ce8e06cf10
2 changed files with 23 additions and 54 deletions

View File

@@ -17,13 +17,8 @@
package org.springframework.test.context.bean.override;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
@@ -34,6 +29,7 @@ import org.springframework.test.context.MergedContextConfiguration;
*
* @author Simon Baslé
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
*/
class BeanOverrideContextCustomizer implements ContextCustomizer {
@@ -56,43 +52,25 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
if (!(context instanceof BeanDefinitionRegistry registry)) {
throw new IllegalStateException("Cannot process bean overrides with an ApplicationContext " +
"that doesn't implement BeanDefinitionRegistry: " + context.getClass().getName());
}
registerInfrastructure(registry);
ConfigurableBeanFactory beanFactory = context.getBeanFactory();
// Since all three Bean Override infrastructure beans are never injected as
// dependencies into other beans within the ApplicationContext, it is sufficient
// to register them as manual singleton instances. In addition, registration of
// the BeanOverrideBeanFactoryPostProcessor as a singleton is a requirement for
// AOT processing, since a bean definition cannot be generated for the
// Set<OverrideMetadata> argument that it accepts in its constructor.
BeanOverrideRegistrar beanOverrideRegistrar = new BeanOverrideRegistrar(beanFactory);
beanFactory.registerSingleton(REGISTRAR_BEAN_NAME, beanOverrideRegistrar);
beanFactory.registerSingleton(INFRASTRUCTURE_BEAN_NAME,
new BeanOverrideBeanFactoryPostProcessor(this.metadata, beanOverrideRegistrar));
beanFactory.registerSingleton(EARLY_INFRASTRUCTURE_BEAN_NAME,
new WrapEarlyBeanPostProcessor(beanOverrideRegistrar));
}
Set<OverrideMetadata> getMetadata() {
return this.metadata;
}
private void registerInfrastructure(BeanDefinitionRegistry registry) {
addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME,
constructorArgs -> {});
RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME);
addInfrastructureBeanDefinition(registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
addInfrastructureBeanDefinition(registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
constructorArgs -> {
constructorArgs.addIndexedArgumentValue(0, this.metadata);
constructorArgs.addIndexedArgumentValue(1, registrarReference);
});
}
private void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
if (!registry.containsBeanDefinition(beanName)) {
RootBeanDefinition definition = new RootBeanDefinition(clazz);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
constructorArgumentsConsumer.accept(constructorArguments);
registry.registerBeanDefinition(beanName, definition);
}
}
@Override
public boolean equals(Object other) {
if (other == this) {

View File

@@ -22,10 +22,7 @@ import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -36,36 +33,31 @@ import org.springframework.util.StringUtils;
* for test execution listeners.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
*/
class BeanOverrideRegistrar implements BeanFactoryAware {
class BeanOverrideRegistrar {
private final Map<OverrideMetadata, String> beanNameRegistry = new HashMap<>();
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
@Nullable
private ConfigurableBeanFactory beanFactory;
private final ConfigurableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
"that doesn't implement ConfigurableBeanFactory: " + beanFactory.getClass().getName());
}
this.beanFactory = cbf;
BeanOverrideRegistrar(ConfigurableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "ConfigurableBeanFactory must not be null");
this.beanFactory = beanFactory;
}
/**
* Check {@link #markWrapEarly(OverrideMetadata, String) early override}
* Check {@linkplain #markWrapEarly(OverrideMetadata, String) early override}
* records and use the {@link OverrideMetadata} to create an override
* instance from the provided bean, if relevant.
* instance based on the provided bean, if relevant.
*/
Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
if (metadata != null && metadata.getStrategy() == BeanOverrideStrategy.WRAP_BEAN) {
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
bean = metadata.createOverride(beanName, null, bean);
metadata.track(bean, this.beanFactory);
}
@@ -99,7 +91,6 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
try {
ReflectionUtils.makeAccessible(field);
Object existingValue = ReflectionUtils.getField(field, target);
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
Object bean = this.beanFactory.getBean(beanName, field.getType());
if (existingValue == bean) {
return;