Skip pseudo bean definition registration during AOT processing

Prior to this commit, AOT processing failed for tests that made use of
the Bean Override feature to "override" a nonexistent bean. The reason
is that we register a "pseudo" bean definition as a placeholder for a
nonexistent bean, and our AOT support cannot automatically convert that
"pseudo" bean definition to a functional bean definition for use at AOT
runtime.

To address that, this commit skips registration of "pseudo" bean
definitions during AOT processing, and by doing so we enable the JVM
runtime and AOT runtime to operate with the same semantics.

See gh-32933
This commit is contained in:
Sam Brannen
2024-10-06 16:05:36 +02:00
parent ce8e06cf10
commit 5ddeb06640
2 changed files with 24 additions and 1 deletions

View File

@@ -35,6 +35,7 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.aot.AbstractAotProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
@@ -105,6 +106,12 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
private void replaceDefinition(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata,
boolean enforceExistingDefinition) {
// NOTE: This method supports 3 distinct scenarios which must be accounted for.
//
// 1) JVM runtime
// 2) AOT processing
// 3) AOT runtime
if (!(beanFactory instanceof BeanDefinitionRegistry registry)) {
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
"that doesn't implement BeanDefinitionRegistry: " + beanFactory.getClass().getName());
@@ -147,12 +154,24 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
if (existingBeanDefinition != null) {
// Validate the existing bean definition.
//
// Applies during "JVM runtime", "AOT processing", and "AOT runtime".
validateBeanDefinition(beanFactory, beanName);
}
else if (Boolean.getBoolean(AbstractAotProcessor.AOT_PROCESSING)) {
// There was no existing bean definition, but during "AOT processing" we
// do not register the "pseudo" bean definition since our AOT support
// cannot automatically convert that to a functional bean definition for
// use at "AOT runtime". Furthermore, by not registering a bean definition
// for a nonexistent bean, we allow the "JVM runtime" and "AOT runtime"
// to operate the same in the following else-block.
}
else {
// There was no existing bean definition, so we register the "pseudo" bean
// definition to ensure that a suitable bean definition exists for the given
// bean name for proper autowiring candidate resolution.
//
// Applies during "JVM runtime" and "AOT runtime".
registry.registerBeanDefinition(beanName, pseudoBeanDefinition);
}
@@ -163,6 +182,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// Now we have an instance (the override) that we can register. At this stage, we don't
// expect a singleton instance to be present. If for some reason a singleton instance
// already exists, the following will throw an exception.
//
// As a bonus, by manually registering a singleton during "AOT processing", we allow
// GenericApplicationContext's preDetermineBeanType() method to transparently register
// runtime hints for a proxy generated by the above createOverride() invocation --
// for example, when @MockitoBean creates a mock based on a JDK dynamic proxy.
beanFactory.registerSingleton(beanName, override);
}