Improve auto-configuration sorting performance

Update `AutoConfigurationSorter` to improve performance by  building
reverse `@AutoConfigureBefore` relationships while the
auto-configuration graph is created and using stable sort ranks during
traversal.

See gh-51867

Signed-off-by: aalsanie <ahmad.alsanie@hotmail.com>
This commit is contained in:
aalsanie
2026-09-28 15:33:17 -07:00
committed by Phillip Webb
parent ab8d5266fb
commit a0c3aecc1a
2 changed files with 59 additions and 20 deletions
@@ -82,29 +82,34 @@ class AutoConfigurationSorter {
}
private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {
List<String> toSort = new ArrayList<>(classNames);
toSort.addAll(classes.getAllNames());
Set<String> requestedClassNames = new LinkedHashSet<>(classNames);
Map<String, Integer> sortOrder = new LinkedHashMap<>();
for (String className : classNames) {
sortOrder.putIfAbsent(className, sortOrder.size());
}
for (String className : classes.getAllNames()) {
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()) {
if (!sorted.contains(className)) {
doSortByAfterAnnotation(classes, sortOrder, sorted, processing, className);
}
}
sorted.retainAll(classNames);
sorted.retainAll(requestedClassNames);
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) {
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 (!sorted.contains(after) && sortOrder.containsKey(after)) {
doSortByAfterAnnotation(classes, sortOrder, sorted, processing, after);
}
}
processing.remove(current);
@@ -120,6 +125,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 +148,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 +169,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,