Spring 注解原理(三)@Qualifier @Value

Spring 注解原理(三)@Qualifier @Value

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

一、AutowireCandidateResolver 接口

AutowireCandidateResolver 用来判断一个给定的 bean 是否可以注入,最主要的方法是 isAutowireCandidate

public interface AutowireCandidateResolver {
    // 判断给定的 bdHolder 是否可以注入 descriptor,BeanDefinition#autowireCandidate 默认为 true
    // DependencyDescriptor 是对字段、方法、参数的封装,便于统一处理,这里一般是对属性写方法参数的封装
    default boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
        return bdHolder.getBeanDefinition().isAutowireCandidate();
    }

    // @since 5.0 判断是否必须注入,如果是字段类型是 Optional 或有 @Null 注解时为 false
    default boolean isRequired(DependencyDescriptor descriptor) {
        return descriptor.isRequired();
    }
    // @since 5.1 判断是否有 @Qualifier(Spring 或 JDK) 或自定义的注解
    default boolean hasQualifier(DependencyDescriptor descriptor) {
        return false;
    }

    // @Value 时直接返回
    default Object getSuggestedValue(DependencyDescriptor descriptor) {
        return null;
    }
    // @since 4.0
    default Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
        return null;
    }
}

AutowireCandidateResolver 的实现有以下几个:

AutowireCandidateResolver 类图

  • SimpleAutowireCandidateResolver 相当于一个简单的适配器
  • GenericTypeAwareAutowireCandidateResolver 进一步判断泛型是否匹配
  • QualifierAnnotationAutowireCandidateResolver 处理 @Qualifier 和 @Value 注解
  • ContextAnnotationAutowireCandidateResolver 处理 @Lazy 注解,重写了 getLazyResolutionProxyIfNecessary 方法。

这里主要看一下 QualifierAnnotationAutowireCandidateResolver 是如何处理 @Qualifier 和 @Value 注解的。

二、QualifierAnnotationAutowireCandidateResolver

2.1 @Value 处理

在 Spring 中可以使用 @Value 注入配置属性

@Value("${jdbd.url}")
private String url;

@Value 的处理方式如下,查找到 @Value 的值作为 AutowireCandidateResolver#getSuggestedValue 的返回值。

private Class<? extends Annotation> valueAnnotationType = Value.class;

@Override
public Object getSuggestedValue(DependencyDescriptor descriptor) {
    // findValue 用于提取 valueAnnotationType 注解的值
    Object value = findValue(descriptor.getAnnotations());
    if (value == null) {
        MethodParameter methodParam = descriptor.getMethodParameter();
        if (methodParam != null) {
            value = findValue(methodParam.getMethodAnnotations());
        }
    }
    return value;
}

如果 getSuggestedValue 返回了值就直接返回了。

2.3 @Qualifier 处理

在使用 Spring 框架中进行自动注人时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出 BeanCreationException 异常。Spring 允许我们通过 Qualifier 指定注入 Bean 的名称,这样歧义就消除了。@Qualifier 四种用法介绍

@Autowire
@Qualifier("person")
private Person person;

2.3.1 默认处理 Spring 和 JDK 的 @Qualifier 注解

private final Set<Class<? extends Annotation>> qualifierTypes = new LinkedHashSet<>(2);
public QualifierAnnotationAutowireCandidateResolver() {
    this.qualifierTypes.add(Qualifier.class);
    try {
        // JSR-330
        this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Qualifier",
                        QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
    } catch (ClassNotFoundException ex) {
    }
}

2.3.2 isAutowireCandidate

@Override
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
    boolean match = super.isAutowireCandidate(bdHolder, descriptor);
    if (match) {
        // 检验是否满足 qualifierTypes 集合中定义的规范
        // descriptor 一般封装的是属性写方法的参数,即方法参数上的注解
        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;
}

