Spring 注入 List 集合详解:用法、原理与实战
作者:步行cgn
一、什么是 List 注入?
List 注入是指将一个 List 类型的集合注入到 Bean 的属性中。与数组注入不同,List 更灵活——元素可以动态增删,支持泛型,是 Spring 开发中处理多个同类型依赖的首选方式。
@Component
public class MyBean {
private List<String> servers; // 字符串 List
private List<DataSource> dataSources; // Bean List
private List<Handler> handlers; // 策略/处理器 List
}
二、XML 配置中注入 List
2.1 注入字符串 List
<bean id="myBean" class="com.example.MyBean">
<property name="servers">
<list>
<value>server1.example.com</value>
<value>server2.example.com</value>
<value>server3.example.com</value>
</list>
</property>
</bean>2.2 注入 Bean List
<bean id="myBean" class="com.example.MyBean">
<property name="dataSources">
<list>
<ref bean="dataSource1"/>
<ref bean="dataSource2"/>
<ref bean="dataSource3"/>
</list>
</property>
</bean>2.3 注入基本类型 List
<bean id="myBean" class="com.example.MyBean">
<property name="ports">
<list>
<value>8080</value>
<value>8081</value>
<value>8082</value>
</list>
</property>
</bean>Spring 会自动将字符串值转换为 Integer 类型。
2.4 注入嵌套 List
<bean id="myBean" class="com.example.MyBean">
<property name="matrix">
<list>
<list>
<value>1</value>
<value>2</value>
</list>
<list>
<value>3</value>
<value>4</value>
</list>
</list>
</property>
</bean>对应 Java:
private List<List<Integer>> matrix;
2.5 注入对象 List
<bean id="myBean" class="com.example.MyBean">
<property name="users">
<list>
<bean class="com.example.User">
<property name="name" value="张三"/>
<property name="age" value="25"/>
</bean>
<bean class="com.example.User">
<property name="name" value="李四"/>
<property name="age" value="30"/>
</bean>
</list>
</property>
</bean>2.6 List 与 Array 的 XML 差异
<!-- List 类型属性 -->
<property name="servers">
<list>
<value>server1</value>
<value>server2</value>
</list>
</property>
<!-- Array 类型属性(Spring 会自动转换) -->
<property name="servers">
<array>
<value>server1</value>
<value>server2</value>
</array>
</property>虽然 <list> 和 <array> 语义不同,但对 List 类型的属性来说,两者都能使用。
三、@Value 注入 List
3.1 从配置文件读取
@Component
public class MyBean {
// 配置文件:app.servers=server1,server2,server3
@Value("${app.servers}")
private List<String> servers;
// 带默认值
@Value("${app.ports:8080,8081,8082}")
private List<Integer> ports;
}配置文件:
app: servers: server1,server2,server3 ports: 8080,8081,8082
Spring 会自动将逗号分隔的字符串拆分为 List。
3.2 使用 SpEL 拆分
当值中包含逗号或需要更复杂的拆分逻辑时,用 SpEL:
@Value("#{'${app.servers}'.split(',')}")
private List<String> servers;
@Value("#{'${app.servers:server1,server2}'.split(',')}")
private List<String> serversWithDefault;3.3 直接指定多个值
@Value("server1,server2,server3")
private List<String> servers;
四、@ConfigurationProperties 绑定 List
4.1 简单 List
@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
private List<String> servers;
private List<Integer> ports;
}
app:
servers:
- server1
- server2
- server3
ports: [8080, 8081, 8082]4.2 对象 List
@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
private List<User> users;
@Getter
@Setter
public static class User {
private String name;
private int age;
private String email;
}
}
app:
users:
- name: 张三
age: 25
email: zhangsan@example.com
- name: 李四
age: 30
email: lisi@example.com4.3 嵌套 List
@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
private List<Cluster> clusters;
@Getter
@Setter
public static class Cluster {
private String name;
private List<String> nodes; // 嵌套 List
}
}app:
clusters:
- name: cluster-a
nodes:
- node1
- node2
- name: cluster-b
nodes:
- node3
- node4
4.4 List + Map 组合
@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
private List<Database> databases;
@Getter
@Setter
public static class Database {
private String type;
private Map<String, String> config; // List 中嵌套 Map
}
}app:
databases:
- type: mysql
config:
url: jdbc:mysql://localhost:3306/db1
username: root
- type: postgresql
config:
url: jdbc:postgresql://localhost:5432/db2
username: admin五、自动注入 Bean List(最常用)
Spring 支持将容器中所有同类型的 Bean 自动注入到 List 中,这是策略模式在 Spring 中的经典应用。
5.1 基本用法
public interface PaymentStrategy {
void pay(double amount);
}
@Component
public class AliPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount);
}
}
@Component
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount);
}
}
@Component
public class PaymentService {
@Autowired
private List<PaymentStrategy> strategies; // 自动注入所有实现
public void payAll(double amount) {
strategies.forEach(s -> s.pay(amount));
}
}5.2 构造器注入 List
@Service
public class PaymentService {
private final List<PaymentStrategy> strategies;
public PaymentService(List<PaymentStrategy> strategies) {
this.strategies = strategies;
}
}5.3 使用 @Order 控制顺序
@Component
@Order(1)
public class FirstStrategy implements PaymentStrategy { }
@Component
@Order(2)
public class SecondStrategy implements PaymentStrategy { }
@Component
@Order(3)
public class ThirdStrategy implements PaymentStrategy { }注入的 List 中,元素按 @Order 的值从小到大排列。也可以用 Ordered 接口:
@Component
public class FirstStrategy implements PaymentStrategy, Ordered {
@Override
public int getOrder() {
return 1;
}
}5.4 策略模式实战:按名称分发
public interface PaymentStrategy {
String getName();
void pay(double amount);
}
@Component
public class AliPayStrategy implements PaymentStrategy {
@Override
public String getName() { return "alipay"; }
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount);
}
}
@Component
public class WechatPayStrategy implements PaymentStrategy {
@Override
public String getName() { return "wechat"; }
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount);
}
}
@Service
public class PaymentService {
private final Map<String, PaymentStrategy> strategyMap;
public PaymentService(List<PaymentStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(PaymentStrategy::getName, s -> s));
}
public void pay(String type, double amount) {
PaymentStrategy strategy = strategyMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式:" + type);
}
strategy.pay(amount);
}
}六、List 为空时的处理
6.1 默认行为
当容器中没有匹配类型的 Bean 时,@Autowired 注入 List 不会抛异常,而是注入一个空 List。
@Autowired private List<Handler> handlers; // 没有 Handler 时,注入空 List(不是 null)
这与数组注入不同——数组注入在找不到 Bean 时会抛异常,而 List 注入会返回空集合。
6.2 使用 ObjectProvider 控制
@Autowired
private ObjectProvider<List<Handler>> handlersProvider;
public void doSomething() {
List<Handler> handlers = handlersProvider.getIfAvailable(Collections::emptyList);
// 使用 handlers
}6.3 校验 List 不为空
@PostConstruct
public void init() {
if (strategies.isEmpty()) {
throw new IllegalStateException("没有可用的策略实现");
}
}
七、List 注入 vs 数组注入 vs Map 注入
| 对比维度 | List | 数组 | Map |
|---|---|---|---|
| 类型 | List<T> | T[] | Map<String, T> |
| 长度 | 可变 | 固定 | 可变 |
| 找不到 Bean 时 | 注入空 List | 抛异常(默认) | 注入空 Map |
| 排序 | @Order | @Order | 无 |
| 按名称查找 | 需手动转换 | 需手动转换 | 直接按 key 获取 |
| 适用场景 | 需要顺序、遍历处理 | 固定长度、性能敏感 | 需要按名称查找 |
Map 注入的用法:
@Autowired private Map<String, PaymentStrategy> strategyMap;
Spring 会自动将 Bean 名称作为 key,Bean 实例作为 value 注入到 Map 中。这种方式在策略模式中比 List 更方便——直接按名称获取策略,不需要遍历。
八、常见问题
8.1 List 注入为 null
原因:没有使用 @Autowired 或没有 @Component 注解,或者类没有被 Spring 管理。
解决:确保类被 Spring 管理,字段标注 @Autowired。
8.2 List 顺序不符合预期
原因:多个 Bean 注入 List 时,默认顺序是 Bean 定义的顺序。
解决:使用 @Order 注解明确指定顺序。
8.3 @Value 注入 List 时逗号被拆分
原因:配置值中的逗号被当作分隔符。
解决:使用 @ConfigurationProperties 绑定,或用 SpEL 自定义拆分:
@Value("#{'${app.servers}'.split(';')}") // 用分号分隔
private List<String> servers;
8.4 List 中混入 null
XML 配置中可以用 <null/> 注入 null 元素:
<list>
<value>server1</value>
<null/>
<value>server3</value>
</list>但建议避免在 List 中放入 null,容易导致空指针。
8.5 List 注入与 @Qualifier 冲突
@Qualifier 用于指定单个 Bean 的名称,不能用于筛选 List 中的 Bean。如果需要按条件筛选 Bean,使用 @Bean 方法手动组装,或使用 ObjectProvider。
九、最佳实践
优先使用自动注入 List。 当需要收集同一接口的所有实现时,@Autowired List<T> 是最简洁的方式,天然支持策略模式。
用 @Order 控制顺序。 当 List 中的元素有执行顺序要求时,给实现类加 @Order 注解。
考虑使用 Map 代替 List。 如果主要目的是按名称获取特定策略,直接注入 Map<String, T> 更方便。
配置文件中的 List 用 @ConfigurationProperties。 比 @Value 更类型安全,支持嵌套对象。
处理空 List。 虽然 List 注入不会抛异常,但空集合可能导致业务逻辑出错。在 @PostConstruct 中校验关键 List 是否为空。
十、总结
| 维度 | 核心要点 |
|---|---|
| XML 注入 | 使用 <list> 标签,支持嵌套、对象、基本类型 |
| @Value 注入 | 逗号分隔字符串自动拆分,或使用 SpEL 自定义拆分 |
| @ConfigurationProperties | 原生支持复杂 List 绑定,推荐用于配置文件 |
| 自动注入 Bean List | 注入容器中所有同类型 Bean,策略模式的经典实现 |
| 顺序控制 | @Order 注解或 Ordered 接口 |
| 空 List 处理 | 默认注入空集合,需要时用 ObjectProvider 或 @PostConstruct 校验 |
| List vs Map | List 适合遍历,Map 适合按名称查找 |
| 最佳实践 | 自动注入用 List,配置绑定用 @ConfigurationProperties,按名称用 Map |
List 注入是 Spring 中最常用的集合注入方式。它天然支持策略模式,让“收集所有实现类”变得极其简单。掌握 List 注入的各种方式,能让你在业务开发中灵活处理多实现、多配置的场景。
到此这篇关于Spring 注入 List 集合详解的文章就介绍到这了,更多相关Spring 注入 List 集合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
