Commit dafd5112 authored by Phillip Webb's avatar Phillip Webb

Support DatabaseInitializerDetector ordering

Update `DatabaseInitializationDependencyConfigurer` so that depends-on
ordering is applied based on the `DatabaseInitializerDetector` order.

Prior to this commit, if multiple DatabaseInitializer beans were
detected the order in which they were initialized was not defined.

See gh-26692
parent fba5ffc6
...@@ -34,4 +34,9 @@ class FlywayMigrationInitializerDatabaseInitializerDetector extends AbstractBean ...@@ -34,4 +34,9 @@ class FlywayMigrationInitializerDatabaseInitializerDetector extends AbstractBean
return Collections.singleton(FlywayMigrationInitializer.class); return Collections.singleton(FlywayMigrationInitializer.class);
} }
@Override
public int getOrder() {
return 1;
}
} }
...@@ -98,12 +98,22 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef ...@@ -98,12 +98,22 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef
if (initializerBeanNames.isEmpty()) { if (initializerBeanNames.isEmpty()) {
return; return;
} }
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)) { for (String dependsOnInitializationBeanNames : detectDependsOnInitializationBeanNames(beanFactory)) {
BeanDefinition definition = getBeanDefinition(dependsOnInitializationBeanNames, beanFactory); BeanDefinition beanDefinition = getBeanDefinition(dependsOnInitializationBeanNames, beanFactory);
definition.setDependsOn(merge(definition.getDependsOn(), initializerBeanNames)); 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<String> additional) { private String[] merge(String[] source, Set<String> additional) {
Set<String> result = new LinkedHashSet<>((source != null) ? Arrays.asList(source) : Collections.emptySet()); Set<String> result = new LinkedHashSet<>((source != null) ? Arrays.asList(source) : Collections.emptySet());
result.addAll(additional); result.addAll(additional);
...@@ -112,7 +122,7 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef ...@@ -112,7 +122,7 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef
private Set<String> detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) { private Set<String> detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) {
List<DatabaseInitializerDetector> detectors = getDetectors(beanFactory, DatabaseInitializerDetector.class); List<DatabaseInitializerDetector> detectors = getDetectors(beanFactory, DatabaseInitializerDetector.class);
Set<String> beanNames = new HashSet<>(); Set<String> beanNames = new LinkedHashSet<>();
for (DatabaseInitializerDetector detector : detectors) { for (DatabaseInitializerDetector detector : detectors) {
for (String beanName : detector.detect(beanFactory)) { for (String beanName : detector.detect(beanFactory)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
......
...@@ -21,6 +21,7 @@ import java.util.Set; ...@@ -21,6 +21,7 @@ import java.util.Set;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;
/** /**
* Detects beans that initialize an SQL database. Implementations should be registered in * Detects beans that initialize an SQL database. Implementations should be registered in
...@@ -30,7 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; ...@@ -30,7 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.5.0 * @since 2.5.0
*/ */
public interface DatabaseInitializerDetector { public interface DatabaseInitializerDetector extends Ordered {
/** /**
* Detect beans defined in the given {@code beanFactory} that initialize a * Detect beans defined in the given {@code beanFactory} that initialize a
...@@ -52,4 +53,9 @@ public interface DatabaseInitializerDetector { ...@@ -52,4 +53,9 @@ public interface DatabaseInitializerDetector {
Set<String> dataSourceInitializerNames) { Set<String> dataSourceInitializerNames) {
} }
@Override
default int getOrder() {
return 0;
}
} }
...@@ -40,6 +40,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; ...@@ -40,6 +40,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
...@@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify; ...@@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify;
* Tests for {@link DatabaseInitializationDependencyConfigurer}. * Tests for {@link DatabaseInitializationDependencyConfigurer}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
*/ */
class DatabaseInitializationDependencyConfigurerTests { class DatabaseInitializationDependencyConfigurerTests {
...@@ -64,7 +66,8 @@ class DatabaseInitializationDependencyConfigurerTests { ...@@ -64,7 +66,8 @@ class DatabaseInitializationDependencyConfigurerTests {
@BeforeEach @BeforeEach
void resetMocks() { void resetMocks() {
reset(MockDatabaseInitializerDetector.instance, MockedDependsOnDatabaseInitializationDetector.instance); reset(MockDatabaseInitializerDetector.instance, OrderedMockDatabaseInitializerDetector.instance,
MockedDependsOnDatabaseInitializationDetector.instance);
} }
@Test @Test
...@@ -103,6 +106,30 @@ class DatabaseInitializationDependencyConfigurerTests { ...@@ -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<Class<?>> detectors, private void performDetection(Collection<Class<?>> detectors,
Consumer<AnnotationConfigApplicationContext> contextCallback) { Consumer<AnnotationConfigApplicationContext> contextCallback) {
DetectorSpringFactoriesClassLoader detectorSpringFactories = new DetectorSpringFactoriesClassLoader(this.temp); DetectorSpringFactoriesClassLoader detectorSpringFactories = new DetectorSpringFactoriesClassLoader(this.temp);
...@@ -158,7 +185,7 @@ class DatabaseInitializationDependencyConfigurerTests { ...@@ -158,7 +185,7 @@ class DatabaseInitializationDependencyConfigurerTests {
@Override @Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) { public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
return MockDatabaseInitializerDetector.instance.detect(beanFactory); return instance.detect(beanFactory);
} }
@Override @Override
...@@ -169,6 +196,22 @@ class DatabaseInitializationDependencyConfigurerTests { ...@@ -169,6 +196,22 @@ class DatabaseInitializationDependencyConfigurerTests {
} }
static class OrderedMockDatabaseInitializerDetector implements DatabaseInitializerDetector {
private static DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class);
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
return instance.detect(beanFactory);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
static class MockedDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector { static class MockedDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector {
private static DependsOnDatabaseInitializationDetector instance = mock( private static DependsOnDatabaseInitializationDetector instance = mock(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment