@Qualifier高级应用---按类别批量依赖注入(QualifierAnnotationAutowireCandidateResolver原理详解)【享学Spring】(上)

简介: @Qualifier高级应用---按类别批量依赖注入(QualifierAnnotationAutowireCandidateResolver原理详解)【享学Spring】(上)

前言


在上篇文章(讲解@LoadBalanced负载均衡)的末尾,我抛出了一个很重要的问题,建议小伙伴自己深入思考一番;本文主要针对此问题,作出一个统一的答复和讲解。

由于本人觉得这块知识点它属于Spring Framework的核心内容之一,非常的重要,因此单拎出来作专文讲述,希望对你有所帮助。


背景案例


说到@Qualifier这个注解大家并不陌生:它用于“精确匹配”Bean,一般用于同一类型的Bean有多个不同实例的case下,可通过此注解来做鉴别和匹配。

本以为@Qualifier注解使用在属性上、类上用于鉴别就够了,直到我看到LoadBalancerAutoConfiguration里有这么应用:


@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList(); 


能把容器内所有RestTemplate类型并且标注有@LoadBalanced注解的Bean全注入进来。

这个用法让我非常的惊喜,它给我提供额外一条思路,让我的框架多了一种玩法。为了融汇贯通它,使用起来尽量避免不采坑,那就只能揭开它,从底层原理处理解它的用法了。


QualifierAnnotationAutowireCandidateResolver详解


它是依赖注入候选处理器接口AutowireCandidateResolver的实现类,继承自GenericTypeAwareAutowireCandidateResolver,所以此类是功能最全、最为强大的一个处理器(ContextAnnotationAutowireCandidateResolver除外~),Spring内默认使用它进行候选处理。


它几乎可以被称为@Qualifier注解的"实现类",专门用于解析此注解。

带着上面的疑问进行原理分析如下:


// @since 2.5
public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwareAutowireCandidateResolver {
  // 是个List,可以知道它不仅仅只支持org.springframework.beans.factory.annotation.Qualifier
  private final Set<Class<? extends Annotation>> qualifierTypes = new LinkedHashSet<>(2);
  private Class<? extends Annotation> valueAnnotationType = Value.class;
  // 空构造:默认支持的是@Qualifier以及JSR330标准的@Qualifier
  public QualifierAnnotationAutowireCandidateResolver() {
    this.qualifierTypes.add(Qualifier.class);
    try {
      this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Qualifier", QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
    } catch (ClassNotFoundException ex) {
      // JSR-330 API not available - simply skip.
    }
  }
  // 非空构造:可自己额外指定注解类型
  // 注意:如果通过构造函数指定qualifierType,上面两种就不支持了,因此不建议使用
  // 而建议使用它提供的addQualifierType() 来添加~~~
  public QualifierAnnotationAutowireCandidateResolver(Class<? extends Annotation> qualifierType) {
  ... // 省略add/set方法  
  // 这是个最重要的接口方法~~~  判断所提供的Bean-->BeanDefinitionHolder 是否是候选的
  // (返回true表示此Bean符合条件)
  @Override
  public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
    // 1、先看父类:ean定义是否允许依赖注入、泛型类型是否匹配
    boolean match = super.isAutowireCandidate(bdHolder, descriptor);
    // 2、若都满足就继续判断@Qualifier注解~~~~
    if (match) {
      // 3、看看标注的@Qualifier注解和候选Bean是否匹配~~~(本处的核心逻辑)
      // descriptor 一般封装的是属性写方法的参数,即方法参数上的注解
      match = checkQualifiers(bdHolder, descriptor.getAnnotations());
      // 4、若Field/方法参数匹配,会继续去看看参数所在的方法Method的情况
      // 若是构造函数/返回void。 进一步校验标注在构造函数/方法上的@Qualifier限定符是否匹配
    if (match) {
        MethodParameter methodParam = descriptor.getMethodParameter();
        // 若是Field,methodParam就是null  所以这里是需要判空的
        if (methodParam != null) {
          Method method = methodParam.getMethod();
          // method == null表示构造函数 void.class表示方法返回void
          if (method == null || void.class == method.getReturnType()) {
            // 注意methodParam.getMethodAnnotations()方法是可能返回空的
            // 毕竟构造方法/普通方法上不一定会标注@Qualifier等注解呀~~~~
            // 同时警示我们:方法上的@Qualifier注解可不要乱标
            match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
          }
        }
      }
    }
    return match;
  }
  ...
}


在源码注释的地方,我按照步骤标出了它进行匹配的一个执行步骤逻辑。需要注意如下几点:


  • qualifierTypes是支持调用者自己指定的(默认只支持@Qualifier类型)
  • 只有类型匹配、Bean定义匹配、泛型匹配等全部Ok了,才会使用@Qualifier去更加精确的匹配
  • descriptor.getAnnotations()的逻辑是:

- 如果DependencyDescriptor描述的是字段(Field),那就去字段里拿注解们

- 若描述的是方法参数(MethodParameter),那就返回的是方法参数的注解

  • 步骤3的match = true表示Field/方法参数上的限定符是匹配的~


说明:能走到isAutowireCandidate()方法里来,那它肯定是标注了@Autowired注解的(才能被AutowiredAnnotationBeanPostProcessor后置处理),所以descriptor.getAnnotations()返回的数组长度至少为1

checkQualifiers()方法:


