前言
在上篇文章(讲解@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它就无法起到区分的效果了,所以才会注入失败了~