From 5d20edc6660fed240ef46fdb657b5ad5d0ded029 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 17 Jun 2021 18:08:39 +0100 Subject: [PATCH] Allow BFPP to modify database initialization ordering Fixes gh-26899 --- ...aseInitializationDependencyConfigurer.java | 8 ++- ...itializationDependencyConfigurerTests.java | 57 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java index 4a70dd0e71..11cd99ec23 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java @@ -34,6 +34,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.util.Instantiator; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.Ordered; import org.springframework.core.env.Environment; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.type.AnnotationMetadata; @@ -84,7 +85,7 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef * {@link BeanFactoryPostProcessor} used to configure database initialization * dependency relationships. */ - static class DependsOnDatabaseInitializationPostProcessor implements BeanFactoryPostProcessor { + static class DependsOnDatabaseInitializationPostProcessor implements BeanFactoryPostProcessor, Ordered { private final Environment environment; @@ -92,6 +93,11 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef this.environment = environment; } + @Override + public int getOrder() { + return 0; + } + @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { Set initializerBeanNames = detectInitializerBeanNames(beanFactory); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java index c8fd265627..4a8fc5859f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java @@ -24,7 +24,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.function.Consumer; @@ -35,9 +38,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; @@ -70,6 +75,30 @@ class DatabaseInitializationDependencyConfigurerTests { MockedDependsOnDatabaseInitializationDetector.instance); } + @Test + void beanFactoryPostProcessorHasOrderAllowingSubsequentPostProcessorsToFineTuneDependencies() { + performDetection(Arrays.asList(MockDatabaseInitializerDetector.class, + MockedDependsOnDatabaseInitializationDetector.class), (context) -> { + BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class) + .getBeanDefinition(); + BeanDefinition bravo = BeanDefinitionBuilder.genericBeanDefinition(String.class) + .getBeanDefinition(); + context.register(DependsOnCaptor.class); + context.register(DependencyConfigurerConfiguration.class); + context.registerBeanDefinition("alpha", alpha); + context.registerBeanDefinition("bravo", bravo); + given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("alpha")); + given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("bravo")); + context.refresh(); + assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("bravo", + (dependencies) -> assertThat(dependencies).containsExactly("alpha")); + assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("alpha", + (dependencies) -> assertThat(dependencies).isEmpty()); + }); + } + @Test void whenDetectorsAreCreatedThenTheEnvironmentCanBeInjected() { performDetection(Arrays.asList(ConstructorInjectionDatabaseInitializerDetector.class, @@ -77,6 +106,7 @@ class DatabaseInitializationDependencyConfigurerTests { BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class) .getBeanDefinition(); context.registerBeanDefinition("alpha", alpha); + context.register(DependencyConfigurerConfiguration.class); context.refresh(); assertThat(ConstructorInjectionDatabaseInitializerDetector.environment).isEqualTo(this.environment); assertThat(ConstructorInjectionDependsOnDatabaseInitializationDetector.environment) @@ -96,6 +126,7 @@ class DatabaseInitializationDependencyConfigurerTests { .willReturn(Collections.singleton("alpha")); given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory())) .willReturn(Collections.singleton("bravo")); + context.register(DependencyConfigurerConfiguration.class); context.refresh(); assertThat(alpha.getAttribute(DatabaseInitializerDetector.class.getName())) .isEqualTo(MockDatabaseInitializerDetector.class.getName()); @@ -123,6 +154,7 @@ class DatabaseInitializationDependencyConfigurerTests { context.registerBeanDefinition("alpha", alpha); context.registerBeanDefinition("bravo", bravo); context.registerBeanDefinition("charlie", charlie); + context.register(DependencyConfigurerConfiguration.class); context.refresh(); assertThat(charlie.getDependsOn()).containsExactly("alpha", "bravo"); assertThat(bravo.getDependsOn()).containsExactly("alpha"); @@ -137,7 +169,6 @@ class DatabaseInitializationDependencyConfigurerTests { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { context.setEnvironment(this.environment); context.setClassLoader(detectorSpringFactories); - context.register(DependencyConfigurerConfiguration.class); contextCallback.accept(context); } } @@ -270,4 +301,28 @@ class DatabaseInitializationDependencyConfigurerTests { } + @Configuration(proxyBeanMethods = false) + static class DependsOnCaptor { + + static final Map> dependsOn = new HashMap<>(); + + @Bean + static BeanFactoryPostProcessor dependsOnCapturingPostProcessor() { + return (beanFactory) -> { + dependsOn.clear(); + for (String name : beanFactory.getBeanDefinitionNames()) { + storeDependsOn(name, beanFactory); + } + }; + } + + private static void storeDependsOn(String name, ConfigurableListableBeanFactory beanFactory) { + String[] dependsOn = beanFactory.getBeanDefinition(name).getDependsOn(); + if (dependsOn != null) { + DependsOnCaptor.dependsOn.put(name, Arrays.asList(dependsOn)); + } + } + + } + }