From e9578fe74512ba5e6d1990bce083c74ba73a61ee Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 2 May 2023 18:53:31 -0700 Subject: [PATCH] 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 --- ...tcontainersLifecycleBeanPostProcessor.java | 30 +++++++++++---- ...cleApplicationContextInitializerTests.java | 38 +++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java index 5316485d5e..5c70032d09 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java @@ -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 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 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)); } } diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java index 111216fb01..caf27a1508 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleApplicationContextInitializerTests.java @@ -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 { + + } + }