Skip to content

Commit 1652c27

Browse files
committed
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
1 parent 0cfc14e commit 1652c27

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Optional;
3131
import java.util.Properties;
3232
import java.util.Set;
33+
import java.util.concurrent.atomic.AtomicBoolean;
3334
import java.util.stream.Stream;
3435

3536
import org.apache.commons.logging.Log;
@@ -1457,10 +1458,10 @@ public Augmented with(Class<?>... sources) {
14571458
*/
14581459
public SpringApplication.Running run(String... args) {
14591460
RunListener runListener = new RunListener();
1460-
SpringApplicationHook hook = (springApplication) -> {
1461+
SpringApplicationHook hook = new SingleUseSpringApplicationHook((springApplication) -> {
14611462
springApplication.addPrimarySources(this.sources);
14621463
return runListener;
1463-
};
1464+
});
14641465
withHook(hook, () -> this.main.accept(args));
14651466
return runListener;
14661467
}
@@ -1580,4 +1581,21 @@ public ConfigurableApplicationContext getApplicationContext() {
15801581

15811582
}
15821583

1584+
private static final class SingleUseSpringApplicationHook implements SpringApplicationHook {
1585+
1586+
private final AtomicBoolean used = new AtomicBoolean();
1587+
1588+
private final SpringApplicationHook delegate;
1589+
1590+
private SingleUseSpringApplicationHook(SpringApplicationHook delegate) {
1591+
this.delegate = delegate;
1592+
}
1593+
1594+
@Override
1595+
public SpringApplicationRunListener getRunListener(SpringApplication springApplication) {
1596+
return this.used.compareAndSet(false, true) ? this.delegate.getRunListener(springApplication) : null;
1597+
}
1598+
1599+
}
1600+
15831601
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
27+
import java.util.concurrent.atomic.AtomicBoolean;
2728
import java.util.concurrent.atomic.AtomicInteger;
2829
import java.util.concurrent.atomic.AtomicReference;
2930
import java.util.function.Supplier;
@@ -1363,18 +1364,30 @@ void shouldUseAotInitializer() {
13631364
@Test
13641365
void fromRunsWithAdditionalSources() {
13651366
assertThat(ExampleAdditionalConfig.local.get()).isNull();
1366-
SpringApplication.from(ExampleFromMainMethod::main).with(ExampleAdditionalConfig.class).run();
1367+
this.context = SpringApplication.from(ExampleFromMainMethod::main)
1368+
.with(ExampleAdditionalConfig.class)
1369+
.run()
1370+
.getApplicationContext();
13671371
assertThat(ExampleAdditionalConfig.local.get()).isNotNull();
13681372
ExampleAdditionalConfig.local.set(null);
13691373
}
13701374

13711375
@Test
13721376
void fromReturnsApplicationContext() {
1373-
ConfigurableApplicationContext context = SpringApplication.from(ExampleFromMainMethod::main)
1377+
this.context = SpringApplication.from(ExampleFromMainMethod::main)
13741378
.with(ExampleAdditionalConfig.class)
13751379
.run()
13761380
.getApplicationContext();
1377-
assertThat(context).isNotNull();
1381+
assertThat(this.context).isNotNull();
1382+
}
1383+
1384+
@Test
1385+
void fromWithMultipleApplicationsOnlyAppliesAdditionalSourcesOnce() {
1386+
this.context = SpringApplication.from(MultipleApplicationsMainMethod::main)
1387+
.with(SingleUseAdditionalConfig.class)
1388+
.run()
1389+
.getApplicationContext();
1390+
assertThatNoException().isThrownBy(() -> this.context.getBean(SingleUseAdditionalConfig.class));
13781391
}
13791392

13801393
private <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState(
@@ -1949,6 +1962,31 @@ static void main(String[] args) {
19491962

19501963
}
19511964

1965+
static class MultipleApplicationsMainMethod {
1966+
1967+
static void main(String[] args) {
1968+
SpringApplication application = new SpringApplication(ExampleConfig.class);
1969+
application.setWebApplicationType(WebApplicationType.NONE);
1970+
application.addListeners(new ApplicationListener<ApplicationEnvironmentPreparedEvent>() {
1971+
1972+
@Override
1973+
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
1974+
SpringApplicationBuilder builder = new SpringApplicationBuilder(
1975+
InnerApplicationConfiguration.class);
1976+
builder.web(WebApplicationType.NONE);
1977+
builder.run().close();
1978+
}
1979+
1980+
});
1981+
application.run(args);
1982+
}
1983+
1984+
static class InnerApplicationConfiguration {
1985+
1986+
}
1987+
1988+
}
1989+
19521990
@Configuration
19531991
static class ExampleAdditionalConfig {
19541992

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

19611999
}
19622000

2001+
@Configuration
2002+
static class SingleUseAdditionalConfig {
2003+
2004+
private static AtomicBoolean used = new AtomicBoolean(false);
2005+
2006+
SingleUseAdditionalConfig() {
2007+
if (!used.compareAndSet(false, true)) {
2008+
throw new IllegalStateException("Single-use configuration has already been used");
2009+
}
2010+
}
2011+
2012+
}
2013+
19632014
}

0 commit comments

Comments
 (0)