最核心的方法是 checkQualifiers(bdHolder, descriptor.getAnnotations()),用于校验所有的 @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;
        // 1. 第一步:存在 @Qualifier
        if (isQualifier(type)) {
            if (!checkQualifier(bdHolder, annotation, typeConverter)) {
                fallbackToMeta = true;
            } else {
                checkMeta = false;
            }
        }
        // 2. 第二步:两种情况下执行,一是存在 @Qualifier 校验失败;二是不存在 @Qualifier
        if (checkMeta) {
            boolean foundMeta = false;
            for (Annotation metaAnn : type.getAnnotations()) {
                Class<? extends Annotation> metaType = metaAnn.annotationType();
                if (isQualifier(metaType)) {
                    foundMeta = true;
                    // 1. 第一步校验失败下元注解上的 @Qualifier 没有配置 value 直接返回 false
                    // 2. 不管第一步执行没有,如果检验失败直接返回 false
                    if ((fallbackToMeta && StringUtils.isEmpty(AnnotationUtils.getValue(metaAnn))) ||
                            !checkQualifier(bdHolder, metaAnn, typeConverter)) {
                        return false;
                    }
                }
            }
            // 第一步说明存在 @Qualifier 则必须校验成功,否则直接返回
            if (fallbackToMeta && !foundMeta) {
                return false;
            }
        }
    }
    return true;
}

最后一步是 checkQualifier(bdHolder, annotation, typeConverter) 将 bd.qualifiers 的信息和当前 @Qualifier 注解的信息进行对比,看是否一致。大多数情况下 bd.qualifiers 为空,除非手动进行以下配置:

<bean id="mysqlDataSourceBean" class="com.bean.MysqlDriveManagerDataSource">
    <!-- qualifier 标签默认 type=org.springframework.beans.factory.annotation.Qualifier -->
    <qualifier value="mysqlDataSource"/>
</bean>

具体较验如下:

protected boolean checkQualifier(
        BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {

    Class<? extends Annotation> type = annotation.annotationType();
    RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();

    // 1. 获取 BeanDefinition 中的 qualifier
    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;
        }
    }

    // 2. bd.qualifier 中的属性和 @Qualifier 中配置的进行对比
    //    attributes 为 @Qualifier 注解信息,value 有一个默认值 ""
    //    qualifier 为 bd 中的配置,默认为 null
    Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
    if (attributes.isEmpty() && qualifier == null) {
        // 当注解中属性值为空时,如自定义的 @Qualifier,此时 bd.qualifier 必须存在
        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);
        }
        // 如果 actualValue=null(如没有 qualifier),默认是和 bdHolder 中 bean 的名称进行比较
        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;
}

总结一下,bd.qualifiers 的信息和当前 @Qualifier 注解校验规则如下:

  1. 获取 BeanDefinition 中定义的 qualifier 属性,如果没有配置,则尝试从其 factory-method 或 beanClass 上获取 @Qualifier 注解(可能为自定义),如果获取到就直接返回。

  2. 我们使用的大多数场景是没有配置 BeanDefinition#qualifier 属性的,也就是说 qualifier=null,下面进一步比较 bd.qualifier 中的属性和 @Qualifier 中的属性信息 attributes。如果注解没有属性字段,则必须配置 BeanDefinition#qualifier

  3. 将 bd.qualifier 和 attributes 中的属性字段逐一对比,默认是和 bd.beanName 进行对比,如果值不相等则抛出异常,也就是说可以在自定义注解中定义多个字段进行校验。如:

// 自定义的没有字段信息则必须显示的配置在 bd.qualifier 中
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
private static @interface TestQualifier {
}

// 会逐一对比 @TestQualifier 和 bd 中的字段信息,必须相等才能匹配
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
private static @interface TestQualifier {
    String specialName default "";
}

参考:

  1. 《Spring 注解实现 Bean 依赖注入之 @Qualifier》:https://blog.csdn.net/lovin_fang/article/details/78537547

每天用心记录一点点。内容也许不重要,但习惯很重要!

猜你喜欢

转载自www.cnblogs.com/binarylei/p/10428999.html