java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring注入Set集合

在Spring中注入Set集合的方法详解

作者:步行cgn

Set 注入是指将一个 Set 类型的集合注入到 Bean 的属性中,Set 与 List 最大的区别是:Set 中的元素不允许重复,且通常不保证顺序,本文系统讲解Set注入的XML、@Value、@ConfigurationProperties和自动注入四种方式,对比Set与List的核心差异,需要的朋友可以参考下

一、什么是 Set 注入?

Set 注入是指将一个 Set 类型的集合注入到 Bean 的属性中。Set 与 List 最大的区别是:Set 中的元素不允许重复,且通常不保证顺序(除非使用 LinkedHashSetTreeSet)。

@Component
public class MyBean {
    private Set<String> servers;          // 字符串 Set
    private Set<DataSource> dataSources;  // Bean Set
    private Set<Handler> handlers;        // 策略/处理器 Set
}

在 Spring 注入场景中,Set 常用于:

二、XML 配置中注入 Set

2.1 注入字符串 Set

<bean id="myBean" class="com.example.MyBean">
    <property name="servers">
        <set>
            <value>server1.example.com</value>
            <value>server2.example.com</value>
            <value>server3.example.com</value>
        </set>
    </property>
</bean>

2.2 注入 Bean Set

<bean id="myBean" class="com.example.MyBean">
    <property name="dataSources">
        <set>
            <ref bean="dataSource1"/>
            <ref bean="dataSource2"/>
            <ref bean="dataSource3"/>
        </set>
    </property>
</bean>

2.3 注入基本类型 Set

<bean id="myBean" class="com.example.MyBean">
    <property name="ports">
        <set>
            <value>8080</value>
            <value>8081</value>
            <value>8082</value>
        </set>
    </property>
</bean>

Spring 会自动将字符串值转换为 Integer 类型。

2.4 注入对象 Set

<bean id="myBean" class="com.example.MyBean">
    <property name="users">
        <set>
            <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>
        </set>
    </property>
</bean>

2.5<set>与<list>的 XML 差异

<!-- Set 类型属性 -->
<property name="servers">
    <set>
        <value>server1</value>
        <value>server2</value>
    </set>
</property>
<!-- List 类型属性 -->
<property name="servers">
    <list>
        <value>server1</value>
        <value>server2</value>
    </list>
</property>

虽然 XML 标签不同,但 Spring 会根据目标属性的类型自动选择合适的集合实现。如果属性是 Set,即使写 <list>,Spring 也会注入 Set(通常是 LinkedHashSet)。

三、@Value 注入 Set

3.1 从配置文件读取

@Component
public class MyBean {
    
    // 配置文件:app.servers=server1,server2,server3
    @Value("${app.servers}")
    private Set<String> servers;
    
    // 带默认值
    @Value("${app.ports:8080,8081,8082}")
    private Set<Integer> ports;
}

配置文件:

app:
  servers: server1,server2,server3
  ports: 8080,8081,8082

Spring 会自动将逗号分隔的字符串拆分为 Set。

3.2 使用 SpEL 拆分

@Value("#{'${app.servers}'.split(',')}")
private Set<String> servers;

注意:split() 返回数组,Spring 会自动转换为 Set。如果数组中有重复元素,Set 会自动去重。

3.3 直接指定多个值

@Value("server1,server2,server3")
private Set<String> servers;

四、@ConfigurationProperties 绑定 Set

4.1 简单 Set

@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
    private Set<String> servers;
    private Set<Integer> ports;
}
app:
  servers:
    - server1
    - server2
    - server3
  ports: [8080, 8081, 8082]

4.2 对象 Set

@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
    private Set<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.com

4.3 嵌套 Set

@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
    private Set<Cluster> clusters;
    
    @Getter
    @Setter
    public static class Cluster {
        private String name;
        private Set<String> nodes;  // 嵌套 Set
    }
}
app:
  clusters:
    - name: cluster-a
      nodes:
        - node1
        - node2
    - name: cluster-b
      nodes:
        - node3
        - node4

五、自动注入 Bean Set(最常用)

Spring 支持将容器中所有同类型的 Bean 自动注入到 Set 中。

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 Set<PaymentStrategy> strategies;  // 自动注入所有实现
    
    public void payAll(double amount) {
        strategies.forEach(s -> s.pay(amount));
    }
}

5.2 构造器注入 Set

@Service
public class PaymentService {
    private final Set<PaymentStrategy> strategies;
    
    public PaymentService(Set<PaymentStrategy> strategies) {
        this.strategies = strategies;
    }
}

5.3 Set 的默认实现类型

Spring 注入 Set 时,默认使用 LinkedHashSetLinkedHashSet 内部维护了一个链表,保留了元素的插入顺序,因此注入到 Set 中的 Bean 顺序通常是 Bean 定义的顺序。

如果需要去重且不关心顺序,可以声明为 Set。如果需要排序,可以声明为 SortedSetTreeSet

@Autowired
private SortedSet<PaymentStrategy> strategies;  // 按自然顺序排序

@Autowired
private TreeSet<PaymentStrategy> strategies;    // 按 Comparator 或自然顺序

5.4 使用 @Order 控制顺序

虽然 Set 通常不保证顺序,但 LinkedHashSet 会保留插入顺序。配合 @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 { }

注入的 LinkedHashSet 中,元素按 @Order 的值从小到大排列。

六、Set 的自动去重特性

Set 最重要的特性是元素去重。当容器中有多个相同类型的 Bean 时,如果 Bean 的 equals()hashCode() 判定为相同,Set 会自动去重。

