mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
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 7d6532cac4
whilst still retaining the correct run order.
Fixes gh-38647
This commit is contained in:
parent
e63be1bf73
commit
13fb450563
@ -17,12 +17,15 @@
|
|||||||
package org.springframework.boot;
|
package org.springframework.boot;
|
||||||
|
|
||||||
import java.lang.StackWalker.StackFrame;
|
import java.lang.StackWalker.StackFrame;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -38,14 +41,17 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
import org.springframework.aot.AotDetector;
|
import org.springframework.aot.AotDetector;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
|
import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
|
||||||
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
|
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||||
import org.springframework.boot.Banner.Mode;
|
import org.springframework.boot.Banner.Mode;
|
||||||
import org.springframework.boot.context.properties.bind.Bindable;
|
import org.springframework.boot.context.properties.bind.Bindable;
|
||||||
@ -68,6 +74,8 @@ import org.springframework.context.aot.AotApplicationContextInitializer;
|
|||||||
import org.springframework.context.support.AbstractApplicationContext;
|
import org.springframework.context.support.AbstractApplicationContext;
|
||||||
import org.springframework.context.support.GenericApplicationContext;
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
import org.springframework.core.GenericTypeResolver;
|
import org.springframework.core.GenericTypeResolver;
|
||||||
|
import org.springframework.core.OrderComparator;
|
||||||
|
import org.springframework.core.OrderComparator.OrderSourceProvider;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
@ -746,33 +754,40 @@ public class SpringApplication {
|
|||||||
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
|
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callRunners(ApplicationContext context, ApplicationArguments args) {
|
private void callRunners(ConfigurableApplicationContext context, ApplicationArguments args) {
|
||||||
context.getBeanProvider(Runner.class).orderedStream().forEach((runner) -> {
|
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||||
if (runner instanceof ApplicationRunner applicationRunner) {
|
String[] beanNames = beanFactory.getBeanNamesForType(Runner.class);
|
||||||
callRunner(applicationRunner, args);
|
Map<Runner, String> instancesToBeanNames = new IdentityHashMap<>();
|
||||||
}
|
for (String beanName : beanNames) {
|
||||||
if (runner instanceof CommandLineRunner commandLineRunner) {
|
instancesToBeanNames.put(beanFactory.getBean(beanName, Runner.class), beanName);
|
||||||
callRunner(commandLineRunner, args);
|
}
|
||||||
}
|
Comparator<Object> comparator = getOrderComparator(beanFactory)
|
||||||
});
|
.withSourceProvider(new FactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
|
||||||
|
instancesToBeanNames.keySet().stream().sorted(comparator).forEach((runner) -> callRunner(runner, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
|
private OrderComparator getOrderComparator(ConfigurableListableBeanFactory beanFactory) {
|
||||||
try {
|
Comparator<?> dependencyComparator = (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory)
|
||||||
(runner).run(args);
|
? defaultListableBeanFactory.getDependencyComparator() : null;
|
||||||
|
return (dependencyComparator instanceof OrderComparator orderComparator) ? orderComparator
|
||||||
|
: AnnotationAwareOrderComparator.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callRunner(Runner runner, ApplicationArguments args) {
|
||||||
|
if (runner instanceof ApplicationRunner) {
|
||||||
|
callRunner(ApplicationRunner.class, runner, (applicationRunner) -> applicationRunner.run(args));
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
if (runner instanceof CommandLineRunner) {
|
||||||
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
|
callRunner(CommandLineRunner.class, runner,
|
||||||
|
(commandLineRunner) -> commandLineRunner.run(args.getSourceArgs()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
|
@SuppressWarnings("unchecked")
|
||||||
try {
|
private <R extends Runner> void callRunner(Class<R> type, Runner runner, ThrowingConsumer<R> call) {
|
||||||
(runner).run(args.getSourceArgs());
|
call.throwing(
|
||||||
}
|
(message, ex) -> new IllegalStateException("Failed to execute " + ClassUtils.getShortName(type), ex))
|
||||||
catch (Exception ex) {
|
.accept((R) runner);
|
||||||
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
|
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
|
||||||
@ -1598,4 +1613,41 @@ public class SpringApplication {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link OrderSourceProvider} used to obtain factory method and target type order
|
||||||
|
* sources. Based on internal {@link DefaultListableBeanFactory} code.
|
||||||
|
*/
|
||||||
|
private class FactoryAwareOrderSourceProvider implements OrderSourceProvider {
|
||||||
|
|
||||||
|
private final ConfigurableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
private final Map<?, String> instancesToBeanNames;
|
||||||
|
|
||||||
|
FactoryAwareOrderSourceProvider(ConfigurableBeanFactory beanFactory, Map<?, String> instancesToBeanNames) {
|
||||||
|
this.beanFactory = beanFactory;
|
||||||
|
this.instancesToBeanNames = instancesToBeanNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getOrderSource(Object obj) {
|
||||||
|
String beanName = this.instancesToBeanNames.get(obj);
|
||||||
|
return (beanName != null) ? getOrderSource(beanName, obj.getClass()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getOrderSource(String beanName, Class<?> instanceType) {
|
||||||
|
try {
|
||||||
|
RootBeanDefinition beanDefinition = (RootBeanDefinition) this.beanFactory
|
||||||
|
.getMergedBeanDefinition(beanName);
|
||||||
|
Method factoryMethod = beanDefinition.getResolvedFactoryMethod();
|
||||||
|
Class<?> targetType = beanDefinition.getTargetType();
|
||||||
|
targetType = (targetType != instanceType) ? targetType : null;
|
||||||
|
return Stream.of(factoryMethod, targetType).filter(Objects::nonNull).toArray();
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ import org.springframework.boot.availability.AvailabilityChangeEvent;
|
|||||||
import org.springframework.boot.availability.AvailabilityState;
|
import org.springframework.boot.availability.AvailabilityState;
|
||||||
import org.springframework.boot.availability.LivenessState;
|
import org.springframework.boot.availability.LivenessState;
|
||||||
import org.springframework.boot.availability.ReadinessState;
|
import org.springframework.boot.availability.ReadinessState;
|
||||||
|
import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
|
||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
|
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
|
||||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||||
@ -630,6 +631,19 @@ class SpringApplicationTests {
|
|||||||
assertThat(this.context).has(runTestRunnerBean("runnerC"));
|
assertThat(this.context).has(runTestRunnerBean("runnerC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runCommandLineRunnersAndApplicationRunnersWithParentContext() {
|
||||||
|
SpringApplication application = new SpringApplication(CommandLineRunConfig.class);
|
||||||
|
application.setWebApplicationType(WebApplicationType.NONE);
|
||||||
|
application.addInitializers(new ParentContextApplicationContextInitializer(
|
||||||
|
new AnnotationConfigApplicationContext(CommandLineRunParentConfig.class)));
|
||||||
|
this.context = application.run("arg");
|
||||||
|
assertThat(this.context).has(runTestRunnerBean("runnerA"));
|
||||||
|
assertThat(this.context).has(runTestRunnerBean("runnerB"));
|
||||||
|
assertThat(this.context).has(runTestRunnerBean("runnerC"));
|
||||||
|
assertThat(this.context).doesNotHave(runTestRunnerBean("runnerP"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void runCommandLineRunnersAndApplicationRunnersUsingOrderOnBeanDefinitions() {
|
void runCommandLineRunnersAndApplicationRunnersUsingOrderOnBeanDefinitions() {
|
||||||
SpringApplication application = new SpringApplication(BeanDefinitionOrderRunnerConfig.class);
|
SpringApplication application = new SpringApplication(BeanDefinitionOrderRunnerConfig.class);
|
||||||
@ -1432,7 +1446,7 @@ class SpringApplicationTests {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Condition<ConfigurableApplicationContext> runTestRunnerBean(final String name) {
|
private Condition<ConfigurableApplicationContext> runTestRunnerBean(String name) {
|
||||||
return new Condition<>("run testrunner bean") {
|
return new Condition<>("run testrunner bean") {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1642,17 +1656,27 @@ class SpringApplicationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
TestCommandLineRunner runnerC() {
|
TestCommandLineRunner runnerC() {
|
||||||
return new TestCommandLineRunner(Ordered.LOWEST_PRECEDENCE, "runnerB", "runnerA");
|
return new TestCommandLineRunner("runnerC", Ordered.LOWEST_PRECEDENCE, "runnerB", "runnerA");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
TestApplicationRunner runnerB() {
|
TestApplicationRunner runnerB() {
|
||||||
return new TestApplicationRunner(Ordered.LOWEST_PRECEDENCE - 1, "runnerA");
|
return new TestApplicationRunner("runnerB", Ordered.LOWEST_PRECEDENCE - 1, "runnerA");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
TestCommandLineRunner runnerA() {
|
TestCommandLineRunner runnerA() {
|
||||||
return new TestCommandLineRunner(Ordered.HIGHEST_PRECEDENCE);
|
return new TestCommandLineRunner("runnerA", Ordered.HIGHEST_PRECEDENCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CommandLineRunParentConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
TestCommandLineRunner runnerP() {
|
||||||
|
return new TestCommandLineRunner("runnerP", Ordered.LOWEST_PRECEDENCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1861,12 +1885,16 @@ class SpringApplicationTests {
|
|||||||
|
|
||||||
static class TestCommandLineRunner extends AbstractTestRunner implements CommandLineRunner {
|
static class TestCommandLineRunner extends AbstractTestRunner implements CommandLineRunner {
|
||||||
|
|
||||||
TestCommandLineRunner(int order, String... expectedBefore) {
|
private final String name;
|
||||||
|
|
||||||
|
TestCommandLineRunner(String name, int order, String... expectedBefore) {
|
||||||
super(order, expectedBefore);
|
super(order, expectedBefore);
|
||||||
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(String... args) {
|
public void run(String... args) {
|
||||||
|
System.out.println(">>> " + this.name);
|
||||||
markAsRan();
|
markAsRan();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1874,12 +1902,16 @@ class SpringApplicationTests {
|
|||||||
|
|
||||||
static class TestApplicationRunner extends AbstractTestRunner implements ApplicationRunner {
|
static class TestApplicationRunner extends AbstractTestRunner implements ApplicationRunner {
|
||||||
|
|
||||||
TestApplicationRunner(int order, String... expectedBefore) {
|
private final String name;
|
||||||
|
|
||||||
|
TestApplicationRunner(String name, int order, String... expectedBefore) {
|
||||||
super(order, expectedBefore);
|
super(order, expectedBefore);
|
||||||
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(ApplicationArguments args) {
|
public void run(ApplicationArguments args) {
|
||||||
|
System.out.println(">>> " + this.name);
|
||||||
markAsRan();
|
markAsRan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user