Skip to content

Commit 95a8fa5

Browse files
committed
Correctly order @AutoConfigureAfter values when sorting
Update `AutoConfigurationSorter` so that `getClassesRequestedAfter()` results are sorted to match the earlier name/order sorting. Prior to this commit the order of items added via `@AutoConfigureAfter` was in an undetermined order which could cause very subtle `@ConditionalOnBean` bugs. Thanks very much to Alexandre Baron for their help in diagnosing and reproducing this issue. Fixes gh-38904
1 parent 53528d3 commit 95a8fa5

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import java.util.ArrayList;
2121
import java.util.Collection;
2222
import java.util.Collections;
23-
import java.util.HashMap;
23+
import java.util.Comparator;
24+
import java.util.LinkedHashMap;
2425
import java.util.LinkedHashSet;
2526
import java.util.List;
2627
import java.util.Map;
2728
import java.util.Set;
29+
import java.util.TreeSet;
2830

2931
import org.springframework.core.type.AnnotationMetadata;
3032
import org.springframework.core.type.classreading.MetadataReader;
@@ -53,12 +55,14 @@ class AutoConfigurationSorter {
5355
}
5456

5557
List<String> getInPriorityOrder(Collection<String> classNames) {
58+
// Initially sort alphabetically
59+
List<String> alphabeticallyOrderedClassNames = new ArrayList<>(classNames);
60+
Collections.sort(alphabeticallyOrderedClassNames);
61+
// Then sort by order
5662
AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
57-
this.autoConfigurationMetadata, classNames);
63+
this.autoConfigurationMetadata, alphabeticallyOrderedClassNames);
5864
List<String> orderedClassNames = new ArrayList<>(classNames);
59-
// Initially sort alphabetically
6065
Collections.sort(orderedClassNames);
61-
// Then sort by order
6266
orderedClassNames.sort((o1, o2) -> {
6367
int i1 = classes.get(o1).getOrder();
6468
int i2 = classes.get(o2).getOrder();
@@ -87,7 +91,9 @@ private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<Stri
8791
current = toSort.remove(0);
8892
}
8993
processing.add(current);
90-
for (String after : classes.getClassesRequestedAfter(current)) {
94+
Set<String> afters = new TreeSet<>(Comparator.comparing(toSort::indexOf));
95+
afters.addAll(classes.getClassesRequestedAfter(current));
96+
for (String after : afters) {
9197
checkForCycles(processing, current, after);
9298
if (!sorted.contains(after) && toSort.contains(after)) {
9399
doSortByAfterAnnotation(classes, toSort, sorted, processing, after);
@@ -104,7 +110,7 @@ private void checkForCycles(Set<String> processing, String current, String after
104110

105111
private static class AutoConfigurationClasses {
106112

107-
private final Map<String, AutoConfigurationClass> classes = new HashMap<>();
113+
private final Map<String, AutoConfigurationClass> classes = new LinkedHashMap<>();
108114

109115
AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,
110116
AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* @author Phillip Webb
4747
* @author Andy Wilkinson
4848
* @author Moritz Halbritter
49+
* @author Alexandre Baron
4950
*/
5051
class AutoConfigurationSorterTests {
5152

@@ -206,6 +207,17 @@ void useAnnotationWithNoDirectLinkAndCycle() throws Exception {
206207
.withMessageContaining("AutoConfigure cycle detected");
207208
}
208209

210+
@Test // gh-38904
211+
void byBeforeAnnotationThenOrderAnnotation() {
212+
String oa = OrderAutoConfigureA.class.getName();
213+
String oa1 = OrderAutoConfigureASeedR1.class.getName();
214+
String oa2 = OrderAutoConfigureASeedY2.class.getName();
215+
String oa3 = OrderAutoConfigureASeedA3.class.getName();
216+
String oa4 = OrderAutoConfigureAutoConfigureASeedG4.class.getName();
217+
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(oa4, oa3, oa2, oa1, oa));
218+
assertThat(actual).containsExactly(oa1, oa2, oa3, oa4, oa);
219+
}
220+
209221
private AutoConfigurationMetadata getAutoConfigurationMetadata(String... classNames) throws Exception {
210222
Properties properties = new Properties();
211223
for (String className : classNames) {
@@ -348,6 +360,36 @@ static class AutoConfigureZ2 {
348360

349361
}
350362

363+
static class OrderAutoConfigureA {
364+
365+
}
366+
367+
// Use seeds in auto-configuration class names to mislead the sort by names done in
368+
// AutoConfigurationSorter class.
369+
@AutoConfigureBefore(OrderAutoConfigureA.class)
370+
@AutoConfigureOrder(1)
371+
static class OrderAutoConfigureASeedR1 {
372+
373+
}
374+
375+
@AutoConfigureBefore(OrderAutoConfigureA.class)
376+
@AutoConfigureOrder(2)
377+
static class OrderAutoConfigureASeedY2 {
378+
379+
}
380+
381+
@AutoConfigureBefore(OrderAutoConfigureA.class)
382+
@AutoConfigureOrder(3)
383+
static class OrderAutoConfigureASeedA3 {
384+
385+
}
386+
387+
@AutoConfigureBefore(OrderAutoConfigureA.class)
388+
@AutoConfigureOrder(4)
389+
static class OrderAutoConfigureAutoConfigureASeedG4 {
390+
391+
}
392+
351393
static class SkipCycleMetadataReaderFactory extends CachingMetadataReaderFactory {
352394

353395
@Override

0 commit comments

Comments
 (0)