Skip to content

Commit 13fb450

Browse files
committed
Don't call runners in parent ApplicationContext
Update `SpringApplication` so that `ApplicationRunner` and `CommandLineRunner` beans are not considered from the parent `ApplicationContext`. The restores the behavior that applied before commit 7d6532c whilst still retaining the correct run order. Fixes gh-38647
1 parent e63be1b commit 13fb450

File tree

2 files changed

+111
-27
lines changed

2 files changed

+111
-27
lines changed

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

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
package org.springframework.boot;
1818

1919
import java.lang.StackWalker.StackFrame;
20+
import java.lang.reflect.Method;
2021
import java.time.Duration;
2122
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.Collection;
2425
import java.util.Collections;
26+
import java.util.Comparator;
2527
import java.util.HashMap;
28+
import java.util.IdentityHashMap;
2629
import java.util.LinkedHashSet;
2730
import java.util.List;
2831
import java.util.Map;
@@ -38,14 +41,17 @@
3841

3942
import org.springframework.aot.AotDetector;
4043
import org.springframework.beans.BeansException;
44+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
4145
import org.springframework.beans.factory.config.BeanDefinition;
4246
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
47+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4348
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4449
import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
4550
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
4651
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
4752
import org.springframework.beans.factory.support.BeanNameGenerator;
4853
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
54+
import org.springframework.beans.factory.support.RootBeanDefinition;
4955
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
5056
import org.springframework.boot.Banner.Mode;
5157
import org.springframework.boot.context.properties.bind.Bindable;
@@ -68,6 +74,8 @@
6874
import org.springframework.context.support.AbstractApplicationContext;
6975
import org.springframework.context.support.GenericApplicationContext;
7076
import org.springframework.core.GenericTypeResolver;
77+
import org.springframework.core.OrderComparator;
78+
import org.springframework.core.OrderComparator.OrderSourceProvider;
7179
import org.springframework.core.Ordered;
7280
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
7381
import org.springframework.core.annotation.Order;
@@ -746,35 +754,42 @@ protected void refresh(ConfigurableApplicationContext applicationContext) {
746754
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
747755
}
748756

