Merge pull request #51867 from aalsanie

Closes gh-51867

* pr/51867:
  Polish 'Improve auto-configuration sorting performance'
  Improve auto-configuration sorting performance
This commit is contained in:
Phillip Webb
2026-09-28 15:36:11 -07:00
2 changed files with 55 additions and 20 deletions
@@ -77,34 +77,35 @@ class AutoConfigurationSorter {
return Integer.compare(i1, i2);
});
// Then respect @AutoConfigureBefore @AutoConfigureAfter
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
orderedClassNames = sortByAnnotation(new LinkedHashSet<>(orderedClassNames), classes);
return orderedClassNames;
}
private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {
List<String> toSort = new ArrayList<>(classNames);
toSort.addAll(classes.getAllNames());
private List<String> sortByAnnotation(Set<String> classNames, AutoConfigurationClasses classes) {
Map<String, Integer> sortOrder = new LinkedHashMap<>();
classNames.forEach((className) -> sortOrder.putIfAbsent(className, sortOrder.size()));
classes.getAllNames().forEach((className) -> sortOrder.putIfAbsent(className, sortOrder.size()));
Set<String> sorted = new LinkedHashSet<>();
Set<String> processing = new LinkedHashSet<>();
while (!toSort.isEmpty()) {
doSortByAfterAnnotation(classes, toSort, sorted, processing, null);
for (String className : sortOrder.keySet()) {
doSortByAfterAnnotation(classes, sortOrder, sorted, processing, className);
}
sorted.retainAll(classNames);
return new ArrayList<>(sorted);
}
private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, Set<String> sorted,
Set<String> processing, @Nullable String current) {
if (current == null) {
current = toSort.remove(0);
private void doSortByAfterAnnotation(AutoConfigurationClasses classes, Map<String, Integer> sortOrder,
Set<String> sorted, Set<String> processing, String current) {
if (sorted.contains(current)) {
return;
}
processing.add(current);
Set<String> afters = new TreeSet<>(Comparator.comparing(toSort::indexOf));
Set<String> afters = new TreeSet<>(Comparator.comparingInt((String name) -> sortOrder.getOrDefault(name, -1)));
afters.addAll(classes.getClassesRequestedAfter(current));
for (String after : afters) {
checkForCycles(processing, current, after);
if (!sorted.contains(after) && toSort.contains(after)) {
doSortByAfterAnnotation(classes, toSort, sorted, processing, after);
if (sortOrder.containsKey(after)) {
doSortByAfterAnnotation(classes, sortOrder, sorted, processing, after);
}
}
processing.remove(current);
@@ -120,6 +121,8 @@ class AutoConfigurationSorter {
private final Map<String, AutoConfigurationClass> classes = new LinkedHashMap<>();
private final Map<String, Set<String>> classesRequestedAfter = new LinkedHashMap<>();
AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,
@Nullable AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {
addToClasses(metadataReaderFactory, autoConfigurationMetadata, classNames, true);
@@ -141,8 +144,12 @@ class AutoConfigurationSorter {
this.classes.put(className, autoConfigurationClass);
}
if (available) {
addToClasses(metadataReaderFactory, autoConfigurationMetadata,
autoConfigurationClass.getBefore(), false);
Set<String> before = autoConfigurationClass.getBefore();
for (String name : before) {
this.classesRequestedAfter.computeIfAbsent(name, (key) -> new LinkedHashSet<>())
.add(className);
}
addToClasses(metadataReaderFactory, autoConfigurationMetadata, before, false);
addToClasses(metadataReaderFactory, autoConfigurationMetadata,
autoConfigurationClass.getAfter(), false);
}
@@ -158,11 +165,7 @@ class AutoConfigurationSorter {
Set<String> getClassesRequestedAfter(String className) {
Set<String> classesRequestedAfter = new LinkedHashSet<>(get(className).getAfter());
this.classes.forEach((name, autoConfigurationClass) -> {
if (autoConfigurationClass.getBefore().contains(className)) {
classesRequestedAfter.add(name);
}
});
classesRequestedAfter.addAll(this.classesRequestedAfter.getOrDefault(className, Collections.emptySet()));
return classesRequestedAfter;
}
@@ -188,6 +188,38 @@ class AutoConfigurationSorterTests {
assertThat(actual).containsExactly(B, A);
}
@Test
void orderIsStableForLargerMixedGraph() {
List<String> actual = getInPriorityOrder(LOWEST, HIGHEST, DEFAULT, A, A2, B, C, E, W, W2, X, Y, Z);
assertThat(actual).containsExactly(HIGHEST, C, E, W, W2, B, A, A2, Z, Y, X, DEFAULT, LOWEST);
}
@Test
void orderIsStableForLargerMixedGraphWithReversedInput() {
List<String> actual = getInPriorityOrder(Z, Y, X, W2, W, E, C, B, A2, A, DEFAULT, HIGHEST, LOWEST);
assertThat(actual).containsExactly(HIGHEST, C, E, W, W2, B, A, A2, Z, Y, X, DEFAULT, LOWEST);
}
@Test
void duplicateInputDoesNotChangeResult() {
List<String> actual = getInPriorityOrder(A, B, C, B, A, C);
assertThat(actual).containsExactly(C, B, A);
}
@Test
void transitivelyDiscoveredClassesAreNotIncludedInResult() {
List<String> actual = getInPriorityOrder(A, C);
assertThat(actual).containsExactly(C, A);
}
@Test
void cycleReportsSameClasses() {
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(), this.autoConfigurationMetadata,
REPLACEMENT_MAPPER);
assertThatIllegalStateException().isThrownBy(() -> getInPriorityOrder(A, B, C, D))
.withMessage("AutoConfigure cycle detected between " + D + " and " + A);
}
@Test
void byAutoConfigureAfterWithCycle() {
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory(), this.autoConfigurationMetadata,