Merge 7bff887a98
into 7e6874ad80
This commit is contained in:
commit
a9ed304ea0
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a cyclic ordering dependency is detected.
|
||||||
|
*
|
||||||
|
* @author Yongjun Hong
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CyclicOrderException extends RuntimeException {
|
||||||
|
|
||||||
|
private final List<Object> cycle;
|
||||||
|
|
||||||
|
public CyclicOrderException(String message, List<Object> cycle) {
|
||||||
|
super(buildDetailedMessage(message, cycle));
|
||||||
|
this.cycle = cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildDetailedMessage(String message, List<Object> cycle) {
|
||||||
|
String cycleDescription = cycle.stream()
|
||||||
|
.map(obj -> (obj instanceof Class) ? ((Class<?>) obj).getSimpleName() : obj.getClass().getSimpleName())
|
||||||
|
.collect(Collectors.joining(" -> "));
|
||||||
|
|
||||||
|
return message + ". Detected cycle: " + cycleDescription + " -> " +
|
||||||
|
cycle.get(0).getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getCycle() {
|
||||||
|
return this.cycle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the annotated component should be ordered after the specified target classes.
|
||||||
|
*
|
||||||
|
* <p>This annotation is an extension of the {@code @AutoConfigureAfter} pattern from Spring Boot,
|
||||||
|
* adapted for use in the core Spring framework. It allows developers to specify that a component
|
||||||
|
* must be initialized or processed after certain other components.</p>
|
||||||
|
*
|
||||||
|
* <p>For example, if class A depends on class B being initialized first, class A can be annotated
|
||||||
|
* with {@code @DependsOnAfter(B.class)} to enforce this order.</p>
|
||||||
|
*
|
||||||
|
* <p>This annotation is particularly useful in scenarios where the initialization order of
|
||||||
|
* components affects application behavior, and topological sorting is required to resolve
|
||||||
|
* dependencies.</p>
|
||||||
|
*
|
||||||
|
* @author Yongjun Hong
|
||||||
|
*/
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface DependsOnAfter {
|
||||||
|
/**
|
||||||
|
* The target classes after which this component should be ordered.
|
||||||
|
*/
|
||||||
|
Class<?>[] value() default {};
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the annotated component should be ordered before the specified target classes.
|
||||||
|
*
|
||||||
|
* <p>This annotation extends the {@code @AutoConfigureBefore} pattern from Spring Boot
|
||||||
|
* to the core Spring framework, allowing for fine-grained control over the initialization
|
||||||
|
* or processing order of components.</p>
|
||||||
|
*
|
||||||
|
* <p>For example, if class A must be initialized before class B, you can annotate class A
|
||||||
|
* with {@code @DependsOnBefore(B.class)}.</p>
|
||||||
|
*
|
||||||
|
* <p>This annotation is primarily used in dependency management scenarios where
|
||||||
|
* topological sorting is required to determine the correct order of execution.</p>
|
||||||
|
*
|
||||||
|
* @author Yongjun Hong
|
||||||
|
*/
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface DependsOnBefore {
|
||||||
|
/**
|
||||||
|
* The target classes before which this component should be ordered.
|
||||||
|
*/
|
||||||
|
Class<?>[] value() default {};
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A directed graph to represent relative ordering relationships.
|
||||||
|
*
|
||||||
|
* @author Yongjun Hong
|
||||||
|
*/
|
||||||
|
public class OrderGraph {
|
||||||
|
|
||||||
|
private final Map<Object, Set<Object>> adjacencyList = new HashMap<>();
|
||||||
|
private final Map<Object, Integer> inDegree = new HashMap<>();
|
||||||
|
private final Set<Object> allNodes = new HashSet<>();
|
||||||
|
|
||||||
|
public void addNode(Object node) {
|
||||||
|
this.allNodes.add(node);
|
||||||
|
this.adjacencyList.putIfAbsent(node, new HashSet<>());
|
||||||
|
this.inDegree.putIfAbsent(node, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an edge indicating that 'from' must be ordered before 'to'.
|
||||||
|
*/
|
||||||
|
public void addEdge(Object from, Object to) {
|
||||||
|
addNode(from);
|
||||||
|
addNode(to);
|
||||||
|
|
||||||
|
Set<Object> neighbors = this.adjacencyList.get(from);
|
||||||
|
if (neighbors != null && neighbors.add(to)) {
|
||||||
|
this.inDegree.put(to, this.inDegree.getOrDefault(to, 0) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a topological sort using Kahn's algorithm.
|
||||||
|
*/
|
||||||
|
public List<Object> topologicalSort() {
|
||||||
|
Map<Object, Integer> tempInDegree = new HashMap<>(this.inDegree);
|
||||||
|
Queue<Object> queue = new LinkedList<>();
|
||||||
|
List<Object> result = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Object node : this.allNodes) {
|
||||||
|
if (tempInDegree.getOrDefault(node, 0) == 0) {
|
||||||
|
queue.offer(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
Object current = queue.poll();
|
||||||
|
result.add(current);
|
||||||
|
|
||||||
|
for (Object neighbor : this.adjacencyList.getOrDefault(current, Collections.emptySet())) {
|
||||||
|
tempInDegree.put(neighbor, tempInDegree.getOrDefault(neighbor, 0) - 1);
|
||||||
|
if (tempInDegree.get(neighbor) == 0) {
|
||||||
|
queue.offer(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.size() != this.allNodes.size()) {
|
||||||
|
List<Object> cycle = detectCycle();
|
||||||
|
throw new CyclicOrderException("Circular ordering dependency detected", cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects a cycle in the graph using Depth-First Search (DFS).
|
||||||
|
*/
|
||||||
|
public List<Object> detectCycle() {
|
||||||
|
Set<Object> visited = new HashSet<>();
|
||||||
|
Set<Object> recursionStack = new HashSet<>();
|
||||||
|
Map<Object, Object> parent = new HashMap<>();
|
||||||
|
|
||||||
|
for (Object node : this.allNodes) {
|
||||||
|
if (!visited.contains(node)) {
|
||||||
|
List<Object> cycle = dfsDetectCycle(node, visited, recursionStack, parent);
|
||||||
|
if (!cycle.isEmpty()) {
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object> dfsDetectCycle(Object node, Set<Object> visited,
|
||||||
|
Set<Object> recursionStack, Map<Object, Object> parent) {
|
||||||
|
visited.add(node);
|
||||||
|
recursionStack.add(node);
|
||||||
|
|
||||||
|
for (Object neighbor : this.adjacencyList.getOrDefault(node, Collections.emptySet())) {
|
||||||
|
if (neighbor != null) {
|
||||||
|
parent.put(neighbor, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visited.contains(neighbor)) {
|
||||||
|
List<Object> cycle = dfsDetectCycle(neighbor, visited, recursionStack, parent);
|
||||||
|
if (!cycle.isEmpty()) {
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (recursionStack.contains(neighbor)) {
|
||||||
|
// Cycle detected - build the cycle path for the exception message
|
||||||
|
return buildCyclePath(neighbor, node, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recursionStack.remove(node);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object> buildCyclePath(Object cycleStart, Object current, Map<Object, Object> parent) {
|
||||||
|
List<Object> cycle = new ArrayList<>();
|
||||||
|
Object node = current;
|
||||||
|
|
||||||
|
while (node != null && !node.equals(cycleStart)) {
|
||||||
|
cycle.add(node);
|
||||||
|
node = parent.get(node);
|
||||||
|
}
|
||||||
|
if (node != null) {
|
||||||
|
cycle.add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(cycle);
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A processor that sorts a list of components based on @Order, @DependsOnBefore, and @DependsOnAfter annotations.
|
||||||
|
* This design separates the preparation phase (graph building and topological sort)
|
||||||
|
* from the comparison phase, adhering to the standard Comparator contract.
|
||||||
|
*
|
||||||
|
* @author Yongjun Hong
|
||||||
|
*/
|
||||||
|
public final class RelativeOrderProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hared default instance of {@code RelativeOrderProcessor}.
|
||||||
|
*/
|
||||||
|
public static final RelativeOrderProcessor INSTANCE = new RelativeOrderProcessor();
|
||||||
|
|
||||||
|
private static final TopologicalOrderSolver TOPOLOGICAL_ORDER_SOLVER = new TopologicalOrderSolver();
|
||||||
|
|
||||||
|
private RelativeOrderProcessor() {
|
||||||
|
// Private constructor to prevent instantiation
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the given list of objects in place.
|
||||||
|
* @param list the list to be sorted.
|
||||||
|
*/
|
||||||
|
public static void sort(List<?> list) {
|
||||||
|
if (list.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort(getComparatorFor(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a comparator tailored to the specific components in the input list.
|
||||||
|
* It pre-calculates the topological order for components with relative ordering needs.
|
||||||
|
* @param items the collection of items to create a comparator for.
|
||||||
|
* @return a fully configured Comparator.
|
||||||
|
*/
|
||||||
|
private static Comparator<Object> getComparatorFor(Collection<?> items) {
|
||||||
|
List<Object> components = new ArrayList<>(items);
|
||||||
|
|
||||||
|
Map<Object, Integer> orderMap = new HashMap<>();
|
||||||
|
if (!components.isEmpty()) {
|
||||||
|
List<Object> sortedRelative = TOPOLOGICAL_ORDER_SOLVER.resolveOrder(components);
|
||||||
|
for (int i = 0; i < sortedRelative.size(); i++) {
|
||||||
|
orderMap.put(sortedRelative.get(i), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConfiguredComparator(orderMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasRelativeOrderAnnotations(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
|
||||||
|
return AnnotationUtils.findAnnotation(clazz, DependsOnBefore.class) != null ||
|
||||||
|
AnnotationUtils.findAnnotation(clazz, DependsOnAfter.class) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual comparator implementation. It uses a pre-computed order map for relative
|
||||||
|
* components and falls back to AnnotationAwareOrderComparator for everything else.
|
||||||
|
*/
|
||||||
|
private static class ConfiguredComparator implements Comparator<Object> {
|
||||||
|
private final Map<Object, Integer> orderMap;
|
||||||
|
private final Comparator<Object> fallbackComparator = AnnotationAwareOrderComparator.INSTANCE;
|
||||||
|
|
||||||
|
public ConfiguredComparator(Map<Object, Integer> orderMap) {
|
||||||
|
this.orderMap = orderMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Object o1, Object o2) {
|
||||||
|
boolean o1isRelative = hasRelativeOrderAnnotations(o1);
|
||||||
|
boolean o2isRelative = hasRelativeOrderAnnotations(o2);
|
||||||
|
|
||||||
|
// Case 1: Either components have relative order. Compare their topological index.
|
||||||
|
if (o1isRelative || o2isRelative) {
|
||||||
|
int order1 = this.orderMap.getOrDefault(o1, 0);
|
||||||
|
int order2 = this.orderMap.getOrDefault(o2, 0);
|
||||||
|
return Integer.compare(order1, order2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: One is relative, the other is absolute, or both are absolute.
|
||||||
|
// Use the fallback comparator (@Order, @Priority) for tie-breaking.
|
||||||
|
return this.fallbackComparator.compare(o1, o2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.core;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves relative ordering relationships using a topological sort.
|
||||||
|
*
|
||||||
|
* @author Yongjun Hong
|
||||||
|
*/
|
||||||
|
public class TopologicalOrderSolver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the relative order of the given components and returns a sorted list.
|
||||||
|
*/
|
||||||
|
public List<Object> resolveOrder(List<Object> components) {
|
||||||
|
if (components.size() <= 1) {
|
||||||
|
return new ArrayList<>(components);
|
||||||
|
}
|
||||||
|
OrderGraph graph = buildOrderGraph(components);
|
||||||
|
return graph.topologicalSort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the ordering relationship graph from the given components.
|
||||||
|
*/
|
||||||
|
private OrderGraph buildOrderGraph(List<Object> components) {
|
||||||
|
OrderGraph graph = new OrderGraph();
|
||||||
|
|
||||||
|
for (Object component : components) {
|
||||||
|
graph.addNode(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object component : components) {
|
||||||
|
addOrderConstraints(graph, component);
|
||||||
|
}
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addOrderConstraints(OrderGraph graph, Object component) {
|
||||||
|
if (component == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Class<?> componentClass = (component instanceof Class) ? (Class<?>) component : component.getClass();
|
||||||
|
|
||||||
|
// Process @DependsOnBefore
|
||||||
|
DependsOnBefore dependsOnBefore = AnnotationUtils.findAnnotation(componentClass, DependsOnBefore.class);
|
||||||
|
if (dependsOnBefore != null) {
|
||||||
|
for (Class<?> beforeClass : dependsOnBefore.value()) {
|
||||||
|
graph.addEdge(component, beforeClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process @DependsOnAfter
|
||||||
|
DependsOnAfter dependsOnAfter = AnnotationUtils.findAnnotation(componentClass, DependsOnAfter.class);
|
||||||
|
if (dependsOnAfter != null) {
|
||||||
|
for (Class<?> afterClass : dependsOnAfter.value()) {
|
||||||
|
graph.addEdge(afterClass, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.DecoratingProxy;
|
import org.springframework.core.DecoratingProxy;
|
||||||
import org.springframework.core.OrderComparator;
|
import org.springframework.core.OrderComparator;
|
||||||
|
import org.springframework.core.RelativeOrderProcessor;
|
||||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +41,7 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Yongjun Hong
|
||||||
* @since 2.0.1
|
* @since 2.0.1
|
||||||
* @see org.springframework.core.Ordered
|
* @see org.springframework.core.Ordered
|
||||||
* @see org.springframework.core.annotation.Order
|
* @see org.springframework.core.annotation.Order
|
||||||
|
@ -105,9 +107,10 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
|
||||||
* @see java.util.List#sort(java.util.Comparator)
|
* @see java.util.List#sort(java.util.Comparator)
|
||||||
*/
|
*/
|
||||||
public static void sort(List<?> list) {
|
public static void sort(List<?> list) {
|
||||||
if (list.size() > 1) {
|
if (list.size() <= 1) {
|
||||||
list.sort(INSTANCE);
|
return;
|
||||||
}
|
}
|
||||||
|
RelativeOrderProcessor.sort(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,9 +121,10 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
|
||||||
* @see java.util.Arrays#sort(Object[], java.util.Comparator)
|
* @see java.util.Arrays#sort(Object[], java.util.Comparator)
|
||||||
*/
|
*/
|
||||||
public static void sort(Object[] array) {
|
public static void sort(Object[] array) {
|
||||||
if (array.length > 1) {
|
if (array.length <= 1) {
|
||||||
Arrays.sort(array, INSTANCE);
|
return;
|
||||||
}
|
}
|
||||||
|
RelativeOrderProcessor.sort(Arrays.asList(array));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,11 +22,17 @@ import java.util.List;
|
||||||
import jakarta.annotation.Priority;
|
import jakarta.annotation.Priority;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.CyclicOrderException;
|
||||||
|
import org.springframework.core.DependsOnAfter;
|
||||||
|
import org.springframework.core.DependsOnBefore;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Yongjun Hong
|
||||||
*/
|
*/
|
||||||
class AnnotationAwareOrderComparatorTests {
|
class AnnotationAwareOrderComparatorTests {
|
||||||
|
|
||||||
|
@ -100,6 +106,44 @@ class AnnotationAwareOrderComparatorTests {
|
||||||
assertThat(list).containsExactly(A.class, B.class, null, null);
|
assertThat(list).containsExactly(A.class, B.class, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sortWithDependsOnBefore() {
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
list.add(A.class);
|
||||||
|
list.add(D.class);
|
||||||
|
AnnotationAwareOrderComparator.sort(list);
|
||||||
|
assertThat(list).containsExactly(D.class, A.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sortWithDependsOnAfter() {
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
list.add(B.class);
|
||||||
|
list.add(E.class);
|
||||||
|
AnnotationAwareOrderComparator.sort(list);
|
||||||
|
assertThat(list).containsExactly(B.class, E.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sortWithDependsOnBeforeAndAfter() {
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
list.add(A.class);
|
||||||
|
list.add(B.class);
|
||||||
|
list.add(D.class);
|
||||||
|
list.add(E.class);
|
||||||
|
AnnotationAwareOrderComparator.sort(list);
|
||||||
|
assertThat(list).containsExactly(D.class, A.class, B.class, E.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sortWithCircularDependsOn() {
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
list.add(F.class);
|
||||||
|
list.add(G.class);
|
||||||
|
assertThatThrownBy(() -> AnnotationAwareOrderComparator.sort(list))
|
||||||
|
.isInstanceOf(CyclicOrderException.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Order(1)
|
@Order(1)
|
||||||
private static class A {
|
private static class A {
|
||||||
}
|
}
|
||||||
|
@ -119,4 +163,19 @@ class AnnotationAwareOrderComparatorTests {
|
||||||
private static class B2 {
|
private static class B2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DependsOnBefore(A.class)
|
||||||
|
private static class D {
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependsOnAfter(B.class)
|
||||||
|
private static class E {
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependsOnBefore(G.class)
|
||||||
|
private static class F {
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependsOnBefore(F.class)
|
||||||
|
private static class G {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue