Spring之@Qualifier注解的具体使用

 更新时间:2024年08月18日 08:43:52   作者:一只懒鱼a  
本文主要介绍了Spring之@Qualifier注解的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 前言

当我们注入的依赖存在多个候选者,我们得使用一些方法来筛选出唯一候选者,否则就会抛出异常

2. demo 演示

2.1 创建接口 Car,以及两个实现其接口的子类

public interface Car {
}

@Component
public class RedCar implements Car {
}

@Component
public class WhiteCar implements Car {
}

2.2 创建实体类 Person

@Component
public class Person {

    @Autowired
    private Car car;
}

2.3 创建配置类以及 Main 类

@ComponentScan("com.test.qualifier")
public class AppConfig {

}

public class Main {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Person bean = context.getBean(Person.class);
        System.out.println(bean);

    }
}

2.4 运行main方法

启动过程,抛出异常

2.5 解决方法

  • 在 WhiteCar 或者 RedCar 所在的类上加 @Primary 注解
  • 将 private Car car 改成 private Car redCar
  • 使用 @Qualifier 注解

3. @Qualifier 注解源码

 从源码中我们知道这个注解可以作用于属性、方法、参数、类、注解上面

3.1 作用于属性上

我们以 demo 演示代码为前提,使用 @Qualifier 注解

3.1.1 改造 Person 类

@Component
public class Person {

    @Autowired
    @Qualifier("redCar")
    private Car car;

}

 3.1.2 运行main方法,查看运行结果

3.2 作用于方法上

3.2.1 创建一个接口 Animal,以及两个实现类

public interface Animal {
}

public class Dog implements Animal {
}

public class Cat implements Animal {
}

3.2.2 创建配置类

@Configuration
public class AnimalConfig {

    @Bean
    @Qualifier("mimi")
    public Cat cat(){
        return new Cat();
    }

    @Bean
    @Qualifier("wangcai")
    public Dog dog(){
        return new Dog();
    }
}

3.2.3 改造 Person 类

@Component
public class Person {

    @Autowired
    @Qualifier("redCar")
    private Car car;

    @Autowired
    @Qualifier("mimi")
    private Animal animal;

}

3.2.4 运行main方法,查看运行结果

3.3 作用于类上

3.3.1 改造 Person 和 RedCar

@Component
@Qualifier("car666")
public class RedCar implements Car {
}

@Component
public class Person {

    @Autowired
    @Qualifier("car666")
    private Car car;

    @Autowired
    @Qualifier("mimi")
    private Animal animal;

}

3.3.2 运行 main 方法,查看运行结果

3.4 作用于参数上

3.4.1 改造 Person 类

@Component
public class Person {

    @Autowired
    @Qualifier("car666")
    private Car car;

    private Animal animal;

    public Person(@Qualifier("wangcai") Animal animal) {
        this.animal = animal;
    }
}

3.4.2 运行 main 方法,查看运行结果

3.5 作用于注解上

3.5.1 创建自定义注解 NestQualifier

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface NestQualifier {
    @AliasFor(annotation = Qualifier.class, attribute = "value")
    String value() default "";

}

3.5.2 自定义注解的使用

3.5.2.1 改造 Person 类

@Component
public class Person {

    @Autowired
    @NestQualifier("redCar")
    private Car car;

    private Animal animal;

    public Person(@Qualifier("wangcai") Animal animal) {
        this.animal = animal;
    }
}

3.5.2.2 改造 Person、RedCar 类

@Component
public class Person {

    @Autowired
    @NestQualifier("car666")
    private Car car;

    private Animal animal;

    public Person(@Qualifier("wangcai") Animal animal) {
        this.animal = animal;
    }
}

@Component
@NestQualifier("car666")
public class RedCar implements Car {
}

3.5.3 运行main方法,查看运行结果

3.6 小结

  • 作用于方法上、作用于类上等于给 bean 添加了一个 alias
  • 作用于属性上、作用于参数上时等于注入指定标识符的 bean,这个标识符既可以是 beanName,也可以是 alias
  • 作用于注解上比较特殊,如果作用于方法上、作用于类上时用了包装注解,作用于属性上、作用于参数上也必须使用包装注解,否则标识符只能使用 beanName,使用 alias 会报错

4. 源码解析

4.1 QualifierAnnotationAutowireCandidateResolver#checkQualifier

