Spring Boot Collection Injection & @Order Sorting: Source Code Analysis & Patterns
This article explains how Spring Boot injects multiple beans into collections (List, Set, Map, arrays), details the @Order, Ordered, and PriorityOrdered sorting mechanisms with source code analysis of DefaultListableBeanFactory and OrderComparator, and demonstrates practical applications in Strategy and Chain of Responsibility patterns.
1. Why Spring Can Auto-Inject Multiple Beans into Collections
1.1 Single Injection vs Collection Injection
When injecting a single bean of a type with multiple candidates, Spring throws NoUniqueBeanDefinitionException. Collection types are recognized as multi-value dependencies, so Spring collects all matching beans.
// Single injection: throws if multiple beans exist
@Autowired
private PayStrategy payStrategy;
// Collection injection: collects all matching beans
@Autowired
private List<PayStrategy> strategies;1.2 Collection Injection Determination Point
Dependency injection is handled by post-processors. When the injection point type is Collection, array, or Map, Spring follows the multi-bean resolution branch instead of the single-bean branch.
1.3 Supported Injection Methods
Collections can be injected via constructor (recommended), field, or setter:
// Constructor injection (recommended)
public OrderService(List<PayStrategy> strategies,
Map<String, PayStrategy> strategyMap) {
this.strategies = strategies;
this.strategyMap = strategyMap;
}
// Field injection
@Autowired
private List<PayStrategy> strategies;
// Method injection
@Autowired
public void setStrategies(List<PayStrategy> strategies) { ... }1.4 Generic Type Matching
Collection uses generic type for matching, not just raw type:
// Only collects PayStrategy, not other strategies
List<PayStrategy> strategies;If the generic is Object or a wildcard, the matching scope widens accordingly.
2. Three Forms of Collection Injection: List, Set, Map, and Arrays
2.1 List and Arrays
List<T>and T[] behave identically: collect all matching beans, ordered by sorted order .
Best for ordered scenarios: chain of responsibility, strategy chain, listener lists.
private final List<PayStrategy> strategies;
private final PayStrategy[] strategyArray;2.2 Set
Set<T>collects all matching beans with deduplication.
Order is also sorted, but Set itself does not guarantee iteration order (common implementation LinkedHashSet preserves sorted result).
Use when order is irrelevant, only membership matters.
@Autowired
private Set<Validator> validators;2.3 Map Injection
Map<String, T>is often misunderstood:
Key is the bean name (beanName) .
Value is the bean instance .
Not your custom business key.
@Autowired
private Map<String, PayStrategy> strategyMap;
// Assuming two implementations: alipayStrategy, wechatPayStrategy
// strategyMap = {
// "alipayStrategy": AlipayStrategy@...,
// "wechatPayStrategy": WechatPayStrategy@...
// }To select by business code, define a support() method in the strategy interface and filter from List or Map; Map key is only beanName.
2.4 Injection Result Sources
Collection scope includes all matching beans in the current container and its parent containers; after excluding special handling like @Lazy, Spring resolves bean names by type and instantiates/retrieves each.
2.5 Comparison Summary
List<T> : Bean instances, ordered by @Order, typical for chain of responsibility, strategy chain.
T[] : Bean instances, ordered by @Order, equivalent to List.
Set<T> : Bean instances, sorted then deduplicated, for unordered member sets.
Map<String,T> : beanName → Bean, inserted into LinkedHashMap after sorting, for lookup by name.
3. Sorting Rules: @Order, Ordered, PriorityOrdered — Who Comes First
3.1 Three Sorting Sources
Spring sorts candidate beans with priority from highest to lowest:
PriorityOrdered interface : Highest priority, always before ordinary Ordered.
Ordered interface : Provides order via getOrder().
@Order annotation : Placed on class or @Bean method, uses value.
@Priority (JSR-250) : Also recognized by Spring.
Unannotated : Ordered by registration order, after explicitly sorted beans.
3.2 Numeric Rule: Smaller Comes First
Both Ordered.getOrder() and @Order(value) follow the same rule:
Smaller value → Higher priority → Appears earlier in collectionDefault values:
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
}@Orderdefaults to LOWEST_PRECEDENCE (lowest priority) when unspecified.
3.3 PriorityOrdered vs Ordered
PriorityOrderedextends Ordered:
public interface PriorityOrdered extends Ordered {
}Used for components that must precede ordinary Ordered ones, typically Spring internal post-processors. Comparison: PriorityOrdered group as a whole sorts before ordinary Ordered group , then within each group by numeric value.
3.4 Complete Sorting Example
public interface PayStrategy {
boolean support(String channel);
void pay(Order order);
}
// Order 1: highest priority
@Order(1)
@Component
public class AlipayStrategy implements PayStrategy { ... }
// Order 2
@Order(2)
@Component
public class WechatPayStrategy implements PayStrategy { ... }
// Implements Ordered interface
@Component
public class UnionPayStrategy implements PayStrategy, Ordered {
@Override
public int getOrder() { return 3; }
}
// PriorityOrdered: sorts before ordinary Ordered
@Component
public class InternalPayStrategy implements PayStrategy, PriorityOrdered {
@Override
public int getOrder() { return 10; }
}Resulting injection order:
InternalPayStrategy (PriorityOrdered, 10)
AlipayStrategy (@Order 1)
WechatPayStrategy (@Order 2)
UnionPayStrategy (Ordered 3)3.5 Handling Equal Order Values
When two beans have the same order value, relative order is not guaranteed stable; typically follows container registration order. Do not rely on same-order positioning for business correctness.
3.6 What @Order Does NOT Control
@Orderdoes not control singleton bean initialization/loading order .
It primarily affects collection, list, array, interceptor, listener, and other multi-component scenarios ordering.
Single bean creation order is determined by dependency relationships and post-processors; adding @Order does not change it.
4. Source Code Path: From Type Collection to OrderComparator Sorting
4.1 Multi-Bean Resolution Entry
During dependency resolution, DefaultListableBeanFactory handles collection dependencies:
// Simplified source
protected Object resolveMultipleBeans(DependencyDescriptor descriptor,
String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) {
Class<?> type = descriptor.getDependencyType();
if (Collection.class.isAssignableFrom(type) || type.isArray()) {
// Find all bean names by type (including ancestors)
String[] beanNames = beanNamesForTypeIncludingAncestors(
descriptor.getResolvableType().asCollection().resolveGeneric());
// Retrieve bean instances
List<Object> beans = new ArrayList<>(beanNames.length);
for (String name : beanNames) {
beans.add(beanFactory.getBean(name));
}
// Sort and return
AnnotationAwareOrderComparator.INSTANCE.sort(beans);
return beans;
}
if (Map.class.isAssignableFrom(type)) {
// Collect beanName -> bean, also sort then put into LinkedHashMap
...
}
}4.2 Sorting Comparator: OrderComparator
Sorting uses OrderComparator, with Spring's default instance being AnnotationAwareOrderComparator, which reads @Order and @Priority in addition to interface comparison:
public class AnnotationAwareOrderComparator extends OrderComparator {
@Override
protected int getOrder(Object obj) {
// 1. Prefer Ordered interface
if (obj instanceof Ordered ordered) {
return ordered.getOrder();
}
// 2. Then read @Order annotation
Order order = AnnotationUtils.findAnnotation(obj.getClass(), Order.class);
if (order != null) {
return order.value();
}
// 3. Then read @Priority
Priority priority = ...;
...
return Ordered.LOWEST_PRECEDENCE;
}
}4.3 Comparison Rules in compare
OrderComparatorcompares two objects:
Both PriorityOrdered: compare by order value.
One PriorityOrdered, one ordinary Ordered: PriorityOrdered comes first.
Both ordinary Ordered: compare by order value.
Neither has order info: treated as equal, retain original order.
4.4 Sorting Occurs at Injection Time
Sorting happens during dependency injection when collecting candidate beans , and the result is placed into the collection. It is not re-sorted on each iteration. Thus the collection order is fixed after injection.
4.5 Auto-Configuration Order Is Separate
Note: @AutoConfigureOrder, @AutoConfigureBefore/After control auto-configuration class loading order , a different comparison system from component collection @Order. Do not mix them.
5. Strategy Pattern and Chain of Responsibility with Collection Injection
5.1 Strategy Pattern: List Injection + Ordered Matching
public interface PayStrategy {
boolean support(String channel);
void pay(Order order);
}
@Component
public class AlipayStrategy implements PayStrategy {
@Override
public boolean support(String channel) { return "ALIPAY".equals(channel); }
@Override
public void pay(Order order) { /* ... */ }
}
@Component
public class WechatPayStrategy implements PayStrategy {
@Override
public boolean support(String channel) { return "WECHAT".equals(channel); }
@Override
public void pay(Order order) { /* ... */ }
}
@Service
public class PayService {
private final List<PayStrategy> strategies;
public PayService(List<PayStrategy> strategies) {
this.strategies = strategies;
}
public void pay(String channel, Order order) {
strategies.stream()
.filter(s -> s.support(channel))
.findFirst()
.orElseThrow(() -> new BizException("Unsupported payment channel"))
.pay(order);
}
}Adding a new channel only requires a new implementation class, no modification to PayService — this is the Open/Closed Principle enabled by collection injection.
5.2 Chain of Responsibility: Sequential Processing
public interface RuleHandler {
boolean handle(RuleContext context);
}
@Component
@Order(1)
public class ParamCheckHandler implements RuleHandler { ... }
@Component
@Order(2)
public class AuthCheckHandler implements RuleHandler { ... }
@Component
@Order(3)
public class RiskCheckHandler implements RuleHandler { ... }
@Service
public class RuleChainService {
private final List<RuleHandler> handlers;
public RuleChainService(List<RuleHandler> handlers) {
this.handlers = handlers;
}
public void execute(RuleContext context) {
for (RuleHandler handler : handlers) {
if (!handler.handle(context)) {
return; // break chain
}
}
}
}Processing order is controlled uniformly by @Order; adding a new handler only requires inserting a new component, chain code remains unchanged.
5.3 Map Injection: Lookup by beanName
@Service
public class StrategyLookupService {
private final Map<String, PayStrategy> strategyMap;
public StrategyLookupService(Map<String, PayStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
public PayStrategy byBeanName(String beanName) {
return strategyMap.get(beanName);
}
}5.4 Combining with @Qualifier for Filtering
Collection injection collects all by default; to inject only specific beans, combine @Qualifier on the field/parameter for filtering.
Collection injection, @Order sorting, Strategy pattern, and Chain of Responsibility are high-frequency topics in Spring advanced and senior interviews. Understanding multi-bean collection and the Order comparison system enables transferable insight into auto-configuration loading, interceptor chains, event listeners, and extension point design, allowing precise troubleshooting of "wrong order", "multiple bean injection errors", and "strategy matching chaos".
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
