Swallow BeanCurrentlyInCreationException exceptions

Update `TestcontainersLifecycleBeanPostProcessor` to that initialization
doesn't fail if a `BeanCurrentlyInCreationException` is thrown.

Prior to this commit, if the first bean being post-processed was a
configuration class declaring a bean that the `Container` depended on
all initialization would fail.

See gh-35223
This commit is contained in:
Phillip Webb 2023-05-02 18:53:31 -07:00
parent 3997771f6c
commit e9578fe745
2 changed files with 60 additions and 8 deletions

View File

@ -19,7 +19,6 @@ package org.springframework.boot.testcontainers.lifecycle;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -28,6 +27,8 @@ import org.testcontainers.containers.GenericContainer;
import org.testcontainers.lifecycle.Startable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
@ -57,7 +58,7 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
private ConfigurableListableBeanFactory beanFactory;
private AtomicBoolean initializedContainers = new AtomicBoolean();
private volatile boolean containersInitialized = false;
TestcontainersLifecycleBeanPostProcessor(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
@ -75,14 +76,27 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
}
private void initializeContainers() {
if (this.initializedContainers.compareAndSet(false, true)) {
Set<String> beanNames = new LinkedHashSet<>();
beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false)));
beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(Startable.class, false, false)));
for (String beanName : beanNames) {
logger.debug(LogMessage.format("Initializing container bean '%s'", beanName));
if (this.containersInitialized) {
return;
}
this.containersInitialized = true;
Set<String> beanNames = new LinkedHashSet<>();
beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false)));
beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(Startable.class, false, false)));
for (String beanName : beanNames) {
try {
this.beanFactory.getBean(beanName);
}
catch (BeanCreationException ex) {
if (ex.contains(BeanCurrentlyInCreationException.class)) {
this.containersInitialized = false;
return;
}
throw ex;
}
}
if (!beanNames.isEmpty()) {
logger.debug(LogMessage.format("Initialized container beans '%s'", beanNames));
}
}

View File

@ -95,6 +95,15 @@ class TestcontainersLifecycleApplicationContextInitializerTests {
assertThat(applicationContext.getBeanFactoryPostProcessors()).hasSize(initialNumberOfPostProcessors + 1);
}
@Test
void dealsWithBeanCurrentlyInCreationException() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext);
applicationContext.register(BeanCurrentlyInCreationExceptionConfiguration2.class,
BeanCurrentlyInCreationExceptionConfiguration1.class);
applicationContext.refresh();
}
private AnnotationConfigApplicationContext createApplicationContext(Startable container) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
new TestcontainersLifecycleApplicationContextInitializer().initialize(applicationContext);
@ -114,4 +123,33 @@ class TestcontainersLifecycleApplicationContextInitializerTests {
}
@Configuration
static class BeanCurrentlyInCreationExceptionConfiguration1 {
@Bean
TestBean testBean() {
return new TestBean();
}
}
@Configuration
static class BeanCurrentlyInCreationExceptionConfiguration2 {
BeanCurrentlyInCreationExceptionConfiguration2(TestBean testBean) {
}
@Bean
GenericContainer<?> container(TestBean testBean) {
GenericContainer<?> container = mock(GenericContainer.class);
given(container.isShouldBeReused()).willReturn(true);
return container;
}
}
static class TestBean {
}
}