@Component
public class HandlerA implements Handler {
    @Override
    public boolean equals(Object obj) {
        return obj instanceof HandlerA;
    }
    
    @Override
    public int hashCode() {
        return HandlerA.class.hashCode();
    }
}

@Component
public class HandlerA2 implements Handler {
    // 如果 equals 和 hashCode 与 HandlerA 相同,Set 会去重
}

但在实际开发中,不同的 Bean 通常不会互相 equals,所以 Set 注入的去重效果主要体现在同一个 Bean 被多次注册的场景(比如通过不同的配置方式重复注册)。

七、Set 为空时的处理

7.1 默认行为

当容器中没有匹配类型的 Bean 时,@Autowired 注入 Set 不会抛异常,而是注入一个空 Set。

@Autowired
private Set<Handler> handlers;  // 没有 Handler 时,注入空 Set(不是 null)

这与 List 注入行为一致。数组注入在找不到 Bean 时会抛异常,而 Set 和 List 会返回空集合。

7.2 使用 ObjectProvider 控制

@Autowired
private ObjectProvider<Set<Handler>> handlersProvider;

public void doSomething() {
    Set<Handler> handlers = handlersProvider.getIfAvailable(Collections::emptySet);
}

7.3 校验 Set 不为空

@PostConstruct
public void init() {
    if (strategies.isEmpty()) {
        throw new IllegalStateException("没有可用的策略实现");
    }
}

八、Set 注入 vs List 注入 vs 数组注入

对比维度SetList数组
是否去重✅ 自动去重❌ 允许重复❌ 允许重复
是否有序⚠️ 取决于实现类✅ 有序✅ 有序
默认实现LinkedHashSetArrayList
找不到 Bean 时注入空 Set注入空 List抛异常
排序支持支持 @Order(LinkedHashSet)支持 @Order支持 @Order
适用场景需要去重的多实现需要顺序遍历固定长度、性能敏感

选择建议:

九、Set 注入的底层实现

Spring 在 populateBean() 阶段处理 Set 注入时,会根据目标属性的类型选择合适的集合实现类。

简化流程:

  1. Spring 扫描到 Set<PaymentStrategy> 类型的依赖
  2. 调用 DefaultListableBeanFactory.resolveMultipleBeans() 获取所有 PaymentStrategy 类型的 Bean
  3. 根据目标类型 Set 选择合适的实现类(默认 LinkedHashSet
  4. 遍历所有匹配的 Bean,依次添加到 Set 中
  5. 将构建好的 Set 赋值给目标字段

如果 Bean 的数量为 0,返回空 Set,不抛异常。

十、常见问题

10.1 Set 注入为 null

原因:没有使用 @Autowired 或没有 @Component 注解。

解决:确保类被 Spring 管理,字段标注 @Autowired

10.2 Set 顺序不符合预期

原因HashSet 不保证顺序,如果 Spring 注入的是 LinkedHashSet,则按 Bean 定义的顺序。

解决:使用 @Order 注解明确顺序,或声明为 SortedSetTreeSet

10.3 Set 去重导致 Bean 丢失

原因:多个 Bean 的 equals()hashCode() 判定为相同,Set 自动去重。

解决:检查 Bean 的 equals()hashCode() 实现,避免误判。如果不希望去重,改用 List 或数组。

10.4 @Value 注入 Set 时逗号被拆分

原因:配置值中的逗号被当作分隔符。

解决:使用 @ConfigurationProperties 绑定,或用 SpEL 自定义拆分。

10.5 Set 与 @Order 的配合

Set 本身不保证顺序,但 LinkedHashSet 保留了插入顺序。如果使用 HashSet@Order 可能失效。

解决:声明为 Set 时 Spring 默认注入 LinkedHashSet,配合 @Order 可以控制顺序。

十一、最佳实践

优先使用自动注入 Set。 当需要收集同一接口的所有实现且需要去重时,@Autowired Set<T> 是最简洁的方式。

用 @Order 控制顺序。 当 Set 中的元素有执行顺序要求时,给实现类加 @Order 注解。

配置文件中的 Set 用 @ConfigurationProperties。@Value 更类型安全,支持嵌套对象。

处理空 Set。@PostConstruct 中校验关键 Set 是否为空,避免业务逻辑出错。

注意 Set 的去重特性。 如果多个 Bean 的 equals() 判定相同,Set 会去重。确认这是否符合业务预期。

选择合适的具体实现。 如果需要排序,声明为 SortedSetTreeSet;如果需要保留插入顺序,使用默认的 LinkedHashSet

十二、总结

维度核心要点
XML 注入使用 <set> 标签,支持嵌套、对象、基本类型
@Value 注入逗号分隔字符串自动拆分,或使用 SpEL
@ConfigurationProperties原生支持复杂 Set 绑定,推荐用于配置文件
自动注入 Bean Set注入容器中所有同类型 Bean,自动去重
默认实现LinkedHashSet(保留插入顺序)
顺序控制@Order 注解配合 LinkedHashSet
空 Set 处理默认注入空集合,需要时用 @PostConstruct 校验
Set vs ListSet 去重,List 允许重复;Set 顺序取决于实现,List 天然有序
最佳实践自动注入用 Set,配置绑定用 @ConfigurationProperties,需要排序用 SortedSet

Set 注入是 Spring 集合注入的重要形式,特别适合需要去重的多实现场景。理解 Set 与 List 的区别、默认实现类的选择以及 @Order 的配合,能让你在业务开发中更准确地选择集合类型,写出更符合语义的代码。

以上就是在Spring中注入Set集合的方法详解的详细内容,更多关于Spring注入Set集合的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文