发布时间:2025-06-24 19:07:58 作者:北方职教升学中心 阅读量:250
以上就是 checkQualifier 方法完整的比较流程。
说到 @Qualifier,有的小伙伴可能会觉得诧异,这也只得写一篇文章?确实,但凡有点开发经验,多多少少可能都遇到过 @Qualifier 注解的使用场景,然而,对于大部分小伙伴来说,我们平时开发遇到的 @Qualifier 注解使用场景,只是 @Qualifier 注解功能中很小的一部分而已,今天咱们就来完整的捋一捋。该方法的本质实际上去查找当前 Bean 的定义中,是否存在 qualifiedElement,如果存在,则直接读取 qualifiedElement 上的 @Qualifier 注解。
protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) { if (ObjectUtils.isEmpty(annotationsToSearch)) { return true; } SimpleTypeConverter typeConverter = new SimpleTypeConverter(); for (Annotation annotation : annotationsToSearch) { Class<? extends Annotation> type = annotation.annotationType(); boolean checkMeta = true; boolean fallbackToMeta = false; if (isQualifier(type)) { if (!checkQualifier(bdHolder, annotation, typeConverter)) { fallbackToMeta = true; } else { checkMeta = false; } } if (checkMeta) { //... } return true;}
这个方法会遍历传进来的注解,传进来的注解数组是 A 中 B 属性上的所有注解,以本文第一小节的案例为 1,这里是有两个注解,分别是 @Autowired 和 @Qualifier。如此之后,在上面第 3 步的方法中,系统就会找到这个 QualifiedElement(即 B.class),然后读取出来该类上面的注解,如果读取到了,就直接进入到第 7 步。
如果想要自定义注解去匹配 qualifier 标签中提供的多种属性,那么我们可以按照如下方式来进行配置。但问题是 @Qualifier 注解只有一个 value 属性,如果想要使用其它的属性进行匹配,那么就得使用自定义注解了(当然,这种场景实际上使用较少)。这里所谓的 DecoratedDefinition 其实就是一个 BeanDefinitionHolder,这个里边保存了一个 BeanDefinition,这种配置其实比较繁琐,一般我们很少用,给小伙伴们简单演示下,如下:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();RootBeanDefinition rbd = new RootBeanDefinition();GenericBeanDefinition bd = new GenericBeanDefinition();bd.setBeanClass(B.class);rbd.setDecoratedDefinition(new BeanDefinitionHolder(bd, "b99"));rbd.setBeanClass(B.class);ctx.registerBeanDefinition("b99", rbd);ctx.register(JavaConfig.class);ctx.refresh();
这个日常开发中应该很少用,小伙伴们了解即可。
在 isAutowireCandidate 方法,又依次调了三次 isAutowireCandidate 方法,也就是说 isAutowireCandidate 方法一共调了四次之后,将会来到关键的 QualifierAnnotationAutowireCandidateResolver#isAutowireCandidate 方法中。
1.1 指定 Bean 名称
说到 @Qualifier 注解,大家最容易想到的就是处理 Bean 注入的问题了,假设我有如下 Bean:
@Configuration@ComponentScanpublic class JavaConfig { @Bean(value = "b1") B b1() { return new B(); } @Bean("b2") B b2() { return new B(); }}
将 B 向 Spring 容器中注册了两个,名字分别是 b1 和 b2。
@Overridepublic boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { boolean match = super.isAutowireCandidate(bdHolder, descriptor); if (match) { match = checkQualifiers(bdHolder, descriptor.getAnnotations()); if (match) { MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null) { Method method = methodParam.getMethod(); if (method == null || void.class == method.getReturnType()) { match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations()); } } } } return match;}
在当前方法中,首先会调用 super.isAutowireCandidate
方法去判断这个 Bean 将来是否被允许注入到其他 Bean 中:
@Overridepublic boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { if (!super.isAutowireCandidate(bdHolder, descriptor)) { // If explicitly false, do not proceed with any other checks... return false; } return checkGenericTypeMatch(bdHolder, descriptor);}
这里又是两件事,第一个是调用父类方法进行判断,这里单纯只是判断 autowireCandidate 属性是否为 true,如果这个属性为 false,就表示这个 Bean 不能被注入到其他 Bean 中,默认情况下该属性为 true,如果想要设置这个属性为 false,则可以在 @Bean 注解中设置,如下:
@Bean(value = "b1",autowireCandidate = false)B b1() { return new B();}
从这个层面讲,本文第一小节提出来的问题还有一种解决方案,就是把 autowireCandidate 属性设置为 false。
当然,对于这个问题,其实解决方案很多,如使用 @Primary 注解、
1. 基本用法
首先和小伙伴们回顾一下 @Qualifier 注解的基本用法,基本用法我从四个方面来和大家介绍,只有先把这些基本用法捋清楚了,在看源码的时候才会有种醍醐灌顶的感觉。
isAutowireCandidate:这个方法从名字上就能看出来,判断这个 beanName 是否是一个候选的注入 beanName,很明显,这个跟本文案例相关,我们继续来看该方法。 接下来在 XML 中使用该注解:
<bean class="org.javaboy.bean.p3.B" id="b1"> <qualifier type="org.javaboy.bean.p3.MyQualifier"> <attribute key="name" value="b11"/> </qualifier></bean><bean class="org.javaboy.bean.p3.B" id="b2"> <qualifier type="org.javaboy.bean.p3.MyQualifier"> <attribute key="name" value="b22"/> </qualifier></bean>
接下来在 Bean 注入的时候,就可以使用 @MyQualifier 进行匹配了:
@Componentpublic class A { @Autowired @MyQualifier(name = "b11") B b;}
这个就表示匹配 name 属性为 b11 的 Bean。
2. 源码分析
为了小伙伴们能轻松掌握 @Qualifier 的源码,一些前置的步骤我这里就不和大家分析了,重点就看 @Qualifier 注解的处理过程,其他未尽内容,将在后续文章中我会继续和大家分享。总结一下,其实就两步:
- 先去找目标类上是否也存在 @Qualifier 注解,就是前面 7 步找 targetAnnotation 的过程,如果目标类上也存在该注解,直接做注解的比对即可,就不去管属性了。这个方法松哥感觉也是一个特别冷门的用法。
前面我们使用的是 @Qualifier 注解中的 value 属性,实际上,qualifier 标签支持更多的属性定义。
如果没有为 @Qualifier 设置 value,那么就会将 id 为 b2 的 Bean 注入进来,这个就相当于我们前面 1.2 小节的案例。
首先在 Bean 注入的时候,添加 @Qualifier 注解:
@Configuration@ComponentScanpublic class JavaConfig { @Bean(value = "b1") @Qualifier B b1() { return new B(); } @Bean("b2") B b2() { return new B(); }}
大家看到,这里给 b1 添加了 @Qualifier 注解,但是未设置任何 value,然后在需要进行 B 对象注入的地方,也添加 @Qualifier 注解:
@Componentpublic class A { @Autowired @Qualifier B b;}
这样也能解决问题。
isQualifier 方法的逻辑很简单:
protected boolean isQualifier(Class<? extends Annotation> annotationType) { for (Class<? extends Annotation> qualifierType : this.qualifierTypes) { if (annotationType.equals(qualifierType) || annotationType.isAnnotationPresent(qualifierType)) { return true; } } return false;}
这个判断是遍历 qualifierTypes 集合,将集合中的注解类型挨个拿出来和传入的参数进行比对,之所以是一个集合而不是直接拿 @Qualifier 注解做比对,是因为这个注解在 JSR-330 中也有一个实现,如果项目用到了 JSR-330 的话,那么 qualifierTypes 集合中就有两个注解。使用 @Bean 注解但是额外加配置等都能解决问题,不过本文主题是 @Qualifier,所以暂时先不和大家讨论其它方案。
首先我们自定义注解,如下:
@Retention(RetentionPolicy.RUNTIME)@Inherited@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE,ElementType.ANNOTATION_TYPE})@Qualifierpublic @interface MyQualifier { String name() default "";}
这是一个组合注解,本质上还是 @Qualifier,但是现在多了一个我们自定义的 name 属性。
- 如果还没有拿到 actualValue,并且 attributeName 是 value,并且 expectedValue 是字符串类型,然后判断 bdHolder.matchesName 中是否包含 expectedValue,这个判断实质上就是查看 bdHolder 中定义的 Bean 名称、Map 中的 key 就是 Bean 的名称,value 则是一个 Class,此时还没有实例化。
checkQualifiers 方法从名字上就能看出来,就是用来检查 @Qualifier 注解的。
checkQualifier 方法算是整个 @Qualifier 处理最为核心的部分了:
protected boolean checkQualifier( BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) { Class<? extends Annotation> type = annotation.annotationType(); RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition(); AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName()); if (qualifier == null) { qualifier = bd.getQualifier(ClassUtils.getShortName(type)); } if (qualifier == null) { // First, check annotation on qualified element, if any Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type); // Then, check annotation on factory method, if applicable if (targetAnnotation == null) { targetAnnotation = getFactoryMethodAnnotation(bd, type); } if (targetAnnotation == null) { RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd); if (dbd != null) { targetAnnotation = getFactoryMethodAnnotation(dbd, type); } } 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... } } if (targetAnnotation == null && bd.hasBeanClass()) { targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type); } } 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; // Check qualifier first if (qualifier != null) { actualValue = qualifier.getAttribute(attributeName); } if (actualValue == null) { // Fall back on bean definition attribute actualValue = bd.getAttribute(attributeName); } if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) && expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) { // Fall back on bean name (or alias) match continue; } 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()); } if (!expectedValue.equals(actualValue)) { return false; } } return true;}
来细细的说一下这个方法。
现在在 A 中想要使用 B,如下:
@Componentpublic class A { @Autowired B b;}
由于 @Autowired 注解是按照类型进行 Bean 的注入的,此时 Spring 容器中存在两个 B 实例,那么注入就会出错,通过 @Qualifier 注解我们可以指定具体想要使用哪一个 Bean:
@Componentpublic class A { @Autowired @Qualifier("b1") B b;}
这样就指定了在注入时使用 b1 这个对象了。
2.2 findAutowireCandidates
protected Map<String, Object> findAutowireCandidates( @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) { String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); //... for (String candidate : candidateNames) { if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } //... return result;}
在这个方法中,首先调用
BeanFactoryUtils.beanNamesForTypeIncludingAncestors
方法查找出 B 这种类型的所有 beanName,对于本文一开始的案例来说,这里拿到两个 beanName,分别是 b1、b2,如下图:
接下来就去遍历 candidateNames,在遍历的时候,有两个判断条件:
- isSelfReference:这个方法是判断给定的 beanName 是否自引用,即是否指向原始 bean 或者原始 bean 上的工厂方法,这个判断跟本文案例关系不大。
现在,当我想要在 A 中注入 B 的时候,可以按照如下方式来:
@Componentpublic class A { @Autowired @Qualifier("b11") B b;}
大家注意,这里 @Qualifier 注解的 value 是 b11,对应了 qualifier 标签中的 value 属性,表示将 id 为 b1 的 Bean 注入到 A 中的 b 属性上。
好了,经过上面一整套流程后,findAutowireCandidates 方法所返回的 matchingBeans 就只有一个目标 Bean 了~
3. 小结
今天和小伙伴们梳理了一下 @Qualifier 注解的作用,老实说,在源码分析的过程中,也 GET 到 Spring 许多新的玩法,感兴趣的小伙伴赶紧去试试吧~