Each one
Ross: There may be a parallel draft pick, but absolutely no MVP
Foreword
In the last article (explain @LoadBalanced load balancing) at the end, I threw out a very important issue, we recommend that small partners themselves to think deeply about it; this paper for this problem, make a unified response and explanation.
Because I think this piece of knowledge which belongs to Spring Framework
one of the core content is very important, so a single carry her out for a special article about, I hope for your help.
Case background
Speaking of @Qualifier
this comment is familiar: It is used to "exact match" Bean
, generally used under the same type of case there are a number of different instances of Bean, annotations can do this by identifying and matching.
I thought @Qualifier
annotation use on the property, the type used to identify enough, until I see LoadBalancerAutoConfiguration
there are so many applications:
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
It can in the container all RestTemplate
types and annotated with @LoadBalanced
notes of Bean full shot coming in .
This usage so I was very pleasantly surprised, it gives me extra line of thought, let me framework more than a play. For mastered it, try to avoid using them do not mine pit, it will only uncover it, to understand its use from the underlying principle place.
QualifierAnnotationAutowireCandidateResolver
Detailed
It is dependent on injecting candidate processor interface AutowireCandidateResolver
implementation class inherits from GenericTypeAwareAutowireCandidateResolver
, so these are the most full, most powerful one processor ( ContextAnnotationAutowireCandidateResolver
except in the first), Spring
the process using it for the default candidate.
It can almost be called @Qualifier
annotated "implementation class", designed to resolve this comment.
With the above questions were principle is as follows:
// @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;
}
...
}
Comments in the source code where I follow the steps marked a step execution logic to match it. Note the following:
qualifierTypes
It supports caller to specify their own (only supports the default@Qualifier
type)- Only the type of match, Bean define the match, matching all generic Ok, and will be used
@Qualifier
to more accurately match descriptor.getAnnotations()
The logic is:
- If theDependencyDescriptor
description of a field (Field
), then go to the field where they take notes
- if describes the method parameters (MethodParameter
), it returns the annotated method parameterStep 3
match = true
represents the Field / method parameter qualifiers are matched -Description: Can you go
isAutowireCandidate()
method in to, then it must be marked with@Autowired
annotations (can beAutowiredAnnotationBeanPostProcessor
post-processed), so thedescriptor.getAnnotations()
array length returns at least 1
checkQualifiers()
method:
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;
}
checkQualifiers()
Methods which examines all comments marked (loop through one check), rules are as follows:
- If the qualifier annotation (he is
@Qualifier
orisAnnotationPresent
), matches, and to continue to look at a note
- it said@Qualifier
marked notes can be considered qualifiers (isQualifier() = true
) - If the qualifier but did not comment on the match, then fallback. Continue to see marked on it qualifier notes (if any) on whether the match, if the match went into too
- If it is a qualifier annotation, but also go fallback logic
In short: If it were not a direct qualifier annotation ignored. If more than one qualifier annotations take effect, must all match on, be considered to make the final match.
Tips: qualifiers do not take effect effect is not necessarily a failure of implantation, but if it is a single word or injected success. If there is more than just that it can not be effective Bean distinction, so will inject failed ~
Its fallback
strategy had only to find the most up one level (much to die) . For example, the example uses @B also can play a marked @Qualifier
effect, but if combined with a @C
hierarchy qualifier will not enter into force.
Note:
Class.isAnnotationPresent(Class<? extends Annotation> annotationClass)
indicateannotationClass
whether the label on this type (this type can be any type Class).
This method does not transitive: for example, there are @Qualifier notes marked on the A, B notes have @A mark on notes, then you use this method to determine whether there @Qualifier on @B它是返回false的
(even have written@Inherited
notes, and because it does not matter)
In fact, to this still can not explain why in this article @LoadBalanced
participated dependency injection, have to continue to look at the essence of the essence of the checkQualifier()
method (method name is a single number that represents a precise examination of a single comment):
QualifierAnnotationAutowireCandidateResolver:
// 检查某一个注解限定符,是否匹配当前的Bean
protected boolean checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {
// type:注解类型 bd:当前Bean的RootBeanDefinition
Class<? extends Annotation> type = annotation.annotationType();
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
// ========下面是匹配的关键步骤=========
// 1、Bean定义信息的qualifiers字段一般都无值了(XML时代的配置除外)
// 长名称不行再拿短名称去试了一把。显然此处 qualifier还是为null的
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
// 1、词方法是从bd标签里拿这个类型的注解声明,非XML配置时代此处targetAnnotation 为null
Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);
// Then, check annotation on factory method, if applicable
// 2、若为null。去工厂方法里拿这个类型的注解。这方法里标注了两个注解@Bean和@LoadBalanced,所以此时targetAnnotation就不再为null了~~
if (targetAnnotation == null) {
targetAnnotation = getFactoryMethodAnnotation(bd, type);
}
// 若本类木有,还会去父类去找一趟
if (targetAnnotation == null) {
RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
if (dbd != null) {
targetAnnotation = getFactoryMethodAnnotation(dbd, type);
}
}
// 若xml、工厂方法、父里都还没找到此方法。那好家伙,回退到还去类本身上去看
// 也就是说,如果@LoadBalanced标注在RestTemplate上,也是阔仪的
if (targetAnnotation == null) {
// Look for matching annotation on the target class
...
}
// 找到了,并且当且仅当就是这个注解的时候,就return true了~
// Tips:这里使用的是equals,所以即使目标的和Bean都标注了@Qualifier属性,value值相同才行哟~~~~
// 简单的说:只有value值相同,才会被选中的。否则这个Bean就是不符合条件的
if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
return true;
}
}
// 赞。若targetAnnotation还没找到,也就是还没匹配上。仍旧还不放弃,拿到当前这个注解的所有注解属性继续尝试匹配
Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
if (attributes.isEmpty() && qualifier == null) {
return false;
}
... // 详情不描述了。这就是为什么我们吧@Qualifier标注在某个类上面都能生效的原因 就是这里做了非常强大的兼容性~
}
// =================它最重要的两个判断=================
if (targetAnnotation != null && targetAnnotation.equals(annotation));
// Fall back on bean name (or alias) match
if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
expectedValue instanceof String && bdHolder.matchesName((String) expectedValue));
checkQualifier()
Implementation enough to be seen Spring
as a good framework for its case of comprehensiveness, compatibility, flexibility considerations still in place. Because of Spring
strong support and provide flexible expansion, only to give up SpringBoot、SpringCloud
more possibilities on the frame level design ~
---
@Qualifier
Advanced Use
@Autowired
It is automatic assembly according to the type, when the same type of container Spring Bean more than one time, you need by @Qualifier
used together.
Example 1:
@Configuration
public class WebMvcConfiguration {
@Qualifier("person1")
@Autowired
public Person person;
@Bean
public Person person1() {
return new Person("fsx01", 16);
}
@Bean
public Person person2() {
return new Person("fsx02", 18);
}
}
Single test code is as follows (the same below):
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(WebMvcConfiguration.class);
WebMvcConfiguration bean = context.getBean(WebMvcConfiguration.class);
// 打印字段的值
System.out.println(bean.person);
}
After running print Person(name=fsx01, age=16)
, fully in line with expectations. This is also our @Qualifier
most regular notes, the easiest to use.
Example Two:
If you're careful you may have noticed a @Qualifier
comment that allows inheritance ( @Inherited
), can be marked on the field, the method, the method parameters, classes,注解上
.
It also does so by:
@Configuration
public class WebMvcConfiguration {
@MyAnno // 会把所有标注有此注解的Bean都收入囊中,请List装(因为会有多个)
@Autowired
public List<Person> person;
@MyAnno
@Bean
public Person person1() {
return new Person("fsx01", 16);
}
@MyAnno
@Bean
public Person person2() {
return new Person("fsx02", 18);
}
// 自定义注解:上面标注有@Qualifier注解
@Target({FIELD, METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@interface MyAnno {
}
}
Run single measure, print [Person(name=fsx01, age=16), Person(name=fsx02, age=18)]
, in line with expectations.
Three examples:
If you do not want to customize annotations directly using @Qualifier
annotations classification injection it is possible, the following cases:
@Configuration
public class WebMvcConfiguration {
@Qualifier("person2")
@Autowired
public List<Person> person;
@Qualifier("person2")
@Bean
public Person person1() {
return new Person("fsx01", 16);
}
@Qualifier
@Bean
public Person person2() {
return new Person("fsx02", 18);
}
@Qualifier
@Bean
public Person person3() {
return new Person("fsx03", 20);
}
}
The end result are running:
[Person(name=fsx01, age=16), Person(name=fsx02, age=18)]
It is the @Qualifier
same value specified value or the beanName (or aliases) are injected into the same came. This part of the matching code:
checkQualifier方法:
1、头上标注的注解完全equals(类型和value值都一样,算作匹配成功)
targetAnnotation != null && targetAnnotation.equals(annotation)
2、Fall back on bean name (or alias) match。若@Qualifier没匹配上,回退到BeanName的匹配,规则为:
取头上注解的`value`属性(必须有此属性),如果beanName/alias能匹配上次名称,也算最终匹配成功了
actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)
Note: Use in class, on the use of ginseng is relatively simple, not here to do a demonstration.
From@Qualifier
can see the details of the design, annotationvalue
property is not required, it can be a good use in the United annotations scene.
Dependency on injection and @Qualifier
use should also note the following details:
@Autowired
It may not be written up in the injection Object type field, since the inner container may be given a plurality of N found. ButList<Object>
is possible (equivalent to Bean all have to take over ~)- Can take advantage of
@Qualifier
the advanced features, on demand, by category (not type) dependency injection, this ability is very commendable, given the secondary development framework provides designers with more possibilities
If the specified value is defined in accordance with the key / match, similar to @LoadBalanced
this achievement is to be understood that annotation matching defining classified according to a class of Mo, and a higher degree of freedom.
Recommended Reading
to sum up
This article describes the @Qualifier
advanced application scenarios and case studies, by combining @LoadBalanced
this annotation is used, it should be said is to give you open up a new perspective to look at @Qualifier
, and even look at Spring
the dependency injection, which is the follow-up of understanding, custom extensions / use is still quite meaningful.
== If for Spring, SpringBoot, MyBatis source code analysis and other interested can add me wx: fsx641385712, manually invite you to take off into a group ==
== If for Spring, SpringBoot, MyBatis source code analysis and other interested can add me wx : fsx641385712, manually invite you into the group took off ==