QualifierAnnotationAutowireCandidateResolver:
  // 将给定的限定符注释与候选bean定义匹配。命名中你发现:这里是负数形式,表示多个注解一起匹配
  // 此处指的限定符,显然默认情况下只有@Qualifier注解
  protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
    // 很多人疑问为何没标注注解返回的还是true?
    // 请参照上面我的解释:methodParam.getMethodAnnotations()方法是可能返回空的,so...可以理解了吧
    if (ObjectUtils.isEmpty(annotationsToSearch)) {
      return true;
    }
    SimpleTypeConverter typeConverter = new SimpleTypeConverter();
    // 遍历每个注解(一般有@Autowired+@Qualifier两个注解)
    // 本文示例的两个注解:@Autowired+@LoadBalanced两个注解~~~(@LoadBalanced上标注有@Qualifier)
    for (Annotation annotation : annotationsToSearch) {
      Class<? extends Annotation> type = annotation.annotationType();
      boolean checkMeta = true; // 是否去检查元注解
      boolean fallbackToMeta = false;
      // isQualifier方法逻辑见下面:是否是限定注解(默认的/开发自己指定的)
      // 本文的org.springframework.cloud.client.loadbalancer.LoadBalanced是返回true的
      if (isQualifier(type)) {
        // checkQualifier:检查当前的注解限定符是否匹配
        if (!checkQualifier(bdHolder, annotation, typeConverter)) {
          fallbackToMeta = true; // 没匹配上。那就fallback到Meta去吧
        } else {
          checkMeta = false; // 匹配上了,就没必要校验元数据了喽~~~
        }
      }
      // 开始检查元数据(如果上面匹配上了,就不需要检查元数据了)
      // 比如说@Autowired注解/其它自定义的注解(反正就是未匹配上的),就会进来一个个检查元数据
      // 什么时候会到checkMeta里来:如@A上标注有@Qualifier。@B上标注有@A。这个时候限定符是@B的话会fallback过来
      if (checkMeta) {
        boolean foundMeta = false;
        // type.getAnnotations()结果为元注解们:@Documented、@Retention、@Target等等
        for (Annotation metaAnn : type.getAnnotations()) {
          Class<? extends Annotation> metaType = metaAnn.annotationType();
          if (isQualifier(metaType)) {
            foundMeta = true; // 只要进来了 就标注找到了,标记为true表示从元注解中找到了
            // Only accept fallback match if @Qualifier annotation has a value...
            // Otherwise it is just a marker for a custom qualifier annotation.
            // fallback=true(是限定符但是没匹配上才为true)但没有valeu值
            // 或者根本就没有匹配上,那不好意思,直接return false~
            if ((fallbackToMeta && StringUtils.isEmpty(AnnotationUtils.getValue(metaAnn))) || !checkQualifier(bdHolder, metaAnn, typeConverter)) {
              return false;
            }
          }
        }
        // fallbackToMeta =true你都没有找到匹配的,就返回false的
        if (fallbackToMeta && !foundMeta) {
          return false;
        }
      }
    }
    // 相当于:只有所有的注解都木有返回false,才会认为这个Bean是合法的~~~
    return true;
  }
  // 判断一个类型是否是限定注解   qualifierTypes:表示我所有支持的限定符
  // 本文的关键在于下面这个判断语句:类型就是限定符的类型 or @Qualifier标注在了此注解上(isAnnotationPresent)
  protected boolean isQualifier(Class<? extends Annotation> annotationType) {
    for (Class<? extends Annotation> qualifierType : this.qualifierTypes) {
      // 类型就是限定符的类型 or @Qualifier标注在了此注解上(isAnnotationPresent)
      if (annotationType.equals(qualifierType) || annotationType.isAnnotationPresent(qualifierType)) {
        return true;
      }
    }
    return false;
  }


heckQualifiers()方法它会检查标注的所有的注解(循环遍历一个个检查),规则如下:


  • 若是限定符注解(自己就是@Qualifier或者isAnnotationPresent),匹配上了,就继续看下一个注解- 也就说@Qualifier所标注的注解也算是限定符(isQualifier() = true)
  • 若是限定符注解但是没匹配上,那就fallback。继续看看标注在它身上的限定符注解(如果有)能否匹配上,若匹配上了也成
  • 若不是限定符注解,也是走fallback逻辑
  • 总之:若不是限定符注解直接忽略。若有多个限定符注解都生效,必须全部匹配上了,才算做最终匹配上。


Tips:限定符不生效的效果不一定是注入失败,而是如果是单个的话还是注入成功的。只是若出现多个Bean它就无法起到区分的效果了,所以才会注入失败了~


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
26天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
61 1
|
26天前
|
存储 XML 缓存
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南
62 0
|
5天前
|
存储 安全 Java
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)(下)
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)
17 2
|
5天前
|
安全 Cloud Native Java
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)(上)
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)
22 2
|
5天前
|
安全 Java API
第5章 Spring Security 的高级认证技术(2024 最新版)(上)
第5章 Spring Security 的高级认证技术(2024 最新版)
29 0
|
12天前
|
数据采集 前端开发 Java
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
23 3
|
12天前
|
XML Java 数据格式
进阶注解探秘:深入Spring高级注解的精髓与实际运用
进阶注解探秘:深入Spring高级注解的精髓与实际运用
26 2
|
12天前
|
Java 关系型数据库 MySQL
高级对象装配:解析Spring创建复杂对象的秘诀
高级对象装配:解析Spring创建复杂对象的秘诀
27 0
高级对象装配:解析Spring创建复杂对象的秘诀
|
21天前
|
存储 安全 Java
Spring Security应用讲解(Java案列演示)
Spring Security应用讲解(Java案列演示)
|
27天前
|
Prometheus 监控 Cloud Native
Spring Boot 应用可视化监控
Spring Boot 应用可视化监控
15 0