Skip to content

Commit 9d2cb16

Browse files
committed
Configure initializer dependencies grouped by detector
Previously, database initializers were detected and were configured with dependencies based on their detection order. For example, if detectors a, b, and c detected initializers a1, b1, b2, and c1, c1 would depend on b2, b2 on b1, and b1 on a1: ------ ------ ------ ------ | c1 | --> | b2 | --> | b1 | --> | a1 | ------ ------ ------ ------ This could cause a dependency cycle in certain situations, for example because the user had already configured b1 to depend on b2. This commit reduces the risk of a cycle being created by batching the initializers by their detector, with dependencies being configured between each batch rather than between every initializer. In the example above, this results in c1 depending on b1 and b2, and b1 and b2 depending on a1: ------ ------ | b1 | ------ | c1 | --> | | --> | a1 | ------ | b2 | ------ ------ As b1 and b2 were detected by the same detector, no dependency between those initializers is defined. Closes gh-27131
1 parent cfed99d commit 9d2cb16

File tree

2 files changed

+86
-30
lines changed

2 files changed

+86
-30
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.java

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import java.util.Collection;
2121
import java.util.Collections;
2222
import java.util.HashSet;
23+
import java.util.LinkedHashMap;
2324
import java.util.LinkedHashSet;
2425
import java.util.List;
26+
import java.util.Map;
2527
import java.util.Set;
2628

2729
import org.springframework.beans.factory.BeanFactory;
@@ -38,6 +40,7 @@
3840
import org.springframework.core.env.Environment;
3941
import org.springframework.core.io.support.SpringFactoriesLoader;
4042
import org.springframework.core.type.AnnotationMetadata;
43+
import org.springframework.util.CollectionUtils;
4144
import org.springframework.util.StringUtils;
4245

