认识 Spring 依赖注入中的 @Qualifer

简介: 前言Spring 支持注入单一类型和集合类型的依赖,对于单一类型,如果按照类型进行注入,容器中存在多个相同类型的 bean 时,Spring 将抛出 NoUniqueBeanDefinitionException 异常。对于这种情况,我们可以选择将某一个 bean 设置为 primary,然而如果存在多个 primary 的 bean,Spring 仍将无法处理,这时便引出我们今天介绍的 @Qualifier,使用 @Qualifier 可以明确指出注入哪个 bean。

前言


Spring 支持注入单一类型和集合类型的依赖,对于单一类型,如果按照类型进行注入,容器中存在多个相同类型的 bean 时,Spring 将抛出 NoUniqueBeanDefinitionException 异常。对于这种情况,我们可以选择将某一个 bean 设置为 primary,然而如果存在多个 primary 的 bean,Spring 仍将无法处理,这时便引出我们今天介绍的 @Qualifier,使用 @Qualifier 可以明确指出注入哪个 bean。


@Qualifier 注解的使用

@Qualifer 注解通常有两种用法。


依赖注入单一类型的 bean 时显式指出依赖的 bean 的名称,避免存在多个类型相同的 bean 而抛出异常。

依赖注入集合类型时为依赖进行分组。

注入单一类型 bean 的示例如下。


public class App {
    @Qualifier("bean1") // ①
    @Autowired
    private String bean;
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(App.class);
        context.refresh();
        System.out.println(context.getBean(App.class).bean);
        context.close();
    }
  // @Qualifier("bean1") ②
    @Bean
    public String bean1() {
        return "bean1";
    }
    @Bean
    public String bean2() {
        return "bean2";
    }
}


上述示例,容器中注册了两个类型为 String 的bean,在注入依赖时,使用 @Qualifier 指出需要注入 bean 的名称为 bean1,从而避免了抛出异常。注意此时等同于与在 bean1 上加入 @Qualifier("bean1") ,代码中位置①和位置②同时修改为 @Qualifier("bean") 也可以达到相同的目的。


使用 @Qualifier 为集合类型的依赖分组的示例如下。


public class App {
    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    @Qualifier
    public static @interface MyQualifierGroup{
    }
    @Autowired
    private List<String> bean12;
    @Qualifier
    @Autowired
    private List<String> bean34;
    @MyQualifierGroup
    @Autowired
    private List<String> bean56;
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(App.class);
        context.refresh();
        App app = context.getBean(App.class);
        System.out.println("bean12: "+app.bean12);
        System.out.println("bean34: "+app.bean34);
        System.out.println("bean56: "+app.bean56);
        context.close();
    }
    @Bean
    public String bean1() {
        return "bean1";
    }
    @Bean
    public String bean2() {
        return "bean2";
    }
    @Qualifier
    @Bean
    public String bean3() {
        return "bean3";
    }
    @Qualifier
    @Bean
    public String bean4() {
        return "bean4";
    }
    @MyQualifierGroup
    @Bean
    public String bean5() {
        return "bean5";
    }
    @MyQualifierGroup
    @Bean
    public String bean6() {
        return "bean6";
    }
}


上述示例中,在 Spring 容器中注册了6个 String 类型的 bean,其中 bean1,bean2 上没有加 @Qualifier ,bean3,bean4 上加了 @Qualifier 注解,bean5,bean6 上加了自定义的使用 @Qualifier 标注的注解 @MyQualifierGroup,同时在类型为 App 的 bean 中注入了三个 List<String> 类型的依赖,分别不加 @Qualifier,添加 @Qualifier,添加 @MyQualifierGroup 注解,打印结果如下所示。


bean12: [bean1, bean2, bean3, bean4, bean5, bean6]
bean34: [bean3, bean4, bean5, bean6]
bean56: [bean5, bean6]


不加 @Qualifier 注解,注入了所需类型的所有 bean,而加了 @Qualifier 注解后依赖上注解必须和我们指定的 @Qualifier 类型一致才会注入。


@Qualifier 实现简单分析


