Compare commits

...

5 Commits

Author SHA1 Message Date
Yongjun Hong a9ed304ea0
Merge 7bff887a98 into 7e6874ad80 2025-10-07 23:10:35 +03:00
Sam Brannen 7e6874ad80 Polish @⁠Autowired section of the reference manual
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details
2025-10-07 17:17:27 +02:00
Sam Brannen 097463e3b7 Remove outdated reference to JSR 305 in the reference documentation
Closes gh-35580
2025-10-07 17:10:40 +02:00
yongjunhong 7bff887a98
Rename variable
Signed-off-by: yongjunhong <yongjunh@apache.org>
2025-08-22 23:10:45 +09:00
yongjunhong 7dbe27af69
Add support for relative ordering
Signed-off-by: yongjunhong <yongjunh@apache.org>
2025-08-22 22:50:51 +09:00
9 changed files with 587 additions and 30 deletions

View File

@ -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.
====

View File

@ -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;
}
}

View File

@ -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 {};
}

View File

@ -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 {};
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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));
}
/**

View File

@ -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 {
}
}