From bd2fff1fd18603578c88386ea6517878f1dba44f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 30 May 2023 15:03:17 +0100 Subject: [PATCH] Fix `@ServiceConnection` in native tests Using `@ServiceConnection` results in the definition of one or more connection details beans. These bean definitions use an instance supplier which is not supported by AOT. This results in a failure during AOT processing. This commit introduces a BeanRegistrationExcludeFilter to exclude from AOT processing the beans created from a `@ServiceConnection`. They are not needed as the registrar will run again in the native image and define the beans at which point the use of an instance supplier is supported again. Fixes gh-35663 --- .../connection/ConnectionDetailsRegistrar.java | 15 ++++++++++++++- .../main/resources/META-INF/spring/aot.factories | 2 ++ .../ServiceConnectionAutoConfigurationTests.java | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring/aot.factories diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java index 90c0580926..318ef6a5f9 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ConnectionDetailsRegistrar.java @@ -28,7 +28,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; @@ -102,7 +104,9 @@ class ConnectionDetailsRegistrar { Class beanType = (Class) connectionDetails.getClass(); Supplier beanSupplier = () -> (T) connectionDetails; logger.debug(LogMessage.of(() -> "Registering '%s' for %s".formatted(beanName, source))); - registry.registerBeanDefinition(beanName, new RootBeanDefinition(beanType, beanSupplier)); + RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType, beanSupplier); + beanDefinition.setAttribute(ServiceConnection.class.getName(), true); + registry.registerBeanDefinition(beanName, beanDefinition); } private String getBeanName(ContainerConnectionSource source, ConnectionDetails connectionDetails) { @@ -113,4 +117,13 @@ class ConnectionDetailsRegistrar { return StringUtils.uncapitalize(parts.stream().map(StringUtils::capitalize).collect(Collectors.joining())); } + class ServiceConnectionBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter { + + @Override + public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { + return registeredBean.getMergedBeanDefinition().getAttribute(ServiceConnection.class.getName()) != null; + } + + } + } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring/aot.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..52cbdbe6b1 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\ +org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar.ServiceConnectionBeanRegistrationExcludeFilter \ No newline at end of file diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java index a55163bd1e..6e7fefc653 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ServiceConnectionAutoConfigurationTests.java @@ -21,6 +21,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -36,11 +37,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.type.AnnotationMetadata; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.Mockito.mock; /** @@ -101,6 +104,17 @@ class ServiceConnectionAutoConfigurationTests { } } + @Test + void serviceConnectionBeansDoNotCauseAotProcessingToFail() { + try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext()) { + applicationContext.register(WithNoExtraAutoConfiguration.class, ContainerConfiguration.class); + new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext); + TestGenerationContext generationContext = new TestGenerationContext(); + assertThatNoException().isThrownBy(() -> new ApplicationContextAotGenerator() + .processAheadOfTime(applicationContext, generationContext)); + } + } + @Configuration(proxyBeanMethods = false) @ImportAutoConfiguration(ServiceConnectionAutoConfiguration.class) static class WithNoExtraAutoConfiguration {