4346
/**
@@ -100,48 +103,49 @@ public int getOrder() {
100103

101104
@Override
102105
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
103-
Set<String> initializerBeanNames = detectInitializerBeanNames(beanFactory);
106+
InitializerBeanNames initializerBeanNames = detectInitializerBeanNames(beanFactory);
104107
if (initializerBeanNames.isEmpty()) {
105108
return;
106109
}
107-
String previousInitializerBeanName = null;
108-
for (String initializerBeanName : initializerBeanNames) {
109-
BeanDefinition beanDefinition = getBeanDefinition(initializerBeanName, beanFactory);
110-
beanDefinition.setDependsOn(merge(beanDefinition.getDependsOn(), previousInitializerBeanName));
111-
previousInitializerBeanName = initializerBeanName;
110+
Set<String> previousInitializerBeanNamesBatch = null;
111+
for (Set<String> initializerBeanNamesBatch : initializerBeanNames.batchedBeanNames()) {
112+
for (String initializerBeanName : initializerBeanNamesBatch) {
113+
BeanDefinition beanDefinition = getBeanDefinition(initializerBeanName, beanFactory);
114+
beanDefinition
115+
.setDependsOn(merge(beanDefinition.getDependsOn(), previousInitializerBeanNamesBatch));
116+
}
117+
previousInitializerBeanNamesBatch = initializerBeanNamesBatch;
112118
}
113119
for (String dependsOnInitializationBeanNames : detectDependsOnInitializationBeanNames(beanFactory)) {
114120
BeanDefinition beanDefinition = getBeanDefinition(dependsOnInitializationBeanNames, beanFactory);
115-
beanDefinition.setDependsOn(merge(beanDefinition.getDependsOn(), initializerBeanNames));
121+
beanDefinition.setDependsOn(merge(beanDefinition.getDependsOn(), initializerBeanNames.beanNames()));
116122
}
117123
}
118124

119-
private String[] merge(String[] source, String additional) {
120-
return merge(source, (additional != null) ? Collections.singleton(additional) : Collections.emptySet());
121-
}
122-
123125
private String[] merge(String[] source, Set<String> additional) {
126+
if (CollectionUtils.isEmpty(additional)) {
127+
return source;
128+
}
124129
Set<String> result = new LinkedHashSet<>((source != null) ? Arrays.asList(source) : Collections.emptySet());
125130
result.addAll(additional);
126131
return StringUtils.toStringArray(result);
127132
}
128133

129-
private Set<String> detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) {
134+
private InitializerBeanNames detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) {
130135
List<DatabaseInitializerDetector> detectors = getDetectors(beanFactory, DatabaseInitializerDetector.class);
131-
Set<String> beanNames = new LinkedHashSet<>();
136+
InitializerBeanNames initializerBeanNames = new InitializerBeanNames();
132137
for (DatabaseInitializerDetector detector : detectors) {
133138
for (String beanName : detector.detect(beanFactory)) {
134139
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
135140
beanDefinition.setAttribute(DatabaseInitializerDetector.class.getName(),
136141
detector.getClass().getName());
137-
beanNames.add(beanName);
142+
initializerBeanNames.detected(detector, beanName);
138143
}
139144
}
140-
beanNames = Collections.unmodifiableSet(beanNames);
141145
for (DatabaseInitializerDetector detector : detectors) {
142-
detector.detectionComplete(beanFactory, beanNames);
146+
detector.detectionComplete(beanFactory, initializerBeanNames.beanNames());
143147
}
144-
return beanNames;
148+
return initializerBeanNames;
145149
}
146150

147151
private Collection<String> detectDependsOnInitializationBeanNames(ConfigurableListableBeanFactory beanFactory) {
@@ -174,6 +178,31 @@ private static BeanDefinition getBeanDefinition(String beanName, ConfigurableLis
174178
}
175179
}
176180

181+
static class InitializerBeanNames {
182+
183+
private final Map<DatabaseInitializerDetector, Set<String>> byDetectorBeanNames = new LinkedHashMap<>();
184+
185+
private final Set<String> beanNames = new LinkedHashSet<>();
186+
187+
private void detected(DatabaseInitializerDetector detector, String beanName) {
188+
this.byDetectorBeanNames.computeIfAbsent(detector, (key) -> new LinkedHashSet<>()).add(beanName);
189+
this.beanNames.add(beanName);
190+
}
191+
192+
private boolean isEmpty() {
193+
return this.beanNames.isEmpty();
194+
}
195+
196+
private Iterable<Set<String>> batchedBeanNames() {
197+
return this.byDetectorBeanNames.values();
198+
}
199+
200+
private Set<String> beanNames() {
201+
return Collections.unmodifiableSet(this.beanNames);
202+
}
203+
204+
}
205+
177206
}
178207

179208
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurerTests.java

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Enumeration;
2727
import java.util.HashMap;
2828
import java.util.HashSet;
29+
import java.util.LinkedHashSet;
2930
import java.util.List;
3031
import java.util.Map;
3132
import java.util.Properties;
@@ -71,7 +72,8 @@ class DatabaseInitializationDependencyConfigurerTests {
7172

7273
@BeforeEach
7374
void resetMocks() {
74-
reset(MockDatabaseInitializerDetector.instance, OrderedMockDatabaseInitializerDetector.instance,
75+
reset(MockDatabaseInitializerDetector.instance, OrderedNearLowestMockDatabaseInitializerDetector.instance,
76+
OrderedLowestMockDatabaseInitializerDetector.instance,
7577
MockedDependsOnDatabaseInitializationDetector.instance);
7678
}
7779

@@ -94,8 +96,7 @@ void beanFactoryPostProcessorHasOrderAllowingSubsequentPostProcessorsToFineTuneD
9496
context.refresh();
9597
assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("bravo",
9698
(dependencies) -> assertThat(dependencies).containsExactly("alpha"));
97-
assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("alpha",
98-
(dependencies) -> assertThat(dependencies).isEmpty());
99+
assertThat(DependsOnCaptor.dependsOn).doesNotContainKey("alpha");
99100
});
100101
}
101102

@@ -140,24 +141,34 @@ void whenDependenciesAreConfiguredThenBeansThatDependUponDatabaseInitializationD
140141
@Test
141142
void whenDependenciesAreConfiguredDetectedDatabaseInitializersAreInitializedInCorrectOrder() {
142143
BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
143-
BeanDefinition bravo = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
144+
BeanDefinition bravo1 = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
145+
BeanDefinition bravo2 = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
144146
BeanDefinition charlie = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
145-
performDetection(Arrays.asList(MockDatabaseInitializerDetector.class,
146-
OrderedMockDatabaseInitializerDetector.class, MockedDependsOnDatabaseInitializationDetector.class),
147+
BeanDefinition delta = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
148+
performDetection(
149+
Arrays.asList(MockDatabaseInitializerDetector.class, OrderedLowestMockDatabaseInitializerDetector.class,
150+
OrderedNearLowestMockDatabaseInitializerDetector.class,
151+
MockedDependsOnDatabaseInitializationDetector.class),
147152
(context) -> {
148153
given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
149154
.willReturn(Collections.singleton("alpha"));
150-
given(OrderedMockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
151-
.willReturn(Collections.singleton("bravo"));
155+
given(OrderedNearLowestMockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
156+
.willReturn(new LinkedHashSet<>(Arrays.asList("bravo1", "bravo2")));
157+
given(OrderedLowestMockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
158+
.willReturn(new LinkedHashSet<>(Arrays.asList("charlie")));
152159
given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory()))
153-
.willReturn(Collections.singleton("charlie"));
160+
.willReturn(Collections.singleton("delta"));
154161
context.registerBeanDefinition("alpha", alpha);
155-
context.registerBeanDefinition("bravo", bravo);
162+
context.registerBeanDefinition("bravo1", bravo1);
163+
context.registerBeanDefinition("bravo2", bravo2);
156164
context.registerBeanDefinition("charlie", charlie);
165+
context.registerBeanDefinition("delta", delta);
157166
context.register(DependencyConfigurerConfiguration.class);
158167
context.refresh();
159-
assertThat(charlie.getDependsOn()).containsExactly("alpha", "bravo");
160-
assertThat(bravo.getDependsOn()).containsExactly("alpha");
168+
assertThat(delta.getDependsOn()).containsExactlyInAnyOrder("alpha", "bravo1", "bravo2", "charlie");
169+
assertThat(charlie.getDependsOn()).containsExactly("bravo1", "bravo2");
170+
assertThat(bravo1.getDependsOn()).containsExactly("alpha");
171+
assertThat(bravo2.getDependsOn()).containsExactly("alpha");
161172
assertThat(alpha.getDependsOn()).isNullOrEmpty();
162173
});
163174
}
@@ -227,7 +238,7 @@ public void detectionComplete(ConfigurableListableBeanFactory beanFactory,
227238

228239
}
229240

230-
static class OrderedMockDatabaseInitializerDetector implements DatabaseInitializerDetector {
241+
static class OrderedLowestMockDatabaseInitializerDetector implements DatabaseInitializerDetector {
231242

232243
private static DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class);
233244

@@ -243,6 +254,22 @@ public int getOrder() {
243254

244255
}
245256

257+
static class OrderedNearLowestMockDatabaseInitializerDetector implements DatabaseInitializerDetector {
258+
259+
private static DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class);
260+
261+
@Override
262+
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
263+
return instance.detect(beanFactory);
264+
}
265+
266+
@Override
267+
public int getOrder() {
268+
return Ordered.LOWEST_PRECEDENCE - 100;
269+
}
270+
271+
}
272+
246273
static class MockedDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector {
247274

248275
private static DependsOnDatabaseInitializationDetector instance = mock(

0 commit comments

Comments
 (0)