From 5ddeb06640c379448f943b1ae24f5ef2facccc37 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:05:36 +0200 Subject: [PATCH] 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 --- .../BeanOverrideBeanFactoryPostProcessor.java | 24 +++++++++++++++++++ .../test/context/aot/AotIntegrationTests.java | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) 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 466a827c091..c8116f62789 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 @@ -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); } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java index 7695515c8df..e5fa0317f83 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java @@ -155,7 +155,6 @@ class AotIntegrationTests extends AbstractAotTests { runEndToEndTests(testClasses, false); } - @Disabled("Comment out to run Bean Override integration tests in AOT mode") @Test void endToEndTestsForBeanOverrides() { List> testClasses = createTestClassScanner()