protected boolean checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {

    Class<? extends Annotation> type = annotation.annotationType();
    RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();

    // 这里以@NestQualifier注解为例
    // 判断是否存在名称为com.test.qualifier.annotations.NestQualifier的AutowireCandidateQualifier
    AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
    if (qualifier == null) {
        // 判断是否存在名称为NestQualifier的AutowireCandidateQualifier
        qualifier = bd.getQualifier(ClassUtils.getShortName(type));
    }
    if (qualifier == null) {
        // 判断bd的qualifiedElement,是否存在@NestQualifier注解
        Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);

        // 判断bd的factoryMethod,是否存在@NestQualifier注解
        // PS:即@Bean标注的方法上是否存在@NestQualifier注解
        if (targetAnnotation == null) {
            targetAnnotation = getFactoryMethodAnnotation(bd, type);
        }
        // 判断bd是否存在装饰器bd,然后判断装饰器bd的factoryMethod,是否存在@NestQualifier注解
        if (targetAnnotation == null) {
            RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
            if (dbd != null) {
                targetAnnotation = getFactoryMethodAnnotation(dbd, type);
            }
        }

        // 判断bd的实际类型是否存在@NestQualifier注解
        if (targetAnnotation == null) {
            // Look for matching annotation on the target class
            if (getBeanFactory() != null) {
                try {
                    Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
                    if (beanType != null) {
                        targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
                    }
                } catch (NoSuchBeanDefinitionException ex) {
                    // Not the usual case - simply forget about the type check...
                }
            }

            // 判断bd的实际类型是否存在@NestQualifier注解,这里主要针对没有传入beanFactory的情况
            if (targetAnnotation == null && bd.hasBeanClass()) {
                targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);
            }
        }

        // 如果目标注解等于方法传入的注解,则返回true
        // 即属性注入的value值和类上或者方法上的value值一致,则返回true
        if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
            return true;
        }
    }

    Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
    if (attributes.isEmpty() && qualifier == null) {
        // If no attributes, the qualifier must be present
        return false;
    }
    for (Map.Entry<String, Object> entry : attributes.entrySet()) {
        // 获取注解的属性和属性值
        String attributeName = entry.getKey();
        Object expectedValue = entry.getValue();
        Object actualValue = null;
        // 通过qualifier获取actualValue
        if (qualifier != null) {
            actualValue = qualifier.getAttribute(attributeName);
        }
        // 通过bd获取actualValue
        if (actualValue == null) {
            // Fall back on bean definition attribute
            actualValue = bd.getAttribute(attributeName);
        }
        // 即属性注入的value值和beanName的值相等则,返回true
        if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
                expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) {
            // Fall back on bean name (or alias) match
            continue;
        }
        // actualValue等于null,qualifier不等于null,获取value的默认值
        if (actualValue == null && qualifier != null) {
            // Fall back on default, but only if the qualifier is present
            actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
        }
        if (actualValue != null) {
            actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
        }
        // 判断@NestQualifier注解设置的值是否与默认值相等
        if (!expectedValue.equals(actualValue)) {
            return false;
        }
    }
    return true;
}

4.2 通过 BeanFactoryPostProcessor 来设置上述源码中的一些值

@Component
public class FirstBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ScannedGenericBeanDefinition scannedGenericBeanDefinition = (ScannedGenericBeanDefinition) registry.getBeanDefinition("redCar");

        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName(scannedGenericBeanDefinition.getBeanClassName());

        // 设置qualifiedElement
        beanDefinition.setQualifiedElement(RedCar.class);

        AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(NestQualifier.class);
        // 通过qualifier设置actualValue
        qualifier.setAttribute("value", "whiteCar");
        beanDefinition.addQualifier(qualifier);

        // 通过bd设置actualValue
        beanDefinition.setAttribute("value", "redCar");

        registry.registerBeanDefinition("redCar", beanDefinition);

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

    }
}

PS : 被 @ComponentScan 注解扫描的带有 @Component 注解的类会被解析成ScannedGenericBeanDefinition,但是 Spring 在实例化 bean 的时候会把所有 BeanDefinition 封装成 RootBeanDefinition 处理,如果不提前改造 BeanDefinition 的话,RootBeanDefinition 属性都是默认值

到此这篇关于Spring之@Qualifier注解的具体使用的文章就介绍到这了,更多相关Spring @Qualifier注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中import java.util.Scanner的用处详解

    Java中import java.util.Scanner的用处详解

    文章主要介绍Java中的Scanner类及其常用方法next()和nextLine()的区别,next()方法在遇到空格、Tab键、回车键等分隔符时结束输入,而nextLine()方法则接收所有输入,直到遇到回车键
    2024-11-11
  • springboot 在idea中实现热部署的方法

    springboot 在idea中实现热部署的方法

    这篇文章主要介绍了springboot 在idea中实现热部署的方法,实现了热部署,在每一次作了修改之后,都会自动的重启,非常节约时间,感兴趣的小伙伴们可以参考一下
    2018-10-10
  • Map获取键值,Map的几种遍历方法总结(推荐)

    Map获取键值,Map的几种遍历方法总结(推荐)

    下面小编就为大家带来一篇Map获取键值,Map的几种遍历方法总结(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • SpringBoot redis分布式缓存实现过程解析

    SpringBoot redis分布式缓存实现过程解析

    这篇文章主要介绍了SpringBoot redis分布式缓存实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • SpringBoot+本地消息表实现分布式最终一致性

    SpringBoot+本地消息表实现分布式最终一致性

    本文主要介绍了SpringBoot+本地消息表实现分布式最终一致性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-02-02
  • java ThreadPoolExecutor线程池拒绝策略避坑

    java ThreadPoolExecutor线程池拒绝策略避坑

    这篇文章主要为大家介绍了java ThreadPoolExecutor拒绝策略避坑踩坑示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Spring Cloud接口突然变慢的解决方案

    Spring Cloud接口突然变慢的解决方案

    在Spring Cloud项目中,接口突然变慢可能是由多种原因造成的,本文给大家介绍了一些可能的原因以及相应的解决方案,通过代码示例给大家讲解的非常详细,需要的朋友可以参考下
    2024-01-01
  • java发送飞书消息卡片具体实现方法

    java发送飞书消息卡片具体实现方法

    这篇文章主要介绍了java发送飞书消息卡片具体实现方法的相关资料,Java通过飞书消息卡片API,可以高效解决传统消息推送方式的痛点,实现业务系统事件与飞书消息卡片之间的全流程闭环,需要的朋友可以参考下
    2026-03-03
  • 纯Java类配置与@Configuration实战指南

    纯Java类配置与@Configuration实战指南

    本文介绍@Configuration的核心用法,实战@Bean定义Bean、@ComponentScan扫描组件、@Import组合配置类,带大家体验“无XML”的Spring配置新方式,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • 关于ThreadLocal对request和response的用法说明

    关于ThreadLocal对request和response的用法说明

    这篇文章主要介绍了关于ThreadLocal对request和response的用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08

最新评论