Compare commits
5 Commits
013aa8c3d0
...
a9ed304ea0
Author | SHA1 | Date |
---|---|---|
|
a9ed304ea0 | |
|
7e6874ad80 | |
|
097463e3b7 | |
|
7bff887a98 | |
|
7dbe27af69 |
|
@ -37,18 +37,18 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
[TIP]
|
||||
====
|
||||
As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
|
||||
necessary if the target bean defines only one constructor to begin with. However, if
|
||||
several constructors are available and there is no primary/default constructor, at least
|
||||
one of the constructors must be annotated with `@Autowired` in order to instruct the
|
||||
container which one to use. See the discussion on
|
||||
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution] for details.
|
||||
An `@Autowired` annotation on such a constructor is not necessary if the target bean
|
||||
defines only one constructor. However, if several constructors are available and there is
|
||||
no primary or default constructor, at least one of the constructors must be annotated
|
||||
with `@Autowired` in order to instruct the container which one to use. See the discussion
|
||||
on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
|
||||
for details.
|
||||
====
|
||||
|
||||
You can also apply the `@Autowired` annotation to _traditional_ setter methods,
|
||||
as the following example shows:
|
||||
You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -84,8 +84,8 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
You can also apply the annotation to methods with arbitrary names and multiple
|
||||
arguments, as the following example shows:
|
||||
You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -176,14 +176,15 @@ Kotlin::
|
|||
====
|
||||
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
|
||||
are consistently declared by the type that you use for your `@Autowired`-annotated
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
|
||||
injection points. Otherwise, injection may fail due to a "no type match found" error at
|
||||
runtime.
|
||||
|
||||
For XML-defined beans or component classes found via classpath scanning, the container
|
||||
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
|
||||
to make sure that the declared return type is sufficiently expressive. For components
|
||||
that implement several interfaces or for components potentially referred to by their
|
||||
implementation type, consider declaring the most specific return type on your factory
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
implementation type, declare the most specific return type on your factory method (at
|
||||
least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
.[[beans-autowired-annotation-self-injection]]Self Injection
|
||||
|
@ -312,8 +313,8 @@ through `@Order` values in combination with `@Primary` on a single bean for each
|
|||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
The map values contain all beans of the expected type, and the keys contain the
|
||||
corresponding bean names, as the following example shows:
|
||||
The map values are all beans of the expected type, and the keys are the corresponding
|
||||
bean names, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -431,7 +432,7 @@ annotated constructor does not have to be public.
|
|||
====
|
||||
|
||||
Alternatively, you can express the non-required nature of a particular dependency
|
||||
through Java 8's `java.util.Optional`, as the following example shows:
|
||||
through Java's `java.util.Optional`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
@ -445,8 +446,8 @@ through Java 8's `java.util.Optional`, as the following example shows:
|
|||
----
|
||||
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
|
||||
built-in null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -477,13 +478,6 @@ Kotlin::
|
|||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
|
@ -528,5 +522,6 @@ class MovieRecommender {
|
|||
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
|
||||
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
|
||||
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
|
||||
|
||||
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
|
||||
====
|
||||
|
|
|
@ -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.OrderComparator;
|
||||
import org.springframework.core.RelativeOrderProcessor;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
|
||||
/**
|
||||
|
@ -40,6 +41,7 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
|||
* @author Juergen Hoeller
|
||||
* @author Oliver Gierke
|
||||
* @author Stephane Nicoll
|
||||
* @author Yongjun Hong
|
||||
* @since 2.0.1
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.Order
|
||||
|
@ -105,9 +107,10 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
|
|||
* @see java.util.List#sort(java.util.Comparator)
|
||||
*/
|
||||
public static void sort(List<?> list) {
|
||||
if (list.size() > 1) {
|
||||
list.sort(INSTANCE);
|
||||
if (list.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
RelativeOrderProcessor.sort(list);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,9 +121,10 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
|
|||
* @see java.util.Arrays#sort(Object[], java.util.Comparator)
|
||||
*/
|
||||
public static void sort(Object[] array) {
|
||||
if (array.length > 1) {
|
||||
Arrays.sort(array, INSTANCE);
|
||||
if (array.length <= 1) {
|
||||
return;
|
||||
}
|
||||
RelativeOrderProcessor.sort(Arrays.asList(array));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,11 +22,17 @@ import java.util.List;
|
|||
import jakarta.annotation.Priority;
|
||||
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.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @author Oliver Gierke
|
||||
* @author Yongjun Hong
|
||||
*/
|
||||
class AnnotationAwareOrderComparatorTests {
|
||||
|
||||
|
@ -100,6 +106,44 @@ class AnnotationAwareOrderComparatorTests {
|
|||
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)
|
||||
private static class A {
|
||||
}
|
||||
|
@ -119,4 +163,19 @@ class AnnotationAwareOrderComparatorTests {
|
|||
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