diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDatabaseInitializerDetector.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDatabaseInitializerDetector.java index 4199b14b8e..73690134dc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDatabaseInitializerDetector.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDatabaseInitializerDetector.java @@ -34,4 +34,9 @@ class FlywayMigrationInitializerDatabaseInitializerDetector extends AbstractBean return Collections.singleton(FlywayMigrationInitializer.class); } + @Override + public int getOrder() { + return 1; + } + } 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 41196a28a0..4a70dd0e71 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 @@ -98,10 +98,20 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef if (initializerBeanNames.isEmpty()) { return; } - for (String dependsOnInitializationBeanNames : detectDependsOnInitializationBeanNames(beanFactory)) { - BeanDefinition definition = getBeanDefinition(dependsOnInitializationBeanNames, beanFactory); - definition.setDependsOn(merge(definition.getDependsOn(), initializerBeanNames)); + String previousInitializerBeanName = null; + for (String initializerBeanName : initializerBeanNames) { + BeanDefinition beanDefinition = getBeanDefinition(initializerBeanName, beanFactory); + beanDefinition.setDependsOn(merge(beanDefinition.getDependsOn(), previousInitializerBeanName)); + previousInitializerBeanName = initializerBeanName; } + for (String dependsOnInitializationBeanNames : detectDependsOnInitializationBeanNames(beanFactory)) { + BeanDefinition beanDefinition = getBeanDefinition(dependsOnInitializationBeanNames, beanFactory); + beanDefinition.setDependsOn(merge(beanDefinition.getDependsOn(), initializerBeanNames)); + } + } + + private String[] merge(String[] source, String additional) { + return merge(source, (additional != null) ? Collections.singleton(additional) : Collections.emptySet()); } private String[] merge(String[] source, Set additional) { @@ -112,7 +122,7 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef private Set detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) { List detectors = getDetectors(beanFactory, DatabaseInitializerDetector.class); - Set beanNames = new HashSet<>(); + Set beanNames = new LinkedHashSet<>(); for (DatabaseInitializerDetector detector : detectors) { for (String beanName : detector.detect(beanFactory)) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializerDetector.java index 36ba4daee0..7d31de49c9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializerDetector.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializerDetector.java @@ -21,6 +21,7 @@ import java.util.Set; import javax.sql.DataSource; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.Ordered; /** * Detects beans that initialize an SQL database. Implementations should be registered in @@ -30,7 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; * @author Andy Wilkinson * @since 2.5.0 */ -public interface DatabaseInitializerDetector { +public interface DatabaseInitializerDetector extends Ordered { /** * Detect beans defined in the given {@code beanFactory} that initialize a @@ -52,4 +53,9 @@ public interface DatabaseInitializerDetector { Set dataSourceInitializerNames) { } + @Override + default int getOrder() { + return 0; + } + } 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 7d40abd552..c8fd265627 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 @@ -40,6 +40,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.mock.env.MockEnvironment; @@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify; * Tests for {@link DatabaseInitializationDependencyConfigurer}. * * @author Andy Wilkinson + * @author Phillip Webb */ class DatabaseInitializationDependencyConfigurerTests { @@ -64,7 +66,8 @@ class DatabaseInitializationDependencyConfigurerTests { @BeforeEach void resetMocks() { - reset(MockDatabaseInitializerDetector.instance, MockedDependsOnDatabaseInitializationDetector.instance); + reset(MockDatabaseInitializerDetector.instance, OrderedMockDatabaseInitializerDetector.instance, + MockedDependsOnDatabaseInitializationDetector.instance); } @Test @@ -103,6 +106,30 @@ class DatabaseInitializationDependencyConfigurerTests { }); } + @Test + void whenDependenciesAreConfiguredDetectedDatabaseInitializersAreInitializedInCorrectOrder() { + BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition(); + BeanDefinition bravo = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition(); + BeanDefinition charlie = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition(); + performDetection(Arrays.asList(MockDatabaseInitializerDetector.class, + OrderedMockDatabaseInitializerDetector.class, MockedDependsOnDatabaseInitializationDetector.class), + (context) -> { + given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("alpha")); + given(OrderedMockDatabaseInitializerDetector.instance.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("bravo")); + given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("charlie")); + context.registerBeanDefinition("alpha", alpha); + context.registerBeanDefinition("bravo", bravo); + context.registerBeanDefinition("charlie", charlie); + context.refresh(); + assertThat(charlie.getDependsOn()).containsExactly("alpha", "bravo"); + assertThat(bravo.getDependsOn()).containsExactly("alpha"); + assertThat(alpha.getDependsOn()).isNullOrEmpty(); + }); + } + private void performDetection(Collection> detectors, Consumer contextCallback) { DetectorSpringFactoriesClassLoader detectorSpringFactories = new DetectorSpringFactoriesClassLoader(this.temp); @@ -158,7 +185,7 @@ class DatabaseInitializationDependencyConfigurerTests { @Override public Set detect(ConfigurableListableBeanFactory beanFactory) { - return MockDatabaseInitializerDetector.instance.detect(beanFactory); + return instance.detect(beanFactory); } @Override @@ -169,6 +196,22 @@ class DatabaseInitializationDependencyConfigurerTests { } + static class OrderedMockDatabaseInitializerDetector implements DatabaseInitializerDetector { + + private static DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class); + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + return instance.detect(beanFactory); + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + } + static class MockedDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector { private static DependsOnDatabaseInitializationDetector instance = mock(