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
This commit is contained in:
Andy Wilkinson 2023-05-30 15:03:17 +01:00
parent 9731934360
commit bd2fff1fd1
3 changed files with 30 additions and 1 deletions

View File

@ -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<T> beanType = (Class<T>) connectionDetails.getClass();
Supplier<T> 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;
}
}
}

View File

@ -0,0 +1,2 @@
org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar.ServiceConnectionBeanRegistrationExcludeFilter

View File

@ -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 {