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
This commit is contained in:
Andy Wilkinson 2023-06-16 10:27:33 +01:00
parent 0cfc14ef6c
commit 1652c27b3c
2 changed files with 74 additions and 5 deletions

View File

@ -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;
}
}
}

View File

@ -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 <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
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");
}
}
}
}