如何正确使用 Spring 注入集合类型?

本文探讨了Spring框架中集合类型的自动注入,包括收集装配和直接装配两种方式。当这两种方式共存时,Spring会优先选择收集装配,导致直接装配失效。解决办法是避免两者混用,选择单一注入方式。同时,介绍了通过@Order和@DependsOn注解调整Bean加载顺序以达到指定Bean优先注入的效果。
摘要由CSDN通过智能技术生成

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。

各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责:

  • 中央/分销预订系统性能优化
  • 活动&券等营销中台建设
  • 交易平台及数据中台等架构和开发设计
  • 车联网核心平台-物联网连接平台、大数据平台架构设计及优化
  • LLM Agent应用开发
  • 区块链应用开发
  • 大数据开发挖掘经验
  • 推荐系统项目

目前主攻市级软件项目设计、构建服务全社会的应用系统。
用依赖注入特性,须思考:

  • 对象从哪注入
  • 咋创建
  • 为啥注入这个对象

虽然编写框架,是为了让开发无需关心太多底层细节,专注业务逻辑开发,但作为开发不能真无脑用框架。务必学会注入集合等高级用法,让自己有所提升!

现有需求:存在多个用户Bean,找出来存储到一个List。

1 注入方式

1.1 收集装配

多个用户Bean定义:

@Configuration
public class UserConfig {

    @Bean
    public User user1() {
        return createUser(1, "java");
    }

    @Bean
    public User user2() {
        return createUser(2, "edge");
    }

    private User createUser(int id, String name) {
        return new User(id, name);
    }
}

有了集合类型的自动注入后,即可收集零散的用户Bean:

@RestController
@Slf4j
public class UserController {

    private List<User> users;

    public UserController(List<User> users) {
        this.users = users;
    }

    @GetMapping(path = "users")
    public String listUsers() {
        return users.toString();
    }
}

即可完成集合类型注入:

但当持续增加一些user时,可能就不喜欢用上述的注入集合类型,而是这样:

1.2 直接装配

@Bean
public List<User> users() {
    User user3 = createUser(3, "WeChat");
    User user4 = createUser(4, "QQ");
    return Arrays.asList(user3, user4);
}

分开玩,大家应该不会有啥问题。

若两种方式共存,会咋样?运行程序后发现直接装配方式的未生效:

why?

2 源码解析

就得精通两种注入风格的底层实现。

2.1 收集装配

看DefaultListableBeanFactory#resolveMultipleBeans

private Object resolveMultipleBeans(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) {
   // interface java.util.List
   final Class<?> type = descriptor.getDependencyType();
   ...
   // 装配集合
   else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
      // 获取集合的元素类型
      Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
      // 根据元素类型查找所有的bean
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
            new MultiElementDescriptor(descriptor));
      // 转化查到的所有bean放置到集合并返回
      TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
      Object result = converter.convertIfNecessary(matchingBeans.values(), type);
      // ...
      return result;
   }
}
1 获取集合类型的elementType

目标类型定义为List<User> users,所以元素类型为User:

2 根据元素类型找出所有Bean

据elementType找出所有Bean:

3 将匹配的所有的Bean按目标类型转化

上一步获取的所有的Bean都以java.util.LinkedHashMap.LinkedValues存储,和目标类型大不相同,所以最后按需转化。

本案例需转化为List:

2.2 直接装配

定位源码:DefaultListableBeanFactory#findAutowireCandidates,不再赘述。

最后就是根据目标类型直接寻找匹配Bean名称为users的List<user>装配给userController#users属性。

2.3 同时满足

两种装配方式时,Spring咋处理?定位源码:DefaultListableBeanFactory#doResolveDependency

Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
    return multipleBeans;
}

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
    if (isRequired(descriptor)) {
       raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
    }
    return null;
}

显然两种装配集合方式不能同存,结合本案例:

  • 用收集装配时,能找到任一对应Bean,则返回
  • 若一个都没找到,才采用直接装配

所以后期以List方式直接添加的user Bean都不生效!

3 修正

务必避免两种方式共存去装配集合!只选用一种方式即可。如只用直接装配:

@Bean
public List<User> users() {
    User user1 = createUser(1, "java");
    User user2 = createUser(2, "edge");
    User user3 = createUser(3, "WeChat");
    User user4 = createUser(4, "QQ");
    return Arrays.asList(user1, user2,user3, user4);
}

只使用收集方式:

@Bean
public User user1() {
  return createUser(1, "java");
}

@Bean
public User user2() {
  return createUser(2, "edge");
}

@Bean
public User user3() {
    return createUser(3, "WeChat");
}

@Bean
public User user4() {
    return createUser(4, "QQ");
}

4 咋让用户2先输出?

控制spring bean加载顺序:

  1. Bean上用@Order,如@Order(2)。数值越小,优先级越高。默认优先级最低
  2. @DependsOn,可使依赖的Bean若未被初始化,则被优先初始化
  3. 加@Order(number),number越小优先级越高,越靠前
  4. 声明user这些Bean时将id=2的user提到id=1前
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值