Skip to content

Commit

Permalink
Apply additional sources once when using SpringApplication.from()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed Jun 16, 2023
1 parent 0cfc14e commit 1652c27
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand Down Expand Up @@ -1457,10 +1458,10 @@ public Augmented with(Class<?>... sources) {
*/
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;
}
Expand Down Expand Up @@ -1580,4 +1581,21 @@ public ConfigurableApplicationContext getApplicationContext() {

}

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

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -1363,18 +1364,30 @@ void shouldUseAotInitializer() {
@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(
Expand Down Expand Up @@ -1949,6 +1962,31 @@ static void main(String[] args) {

}

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 {

Expand All @@ -1960,4 +1998,17 @@ static class ExampleAdditionalConfig {

}

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

}

}

0 comments on commit 1652c27

Please sign in to comment.