diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 4666648570..fe2d6ac64d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -30,6 +30,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import org.apache.commons.logging.Log; @@ -1457,10 +1458,10 @@ public class SpringApplication { */ public SpringApplication.Running run(String... args) { RunListener runListener = new RunListener(); - SpringApplicationHook hook = (springApplication) -> { + SpringApplicationHook hook = new SingleUseSpringApplicationHook((springApplication) -> { springApplication.addPrimarySources(this.sources); return runListener; - }; + }); withHook(hook, () -> this.main.accept(args)); return runListener; } @@ -1580,4 +1581,21 @@ public class SpringApplication { } + private static final class SingleUseSpringApplicationHook implements SpringApplicationHook { + + private final AtomicBoolean used = new AtomicBoolean(); + + private final SpringApplicationHook delegate; + + private SingleUseSpringApplicationHook(SpringApplicationHook delegate) { + this.delegate = delegate; + } + + @Override + public SpringApplicationRunListener getRunListener(SpringApplication springApplication) { + return this.used.compareAndSet(false, true) ? this.delegate.getRunListener(springApplication) : null; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 3b9d87be9b..9523dc903c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -24,6 +24,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -1363,18 +1364,30 @@ class SpringApplicationTests { @Test void fromRunsWithAdditionalSources() { assertThat(ExampleAdditionalConfig.local.get()).isNull(); - SpringApplication.from(ExampleFromMainMethod::main).with(ExampleAdditionalConfig.class).run(); + this.context = SpringApplication.from(ExampleFromMainMethod::main) + .with(ExampleAdditionalConfig.class) + .run() + .getApplicationContext(); assertThat(ExampleAdditionalConfig.local.get()).isNotNull(); ExampleAdditionalConfig.local.set(null); } @Test void fromReturnsApplicationContext() { - ConfigurableApplicationContext context = SpringApplication.from(ExampleFromMainMethod::main) + this.context = SpringApplication.from(ExampleFromMainMethod::main) .with(ExampleAdditionalConfig.class) .run() .getApplicationContext(); - assertThat(context).isNotNull(); + assertThat(this.context).isNotNull(); + } + + @Test + void fromWithMultipleApplicationsOnlyAppliesAdditionalSourcesOnce() { + this.context = SpringApplication.from(MultipleApplicationsMainMethod::main) + .with(SingleUseAdditionalConfig.class) + .run() + .getApplicationContext(); + assertThatNoException().isThrownBy(() -> this.context.getBean(SingleUseAdditionalConfig.class)); } private ArgumentMatcher isAvailabilityChangeEventWithState( @@ -1949,6 +1962,31 @@ class SpringApplicationTests { } + static class MultipleApplicationsMainMethod { + + static void main(String[] args) { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addListeners(new ApplicationListener() { + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + SpringApplicationBuilder builder = new SpringApplicationBuilder( + InnerApplicationConfiguration.class); + builder.web(WebApplicationType.NONE); + builder.run().close(); + } + + }); + application.run(args); + } + + static class InnerApplicationConfiguration { + + } + + } + @Configuration static class ExampleAdditionalConfig { @@ -1960,4 +1998,17 @@ class SpringApplicationTests { } + @Configuration + static class SingleUseAdditionalConfig { + + private static AtomicBoolean used = new AtomicBoolean(false); + + SingleUseAdditionalConfig() { + if (!used.compareAndSet(false, true)) { + throw new IllegalStateException("Single-use configuration has already been used"); + } + } + + } + }