8 changed files with 566 additions and 4 deletions
@ -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> relativeOrderMap = new HashMap<>(); |
||||||
|
if (!components.isEmpty()) { |
||||||
|
List<Object> sortedRelative = TOPOLOGICAL_ORDER_SOLVER.resolveOrder(components); |
||||||
|
for (int i = 0; i < sortedRelative.size(); i++) { |
||||||
|
relativeOrderMap.put(sortedRelative.get(i), i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new ConfiguredComparator(relativeOrderMap); |
||||||
|
} |
||||||
|
|
||||||
|
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> relativeOrderMap; |
||||||
|
private final Comparator<Object> fallbackComparator = AnnotationAwareOrderComparator.INSTANCE; |
||||||
|
|
||||||
|
public ConfiguredComparator(Map<Object, Integer> relativeOrderMap) { |
||||||
|
this.relativeOrderMap = relativeOrderMap; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compare(Object o1, Object o2) { |
||||||
|
boolean o1isRelative = hasRelativeOrderAnnotations(o1); |
||||||
|
boolean o2isRelative = hasRelativeOrderAnnotations(o2); |
||||||
|
|
||||||
|
// Case 1: Both components have relative order. Compare their topological index.
|
||||||
|
if (o1isRelative || o2isRelative) { |
||||||
|
int order1 = this.relativeOrderMap.getOrDefault(o1, 0); |
||||||
|
int order2 = this.relativeOrderMap.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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue