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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
应用型负载均衡 ALB,每月750个小时 15LCU
简介: @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它就无法起到区分的效果了,所以才会注入失败了~


相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
6天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
16 0
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
148 1
|
20天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
95 62
|
18天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
18天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
36 2
|
1月前
|
人工智能 开发框架 Java
总计 30 万奖金,Spring AI Alibaba 应用框架挑战赛开赛
Spring AI Alibaba 应用框架挑战赛邀请广大开发者参与开源项目的共建,助力项目快速发展,掌握 AI 应用开发模式。大赛分为《支持 Spring AI Alibaba 应用可视化调试与追踪本地工具》和《基于 Flow 的 AI 编排机制设计与实现》两个赛道,总计 30 万奖金。
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用
【10月更文挑战第8天】本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,通过 Spring Initializr 创建并配置 Spring Boot 项目,实现后端 API 和安全配置。接着,使用 Ant Design Pro Vue 脚手架创建前端项目,配置动态路由和菜单,并创建相应的页面组件。最后,通过具体实践心得,分享了版本兼容性、安全性、性能调优等注意事项,帮助读者快速搭建高效且易维护的应用框架。
41 3
|
1月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
26天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0