749-
private void callRunners(ApplicationContext context, ApplicationArguments args) {
750-
context.getBeanProvider(Runner.class).orderedStream().forEach((runner) -> {
751-
if (runner instanceof ApplicationRunner applicationRunner) {
752-
callRunner(applicationRunner, args);
753-
}
754-
if (runner instanceof CommandLineRunner commandLineRunner) {
755-
callRunner(commandLineRunner, args);
756-
}
757-
});
757+
private void callRunners(ConfigurableApplicationContext context, ApplicationArguments args) {
758+
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
759+
String[] beanNames = beanFactory.getBeanNamesForType(Runner.class);
760+
Map<Runner, String> instancesToBeanNames = new IdentityHashMap<>();
761+
for (String beanName : beanNames) {
762+
instancesToBeanNames.put(beanFactory.getBean(beanName, Runner.class), beanName);
763+
}
764+
Comparator<Object> comparator = getOrderComparator(beanFactory)
765+
.withSourceProvider(new FactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
766+
instancesToBeanNames.keySet().stream().sorted(comparator).forEach((runner) -> callRunner(runner, args));
758767
}
759768

760-
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
761-
try {
762-
(runner).run(args);
763-
}
764-
catch (Exception ex) {
765-
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
766-
}
769+
private OrderComparator getOrderComparator(ConfigurableListableBeanFactory beanFactory) {
770+
Comparator<?> dependencyComparator = (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory)
771+
? defaultListableBeanFactory.getDependencyComparator() : null;
772+
return (dependencyComparator instanceof OrderComparator orderComparator) ? orderComparator
773+
: AnnotationAwareOrderComparator.INSTANCE;
767774
}
768775

769-
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
770-
try {
771-
(runner).run(args.getSourceArgs());
776+
private void callRunner(Runner runner, ApplicationArguments args) {
777+
if (runner instanceof ApplicationRunner) {
778+
callRunner(ApplicationRunner.class, runner, (applicationRunner) -> applicationRunner.run(args));
772779
}
773-
catch (Exception ex) {
774-
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
780+
if (runner instanceof CommandLineRunner) {
781+
callRunner(CommandLineRunner.class, runner,
782+
(commandLineRunner) -> commandLineRunner.run(args.getSourceArgs()));
775783
}
776784
}
777785

786+
@SuppressWarnings("unchecked")
787+
private <R extends Runner> void callRunner(Class<R> type, Runner runner, ThrowingConsumer<R> call) {
788+
call.throwing(
789+
(message, ex) -> new IllegalStateException("Failed to execute " + ClassUtils.getShortName(type), ex))
790+
.accept((R) runner);
791+
}
792+
778793
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
779794
SpringApplicationRunListeners listeners) {
780795
try {
@@ -1598,4 +1613,41 @@ public SpringApplicationRunListener getRunListener(SpringApplication springAppli
15981613

15991614
}
16001615

1616+
/**
1617+
* {@link OrderSourceProvider} used to obtain factory method and target type order
1618+
* sources. Based on internal {@link DefaultListableBeanFactory} code.
1619+
*/
1620+
private class FactoryAwareOrderSourceProvider implements OrderSourceProvider {
1621+
1622+
private final ConfigurableBeanFactory beanFactory;
1623+
1624+
private final Map<?, String> instancesToBeanNames;
1625+
1626+
FactoryAwareOrderSourceProvider(ConfigurableBeanFactory beanFactory, Map<?, String> instancesToBeanNames) {
1627+
this.beanFactory = beanFactory;
1628+
this.instancesToBeanNames = instancesToBeanNames;
1629+
}
1630+
1631+
@Override
1632+
public Object getOrderSource(Object obj) {
1633+
String beanName = this.instancesToBeanNames.get(obj);
1634+
return (beanName != null) ? getOrderSource(beanName, obj.getClass()) : null;
1635+
}
1636+
1637+
private Object getOrderSource(String beanName, Class<?> instanceType) {
1638+
try {
1639+
RootBeanDefinition beanDefinition = (RootBeanDefinition) this.beanFactory
1640+
.getMergedBeanDefinition(beanName);
1641+
Method factoryMethod = beanDefinition.getResolvedFactoryMethod();
1642+
Class<?> targetType = beanDefinition.getTargetType();
1643+
targetType = (targetType != instanceType) ? targetType : null;
1644+
return Stream.of(factoryMethod, targetType).filter(Objects::nonNull).toArray();
1645+
}
1646+
catch (NoSuchBeanDefinitionException ex) {
1647+
return null;
1648+
}
1649+
}
1650+
1651+
}
1652+
16011653
}

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import org.springframework.boot.availability.AvailabilityState;
6161
import org.springframework.boot.availability.LivenessState;
6262
import org.springframework.boot.availability.ReadinessState;
63+
import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
6364
import org.springframework.boot.builder.SpringApplicationBuilder;
6465
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
6566
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
@@ -630,6 +631,19 @@ void runCommandLineRunnersAndApplicationRunners() {
630631
assertThat(this.context).has(runTestRunnerBean("runnerC"));
631632
}
632633

634+
@Test
635+
void runCommandLineRunnersAndApplicationRunnersWithParentContext() {
636+
SpringApplication application = new SpringApplication(CommandLineRunConfig.class);
637+
application.setWebApplicationType(WebApplicationType.NONE);
638+
application.addInitializers(new ParentContextApplicationContextInitializer(
639+
new AnnotationConfigApplicationContext(CommandLineRunParentConfig.class)));
640+
this.context = application.run("arg");
641+
assertThat(this.context).has(runTestRunnerBean("runnerA"));
642+
assertThat(this.context).has(runTestRunnerBean("runnerB"));
643+
assertThat(this.context).has(runTestRunnerBean("runnerC"));
644+
assertThat(this.context).doesNotHave(runTestRunnerBean("runnerP"));
645+
}
646+
633647
@Test
634648
void runCommandLineRunnersAndApplicationRunnersUsingOrderOnBeanDefinitions() {
635649
SpringApplication application = new SpringApplication(BeanDefinitionOrderRunnerConfig.class);
@@ -1432,7 +1446,7 @@ public boolean matches(ConfigurableEnvironment value) {
14321446
};
14331447
}
14341448

1435-
private Condition<ConfigurableApplicationContext> runTestRunnerBean(final String name) {
1449+
private Condition<ConfigurableApplicationContext> runTestRunnerBean(String name) {
14361450
return new Condition<>("run testrunner bean") {
14371451

14381452
@Override
@@ -1642,17 +1656,27 @@ static class CommandLineRunConfig {
16421656

16431657
@Bean
16441658
TestCommandLineRunner runnerC() {
1645-
return new TestCommandLineRunner(Ordered.LOWEST_PRECEDENCE, "runnerB", "runnerA");
1659+
return new TestCommandLineRunner("runnerC", Ordered.LOWEST_PRECEDENCE, "runnerB", "runnerA");
16461660
}
16471661

16481662
@Bean
16491663
TestApplicationRunner runnerB() {
1650-
return new TestApplicationRunner(Ordered.LOWEST_PRECEDENCE - 1, "runnerA");
1664+
return new TestApplicationRunner("runnerB", Ordered.LOWEST_PRECEDENCE - 1, "runnerA");
16511665
}
16521666

16531667
@Bean
16541668
TestCommandLineRunner runnerA() {
1655-
return new TestCommandLineRunner(Ordered.HIGHEST_PRECEDENCE);
1669+
return new TestCommandLineRunner("runnerA", Ordered.HIGHEST_PRECEDENCE);
1670+
}
1671+
1672+
}
1673+
1674+
@Configuration(proxyBeanMethods = false)
1675+
static class CommandLineRunParentConfig {
1676+
1677+
@Bean
1678+
TestCommandLineRunner runnerP() {
1679+
return new TestCommandLineRunner("runnerP", Ordered.LOWEST_PRECEDENCE);
16561680
}
16571681

16581682
}
@@ -1861,25 +1885,33 @@ boolean hasRun() {
18611885

18621886
static class TestCommandLineRunner extends AbstractTestRunner implements CommandLineRunner {
18631887

1864-
TestCommandLineRunner(int order, String... expectedBefore) {
1888+
private final String name;
1889+
1890+
TestCommandLineRunner(String name, int order, String... expectedBefore) {
18651891
super(order, expectedBefore);
1892+
this.name = name;
18661893
}
18671894

18681895
@Override
18691896
public void run(String... args) {
1897+
System.out.println(">>> " + this.name);
18701898
markAsRan();
18711899
}
18721900

18731901
}
18741902

18751903
static class TestApplicationRunner extends AbstractTestRunner implements ApplicationRunner {
18761904

1877-
TestApplicationRunner(int order, String... expectedBefore) {
1905+
private final String name;
1906+
1907+
TestApplicationRunner(String name, int order, String... expectedBefore) {
18781908
super(order, expectedBefore);
1909+
this.name = name;
18791910
}
18801911

18811912
@Override
18821913
public void run(ApplicationArguments args) {
1914+
System.out.println(">>> " + this.name);
18831915
markAsRan();
18841916
}
18851917

0 commit comments

Comments
 (0)