@Qualifier advanced applications --- batch dependency injection by category enjoy learning [Spring]

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 Frameworkone 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 @Qualifierthis 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 @Qualifierannotation use on the property, the type used to identify enough, until I see LoadBalancerAutoConfigurationthere are so many applications:

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

It can in the container all RestTemplatetypes and annotated with @LoadBalancednotes 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.


QualifierAnnotationAutowireCandidateResolverDetailed

It is dependent on injecting candidate processor interface AutowireCandidateResolverimplementation class inherits from GenericTypeAwareAutowireCandidateResolver, so these are the most full, most powerful one processor ( ContextAnnotationAutowireCandidateResolverexcept in the first), Springthe process using it for the default candidate.

It can almost be called @Qualifierannotated "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:

  • qualifierTypesIt supports caller to specify their own (only supports the default @Qualifiertype)
  • Only the type of match, Bean define the match, matching all generic Ok, and will be used @Qualifierto more accurately match
  • descriptor.getAnnotations()The logic is:
    - If the DependencyDescriptordescription of a field ( Field), then go to the field where they take notes
    - if describes the method parameters ( MethodParameter), it returns the annotated method parameter
  • Step 3 match = truerepresents the Field / method parameter qualifiers are matched -

    Description: Can you go isAutowireCandidate()method in to, then it must be marked with @Autowiredannotations (can be AutowiredAnnotationBeanPostProcessorpost-processed), so the descriptor.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 @Qualifieror isAnnotationPresent), matches, and to continue to look at a note
    - it said @Qualifiermarked 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 fallbackstrategy had only to find the most up one level (much to die) . For example, the example uses @B also can play a marked @Qualifiereffect, but if combined with a @Chierarchy qualifier will not enter into force.

Note: Class.isAnnotationPresent(Class<? extends Annotation> annotationClass)indicate annotationClasswhether 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 @Inheritednotes, and because it does not matter)

In fact, to this still can not explain why in this article @LoadBalancedparticipated 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 Springas a good framework for its case of comprehensiveness, compatibility, flexibility considerations still in place. Because of Springstrong support and provide flexible expansion, only to give up SpringBoot、SpringCloudmore possibilities on the frame level design ~

---


@QualifierAdvanced Use

@AutowiredIt is automatic assembly according to the type, when the same type of container Spring Bean more than one time, you need by @Qualifierused 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 @Qualifiermost regular notes, the easiest to use.

Example Two:

If you're careful you may have noticed a @Qualifiercomment 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 @Qualifierannotations 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 @Qualifiersame 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 @Qualifiercan see the details of the design, annotation valueproperty is not required, it can be a good use in the United annotations scene.

Dependency on injection and @Qualifieruse should also note the following details:

  1. @AutowiredIt may not be written up in the injection Object type field, since the inner container may be given a plurality of N found. But List<Object>is possible (equivalent to Bean all have to take over ~)
  2. Can take advantage of @Qualifierthe 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 @LoadBalancedthis achievement is to be understood that annotation matching defining classified according to a class of Mo, and a higher degree of freedom.

Recommended Reading

Why a @LoadBalanced annotation can make RestTemplate have load balancing capabilities? [Learn to enjoy Spring Cloud]

to sum up

This article describes the @Qualifieradvanced application scenarios and case studies, by combining @LoadBalancedthis annotation is used, it should be said is to give you open up a new perspective to look at @Qualifier, and even look at Springthe 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 ==

Guess you like

Origin www.cnblogs.com/fangshixiang/p/11532717.html