Apply additional sources once when using SpringApplication.from()

Previously, when using SpringApplication.from() any additional
sources configured using with() would be applied to every
SpringApplication that was created within the scope of the call to
run(). This caused problems with Spring Cloud's bootstrap context
where the additional sources would be applied to both the user's
application and to the boostrap context's application.

This commit updates the hook that's used to apply the additional
sources so that it's only applied once. This results in the
additional sources only being added to the first SpringApplication
that is run.

Closes gh-35873
pull/36038/head
Andy Wilkinson 1 year ago
parent 0cfc14ef6c
commit 1652c27b3c

@ -30,6 +30,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -1457,10 +1458,10 @@ public class SpringApplication {
*/ */
public SpringApplication.Running run(String... args) { public SpringApplication.Running run(String... args) {
RunListener runListener = new RunListener(); RunListener runListener = new RunListener();
SpringApplicationHook hook = (springApplication) -> { SpringApplicationHook hook = new SingleUseSpringApplicationHook((springApplication) -> {
springApplication.addPrimarySources(this.sources); springApplication.addPrimarySources(this.sources);
return runListener; return runListener;
}; });
withHook(hook, () -> this.main.accept(args)); withHook(hook, () -> this.main.accept(args));
return runListener; 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;
}
}
} }

@ -24,6 +24,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -1363,18 +1364,30 @@ class SpringApplicationTests {
@Test @Test
void fromRunsWithAdditionalSources() { void fromRunsWithAdditionalSources() {
assertThat(ExampleAdditionalConfig.local.get()).isNull(); 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(); assertThat(ExampleAdditionalConfig.local.get()).isNotNull();
ExampleAdditionalConfig.local.set(null); ExampleAdditionalConfig.local.set(null);
} }
@Test @Test
void fromReturnsApplicationContext() { void fromReturnsApplicationContext() {
ConfigurableApplicationContext context = SpringApplication.from(ExampleFromMainMethod::main) this.context = SpringApplication.from(ExampleFromMainMethod::main)
.with(ExampleAdditionalConfig.class) .with(ExampleAdditionalConfig.class)
.run() .run()
.getApplicationContext(); .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 <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState( private <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> 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<ApplicationEnvironmentPreparedEvent>() {
@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 @Configuration
static class ExampleAdditionalConfig { 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");
}
}
}
} }

Loading…
Cancel
Save