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
This commit is contained in:
@@ -34,4 +34,9 @@ class FlywayMigrationInitializerDatabaseInitializerDetector extends AbstractBean
|
||||
return Collections.singleton(FlywayMigrationInitializer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> additional) {
|
||||
@@ -112,7 +122,7 @@ public class DatabaseInitializationDependencyConfigurer implements ImportBeanDef
|
||||
|
||||
private Set<String> detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) {
|
||||
List<DatabaseInitializerDetector> detectors = getDetectors(beanFactory, DatabaseInitializerDetector.class);
|
||||
Set<String> beanNames = new HashSet<>();
|
||||
Set<String> beanNames = new LinkedHashSet<>();
|
||||
for (DatabaseInitializerDetector detector : detectors) {
|
||||
for (String beanName : detector.detect(beanFactory)) {
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
|
||||
@@ -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<String> dataSourceInitializerNames) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Class<?>> detectors,
|
||||
Consumer<AnnotationConfigApplicationContext> contextCallback) {
|
||||
DetectorSpringFactoriesClassLoader detectorSpringFactories = new DetectorSpringFactoriesClassLoader(this.temp);
|
||||
@@ -158,7 +185,7 @@ class DatabaseInitializationDependencyConfigurerTests {
|
||||
|
||||
@Override
|
||||
public Set<String> 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<String> 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(
|
||||
|
||||
Reference in New Issue
Block a user