@Qualifier 作为注解,由处理注解的上下文进行处理,《掌握 Spring 必须知道的 BeanDefinition》 一文中曾提到,AnnotatedBeanDefinitionReader 会将注解信息读取为 BeanDefinition,AnnotatedBeanDefinitionReader 构造方法如下。


  public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    // 注册处理注解的处理器
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  }


实例化时,AnnotatedBeanDefinitionReader 会调用AnnotationConfigUtils#registerAnnotationConfigProcessors 向 Spring 注册一些处理注解的 BeanPostProcessor,跟踪源码如下。


  public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
      BeanDefinitionRegistry registry, @Nullable Object source) {
    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    if (beanFactory != null) {
      if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
        beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
      }
      if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
        // 注册自动注入的候选项解析器
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
      }
    }
    ... 省略部分代码
  }


注册 BeanPostProcessor 时 Spring 会先注册自动注入的候选项解析器 ContextAnnotationAutowireCandidateResolver,重点就在这个解析器中。在文章《浅析 Spring 依赖解析实现》 中提到,Spring 解析依赖时会调用 DefaultListableBeanFactory#isAutowireCandidate 判断给定类型的 bean 是否为依赖的候选项,跟踪源码如下。


  protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd,
      DependencyDescriptor descriptor, AutowireCandidateResolver resolver) {
    String beanDefinitionName = BeanFactoryUtils.transformedBeanName(beanName);
    resolveBeanClass(mbd, beanDefinitionName);
    if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) {
      new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd);
    }
    BeanDefinitionHolder holder = (beanName.equals(beanDefinitionName) ?
        this.mergedBeanDefinitionHolders.computeIfAbsent(beanName,
            key -> new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName))) :
        new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName)));
    // 解析给定的 bean 是否为自动注入的候选项
    return resolver.isAutowireCandidate(holder, descriptor);
  }


这里正是使用到了上面设置的 ContextAnnotationAutowireCandidateResolver,这个类会将 bean 上的 @Qualifier 和依赖描述符 DependencyDescriptor 中的 @Qualifier 信息进行匹配,从而对 bean 进行分组。


总结

@Qualifier 作为 Spring 官方提供的注解,在注入单一类型的 bean 时可以指定 bean 的名称,此时 @Autowired + @Qualifier = @Resource,同时在注入集合类型的 bean 时 @Qualifier 还可以为 bean 分组。


目录
相关文章
|
3月前
|
开发框架 Java Spring
Spring依赖注入以及使用建议
Spring依赖注入以及使用建议
52 0
|
3月前
|
XML Java 数据格式
Spring系列文章2:基于xml方式依赖注入
Spring系列文章2:基于xml方式依赖注入
|
1月前
|
XML Java 测试技术
Spring Boot中的依赖注入和控制反转
Spring Boot中的依赖注入和控制反转
|
3月前
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
50 3
|
3月前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
40 0
|
17天前
|
设计模式 自然语言处理 Java
简单了解下Spring中的各种Aware接口实现依赖注入
在Spring框架中,Aware接口是一组用于提供特定资源或环境信息的回调接口。这些接口被设计用来允许Bean获取对Spring容器或其他相关资源的引用,并在需要时进行适当的处理。
15 2
|
30天前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
2月前
|
设计模式 Java 测试技术
Spring Boot中的依赖注入详解
Spring Boot中的依赖注入详解
|
2月前
|
缓存 Java 测试技术
Spring 框架,不只是依赖注入和面向切面那么简单!
【6月更文挑战第25天】Spring框架超越DI和AOP,涵盖事务管理、数据访问抽象如`JdbcTemplate`、消息驱动支持如`@JmsListener`、缓存管理和测试工具。示例代码展示了其简化复杂性的能力,如自动事务处理、数据库操作及消息队列监听。Spring是构建高效企业级应用的全面解决方案。
28 4
|
2月前
|
Java Spring 容器
spring如何进行依赖注入,通过set方法把Dao注入到serves
spring如何进行依赖注入,通过set方法把Dao注入到serves