diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java index f44b05573b2..6de03997498 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationSorter.java @@ -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 sortByAnnotation(AutoConfigurationClasses classes, List classNames) { - List toSort = new ArrayList<>(classNames); - toSort.addAll(classes.getAllNames()); + private List sortByAnnotation(Set classNames, AutoConfigurationClasses classes) { + Map sortOrder = new LinkedHashMap<>(); + classNames.forEach((className) -> sortOrder.putIfAbsent(className, sortOrder.size())); + classes.getAllNames().forEach((className) -> sortOrder.putIfAbsent(className, sortOrder.size())); Set sorted = new LinkedHashSet<>(); Set 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 toSort, Set sorted, - Set processing, @Nullable String current) { - if (current == null) { - current = toSort.remove(0); + private void doSortByAfterAnnotation(AutoConfigurationClasses classes, Map sortOrder, + Set sorted, Set processing, String current) { + if (sorted.contains(current)) { + return; } processing.add(current); - Set afters = new TreeSet<>(Comparator.comparing(toSort::indexOf)); + Set 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 classes = new LinkedHashMap<>(); + private final Map> classesRequestedAfter = new LinkedHashMap<>(); + AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory, @Nullable AutoConfigurationMetadata autoConfigurationMetadata, Collection 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 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 getClassesRequestedAfter(String className) { Set 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; } diff --git a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java index fa6076dd703..ac47153e6ed 100644 --- a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java +++ b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java @@ -188,6 +188,38 @@ class AutoConfigurationSorterTests { assertThat(actual).containsExactly(B, A); } + @Test + void orderIsStableForLargerMixedGraph() { + List 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 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 actual = getInPriorityOrder(A, B, C, B, A, C); + assertThat(actual).containsExactly(C, B, A); + } + + @Test + void transitivelyDiscoveredClassesAreNotIncludedInResult() { + List 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,