深入理解Spring註解機制(二):元註解解析與屬性映射

前言

衆所周知,spring 從 2.5 版本以後開始支持使用註解代替繁瑣的 xml 配置,到了 springboot 更是全面擁抱了註解式配置。平時在使用的時候,點開一些常見的等註解,會發現往往在一個註解上總會出現一些其他的註解,比如 @Service

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Component
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

大部分情況下,我們可以將 @Service 註解等同於 @Component 註解使用,則是因爲 spring 基於其 JDK 對元註解的機制進行了擴展。

在 java 中,元註解是指可以註解在其他註解上的註解,spring 中通過對這個機制進行了擴展,實現了一些原生 JDK 不支持的功能,比如允許在註解中讓兩個屬性互爲別名,或者將一個帶有元註解的子註解直接作爲元註解看待,或者在這個基礎上,通過 @AliasFor 或者同名策略讓子註解的值覆蓋元註解的值。

本文將基於 spring 源碼 5.2.x 分支,解析 spring 如何實現這套功能的。

這是系列的第二篇文章,將詳細介紹 Spring 是如何解析 @AliasFor,實現各種別名功能。

相關文章:

一、創建合併註解聚合

1、入口

AnnotatedElementUtils 這個工具類中,所有帶有 Merged 關鍵字的方法皆用於提供合併註解支持。

所謂合併註解,其實就是以前文提到 MergedAnnotation 爲基礎實現的一系列功能,包括:

  • 對基於 @AliasFor 註解屬性別名機制的支持;
  • 對註解及元註解的合成支持;

我們可以點開 AnnotatedElementUtils 工具類中的常用方法 findMergedAnnotation

public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
    // 1、下述任意情況下直接獲取元素上聲明的註解:
    // a.查找的註解屬於java、javax或者org.springframework.lang包
    // b.被處理的元素屬於java包,或被java包中的對象聲明,或者就是Ordered.class
    if (AnnotationFilter.PLAIN.matches(annotationType) ||
        AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
        return element.getDeclaredAnnotation(annotationType);
    }

    // 2、將元素上的全部註解合成MergedAnnotation
    return findAnnotations(element)
        // 3、從MergedAnnotation獲取與該類型對應的MergedAnnotations
        .get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
        // 4、根據MergedAnnotation通過動態代理生成一個註解實例
        .synthesize(MergedAnnotation::isPresent).orElse(null);
}

大體過程分三步:

  • 通過 findAnnotations 獲得合併註解聚合 MergedAnnotations,該對象表示與指定 AnnotatedElement 關聯的全部註解的聚合體;
  • MergedAnnotations 通過 get 方法獲取符合條件的合併註解 MergedAnnotation,該過程將從AnnotatedElement 關聯的全部註解中選出所需的註解類型,然後解析其各種映射關係,並變爲一個合併註解;
  • 然後將該合併註解通過 synthesize 方法合成爲一個符合條件的普通註解,該過程將基於處理後的合併註解,使用 JDK 動態代理生成一個指定註解類型的代理對象;

這裏我們重點關注 findMergedAnnotation 方法,以及調用 MergedAnnotations.get 方法後,合併註解聚合是如何在獲得層級結構中的註解後,對其元註解和相關屬性的解析的。

2、TypeMappedAnnotations

AnnotatedElementUtils.findAnnotations 獲取了一個 MergedAnnotations 對象,該方法經過一系列的跳轉,最終會得到一個 TypeMappedAnnotations 實現類實例:

static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
                              RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
    // 該元素若符合下述任一情況,則直接返回空註解:
    // a.被處理的元素屬於java包、被java包中的對象聲明,或者就是Ordered.class
    // b.只查找元素直接聲明的註解,但是元素本身沒有聲明任何註解
    // c.查找元素的層級結構,但是元素本身沒有任何層級結構
    // d.元素是橋接方法
    if (AnnotationsScanner.isKnownEmpty(element, searchStrategy)) {
        return NONE;
    }
    // 5、返回一個具體的實現類實例
    return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers, annotationFilter);
}

在前文我們知道,TypeMappedAnnotationsMergedAnnotations 接口的默認實現,他表示由 AnnotationScanner 從同一個 AnnotatedElement 上掃描出來的註解們轉爲的一批合併註解 MergedAnnotation

舉個例子,假如現有 AnnotatedElement 對象 Foo.class,他上面有一些註解,則理論上轉爲 MergedAnnotations 的過程如下:

image-20220813133938406

不過當 TypeMappedAnnotations 創建以後,內部的 MergedAnnotation 並沒有真正的被創建,而是需要等到調用 TypeMappedAnnotations 纔會完成註解的搜索、解析與合併過程,因此在這個階段,一個 TypeMappedAnnotations 只能表示一組來直接或間接自於同一個 AnnotatedElement 的註解之間的映射關係。

二、元註解的解析

TypeMappedAnnotations 創建後需要等到調用時纔會初始化,當調用 MergedAnnotations.get 方法時,會創建一個 MergedAnnotationFinder 用於獲取符合條件的 MergedAnnotation

public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
                                                      @Nullable Predicate<? super MergedAnnotation<A>> predicate,
                                                      @Nullable MergedAnnotationSelector<A> selector) {

    if (this.annotationFilter.matches(annotationType)) {
        return MergedAnnotation.missing();
    }
    MergedAnnotation<A> result = scan(annotationType,
                                      new MergedAnnotationFinder<>(annotationType, predicate, selector));
    return (result != null ? result : MergedAnnotation.missing());
}

關於 AnnotationScanner 是如何使用 MergedAnnotationFinder 的過程在上文已經詳細介紹了,這裏就不再贅述,我們直接跳到 MergedAnnotationFinder.process 方法:

@Nullable
private MergedAnnotation<A> process(
    Object type, int aggregateIndex, @Nullable Object source, Annotation annotation) {

    // ... ...

    // 獲取這個註解的元註解,並將自己及這些元註解都轉爲類型映射AnnotationTypeMappings
    AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
        annotation.annotationType(), repeatableContainers, annotationFilter);
    for (int i = 0; i < mappings.size(); i++) {
        AnnotationTypeMapping mapping = mappings.get(i);
        if (isMappingForType(mapping, annotationFilter, this.requiredType)) {
            // 根據符合條件的類型映射對象,創建聚合註解
            MergedAnnotation<A> candidate = TypeMappedAnnotation.createIfPossible(
                mapping, source, annotation, aggregateIndex, IntrospectionFailureLogger.INFO);
            // ... ...
        }
    }
    return null;
}

這裏我們需要重點關注 AnnotationTypeMappingsAnnotationTypeMapping 的創建,這兩者纔是真正用於解析與維護原始註解對象信息的主題。

1、創建元註解聚合體

首先先給出定義,AnnotationTypeMappings 用於表示某一個註解類上全部元註解,對應的還有一個 AnnotationTypeMapping ,它表示一個具體的元註解對象。

AnnotationTypeMappingsMergedAnnotations 的設計思路一樣,它表示一組 AnnotationTypeMapping 對象的聚合狀態,同時用於提供對 AnnotationTypeMapping 的創建和搜索等功能。

某種程度上來說,AnnotationTypeMappings 其實就是一個註解類的元註解結合體。

我們看 AnnotationTypeMappings.forAnnotationType 靜態方法,該方法用於根據一個註解類型創建 AnnotationTypeMappings 對象實例:

static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
                                                RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {

    // 針對可重複註解的容器緩存
    if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
        return standardRepeatablesCache.computeIfAbsent(annotationFilter,
                                                        key -> new Cache(repeatableContainers, key)).get(annotationType);
    }
    // 針對不可重複註解的容器緩存
    if (repeatableContainers == RepeatableContainers.none()) {
        return noRepeatablesCache.computeIfAbsent(annotationFilter,
                                                  key -> new Cache(repeatableContainers, key)).get(annotationType);
    }
    // 創建一個AnnotationTypeMappings實例
    return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType);
}

而一切的祕密都在 AnnotationTypeMappings 的構造方法中:

private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
                               AnnotationFilter filter, Class<? extends Annotation> annotationType) {

    this.repeatableContainers = repeatableContainers; // 可重複註解的容器
    this.filter = filter; // 過濾
    this.mappings = new ArrayList<>(); // 映射關係
    addAllMappings(annotationType); // 解析當前類以及其元註解的層次結構中涉及到的全部映射關係
    this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet); // 映射關係解析完後對別名的一些校驗
}

這裏重點分爲兩步:

  • 調用 AnnotationTypeMappings.addAllMappings 方法,解析入參註解類型的全部元註解,將其轉爲 AnnotationTypeMapping 對象;
  • 調用全部已解析好的 AnnotationTypeMapping 對象的 afterAllMappingsSet 方法,做一些基本的校驗;

2、收集元註解

AnnotationTypeMappings 創建時需要重點關注 AnnotationTypeMappings.addAllMappings 方法,該方法實際上就是元註解解析的主體,用於根據廣度優先,把一個註解類上的全部元註解都轉爲 AnnotationTypeMapping 並加入 AnnotationTypeMappings 中:

private void addAllMappings(Class<? extends Annotation> annotationType) {
    // 廣度優先遍歷註解和元註解
    Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
    addIfPossible(queue, null, annotationType, null); // 1.1 添加待解析的元註解
    while (!queue.isEmpty()) {
        AnnotationTypeMapping mapping = queue.removeFirst();
        this.mappings.add(mapping);
        // 繼續解析下一層
        addMetaAnnotationsToQueue(queue, mapping);  // 1.2 解析的元註解
    }
}

// 1.1 添加待解析的元註解
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,
                           Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
    try {
        // 將數據源、元註解類型和元註解實例封裝爲一個AnnotationTypeMapping,作爲下一次處理的數據源
        queue.addLast(new AnnotationTypeMapping(source, annotationType, ann));
    }
    catch (Exception ex) {
        AnnotationUtils.rethrowAnnotationConfigurationException(ex);
        if (failureLogger.isEnabled()) {
            failureLogger.log("Failed to introspect meta-annotation " + annotationType.getName(),
                              (source != null ? source.getAnnotationType() : null), ex);
        }
    }
}

// 1.2 解析的元註解
private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source) {
    // 獲取當前註解上直接聲明的元註解
    Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false);
    for (Annotation metaAnnotation : metaAnnotations) {
        // 若已經解析過了則跳過,避免“循環引用”
        if (!isMappable(source, metaAnnotation)) {
            continue;
        }
        // a.若當前正在解析的註解是容器註解,則將內部的可重複註解取出解析
        Annotation[] repeatedAnnotations = this.repeatableContainers.findRepeatedAnnotations(metaAnnotation);
        if (repeatedAnnotations != null) {
            for (Annotation repeatedAnnotation : repeatedAnnotations) {
                // 1.2.1 判斷是否已經完成映射
                if (!isMappable(source, repeatedAnnotation)) {
                    continue;
                }
                addIfPossible(queue, source, repeatedAnnotation);
            }
        }
        // b.若當前正在解析的註解不是容器註解,則將直接解析
        else {
            addIfPossible(queue, source, metaAnnotation);
        }
    }
}

// 1.2.1 判斷是否已經完成映射
private boolean isMappable(AnnotationTypeMapping source, @Nullable Annotation metaAnnotation) {
    return (metaAnnotation != null && !this.filter.matches(metaAnnotation) &&
            !AnnotationFilter.PLAIN.matches(source.getAnnotationType()) &&
            !isAlreadyMapped(source, metaAnnotation));
}
private boolean isAlreadyMapped(AnnotationTypeMapping source, Annotation metaAnnotation) {
    Class<? extends Annotation> annotationType = metaAnnotation.annotationType();
    // 遞歸映射表,確定這個註解類型是否在映射表的樹結構中存在
    // 這個做法相當於在循環引用中去重
    AnnotationTypeMapping mapping = source;
    while (mapping != null) {
        if (mapping.getAnnotationType() == annotationType) {
            return true;
        }
        mapping = mapping.getSource();
    }
    return false;
}

解析後的 AnnotationTypeMappings 大概可以參考下圖:

image-20220813133630338

不過這個圖仍然不夠準確,因爲 AnnotationTypeMapping 之間還會維持一個彼此間的引用關係,從而保證 AnnotationTypeMapping 彼此之間也能夠區分父子關係。

3、解析元註解

AnnotationTypeMapping 直譯叫做註解類型映射,之所以叫映射,是因爲一個類型映射對象總是跟一個元註解一一對應,它持有原始註解的引用,此外還會記錄註解屬性以及其源註解的一些信息。

實際上,@AliasFor 以及其他註解屬性的映射也在這裏完成,不過本節先重點關注其本身的屬性:

AnnotationTypeMapping(@Nullable AnnotationTypeMapping source,
                      Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {

    this.source = source; // 聲明當前元註解的源註解映射對象
    this.root = (source != null ? source.getRoot() : this); // 當前元註解所在樹根節點對應的元註解映射對象
    this.distance = (source == null ? 0 : source.getDistance() + 1); // 與樹根節點對應的元註解映射對象的距離
    this.annotationType = annotationType; // 當前元註解的類型
    this.metaTypes = merge( // 記錄全部子元註解類型
        source != null ? source.getMetaTypes() : null,
        annotationType);
    this.annotation = annotation; // 記錄對原始元註解的引用

    // 一些屬性解析和處理......
}

private static <T> List<T> merge(@Nullable List<T> existing, T element) {
    if (existing == null) {
        return Collections.singletonList(element);
    }
    List<T> merged = new ArrayList<>(existing.size() + 1);
    merged.addAll(existing);
    merged.add(element);
    return Collections.unmodifiableList(merged);
}

因此,通過構造函數不難看出,AnnotationTypeMapping 之間其實會形成一個類似單向鏈表的結構,我們根據此調整上一節末尾給出的圖例:

image-20220813142108900

至此,通過 AnnotationTypeMappings 可以直接管理所有的 AnnotationTypeMapping,而通過獨立的 AnnotationTypeMapping,又可以追溯元註解之間的父子關係。

三、屬性解析

通過上文,我們分析完元註解的解析問題,通過 AnnotationTypeMappingsAnnotationTypeMapping 都可以完成的元註解樹結構的訪問,不過仍然還沒說清楚Spring 支持的 @AliasFor 以及基於元註解的各種屬性映射機制是怎麼實現的。

這些涉及註解屬性的映射,都是在 AnnotationTypeMapping 創建時,在構造方法裏通過解析註解屬性,以及判斷元註解之間關聯關係完成的。

繼續看 AnnotationTypeMapping 的構造函數中屬性解析解析部分:

AnnotationTypeMapping(@Nullable AnnotationTypeMapping source,
                      Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
	// ================ 元註解解析相關的屬性 ================
    this.source = source;
    this.root = (source != null ? source.getRoot() : this);
    this.distance = (source == null ? 0 : source.getDistance() + 1);
    this.metaTypes = merge(
        source != null ? source.getMetaTypes() : null,
        annotationType);
    this.annotationType = annotationType;
    this.annotation = annotation;
    
    // ================ 屬性解析 ================
    // 將當前元註解的屬性解析爲AttributeMethods
    this.attributes = AttributeMethods.forAnnotationType(annotationType);
    // 屬性別名與相關的值緩存
    this.mirrorSets = new MirrorSets();
    this.aliasMappings = filledIntArray(this.attributes.size());
    this.conventionMappings = filledIntArray(this.attributes.size());
    this.annotationValueMappings = filledIntArray(this.attributes.size());
    this.annotationValueSource = new AnnotationTypeMapping[this.attributes.size()];
    this.aliasedBy = resolveAliasedForTargets();

    // 初始化別名屬性,爲所有存在別名的屬性建立MirrorSet
    processAliases();
    // 爲當前註解內互爲併名的屬性建立屬性映射
    addConventionMappings();
    // 爲跨註解互爲別名的屬性建立屬性映射
    addConventionAnnotationValues();
    this.synthesizable = computeSynthesizableFlag();
}

關於屬性解析部分,大概分爲五部分內容:

  1. 解析註解屬性;解析註解的屬性,將其轉爲 AttributeMethods 對象;
  2. 解析@AliasFor註解:基於 AttributeMethods 對象,解析註解帶有 @AliasFor 註解的屬性;
  3. 映射互爲別名的屬性:爲該註解內通過 @AliasFor 形成互爲別名關係的屬性設置對應的 MirrorSet
  4. 映射子註解對元註解屬性的別名關係:將子註解中通過 @AliasFor 指向父註解的屬性的屬性值,覆蓋到父註解的對應屬性上;
  5. 令子註解覆蓋父註解的同名屬性:將子註解中與父註解同名的屬性的屬性值,覆蓋到父註解的對應屬性上;

1、解析無別名註解屬性

屬性解析的第一步,在 AnnotationTypeMapping 中,註解的屬性會被解析爲 AttributeMethods 對象:

static AttributeMethods forAnnotationType(@Nullable Class<? extends Annotation> annotationType) {
    if (annotationType == null) {
        return NONE;
    }
    return cache.computeIfAbsent(annotationType, AttributeMethods::compute);
}

private static AttributeMethods compute(Class<? extends Annotation> annotationType) {
    Method[] methods = annotationType.getDeclaredMethods();
    int size = methods.length;
    for (int i = 0; i < methods.length; i++) {
        if (!isAttributeMethod(methods[i])) {
            methods[i] = null;
            size--;
        }
    }
    if (size == 0) {
        return NONE;
    }
    Arrays.sort(methods, methodComparator);
    Method[] attributeMethods = Arrays.copyOf(methods, size);
    return new AttributeMethods(annotationType, attributeMethods);
}

private static boolean isAttributeMethod(Method method) {
    return (method.getParameterCount() == 0 && method.getReturnType() != void.class);
}

這個類本質上就是通過 Class.getDeclaredMethods 獲取到的註解屬性的 Method 數組,在 AnnotationTypeMapping 中,所有的屬性都通過它在 AttributeMethods 中的數組下標訪問和調用。

image-20220813145531624

在構造函數中,我們也能看到提前聲明瞭好幾個數組:

this.aliasMappings = filledIntArray(this.attributes.size());
this.conventionMappings = filledIntArray(this.attributes.size());
this.annotationValueMappings = filledIntArray(this.attributes.size());
this.annotationValueSource = new AnnotationTypeMapping[this.attributes.size()];
private static int[] filledIntArray(int size) {
    int[] array = new int[size];
    Arrays.fill(array, -1);
    return array;
}

這些數組都與屬性映射有關,任何一個屬性的相關映射信息,都可以通過其在 AttributeMethods 中對應的數組下標,從這些關聯的數組對應位置獲得。

2、解析帶@AliasFor的別名屬性

屬性解析的第二步,在 AnnotationTypeMapping.resolveAliasedForTargets 方法中,AnnotationTypeMapping 會將所有帶有 @AliasFor 註解,或者被子註解直接/間接通過 @AliasFor 指向的屬性都解析到一個名爲 aliasedBy 的類型爲 Map<Method, List<Method>> 的成員變量中:

private Map<Method, List<Method>> resolveAliasedForTargets() {
    Map<Method, List<Method>> aliasedBy = new HashMap<>();
    for (int i = 0; i < this.attributes.size(); i++) {
        // 遍歷當前註解的屬性方法,並獲取其中的帶有@AliasFor的方法
        Method attribute = this.attributes.get(i);
        AliasFor aliasFor = AnnotationsScanner.getDeclaredAnnotation(attribute, AliasFor.class);
        if (aliasFor != null) {
            // 獲取別名指定的註解類中的方法,並建立別名屬性 -> [屬性1]的映射集合
            Method target = resolveAliasTarget(attribute, aliasFor);
            aliasedBy.computeIfAbsent(target, key -> new ArrayList<>()).add(attribute);
        }
    }
    return Collections.unmodifiableMap(aliasedBy);
}

private Method resolveAliasTarget(Method attribute, AliasFor aliasFor) {
    return resolveAliasTarget(attribute, aliasFor, true);
}

resolveAliasTarget 最終將獲得@AliasFor註解所指定的別名方法,具體如下:

private Method resolveAliasTarget(Method attribute, AliasFor aliasFor, boolean checkAliasPair) {
    if (StringUtils.hasText(aliasFor.value()) && StringUtils.hasText(aliasFor.attribute())) {
        throw new AnnotationConfigurationException(String.format(
            "In @AliasFor declared on %s, attribute 'attribute' and its alias 'value' " +
            "are present with values of '%s' and '%s', but only one is permitted.",
            AttributeMethods.describe(attribute), aliasFor.attribute(),
            aliasFor.value()));
    }

    // 1、若Annotation指定的是Annotation,則認爲目標就是當前註解類
    Class<? extends Annotation> targetAnnotation = aliasFor.annotation();
    if (targetAnnotation == Annotation.class) {
        targetAnnotation = this.annotationType;
    }

    // 2、獲取aliasFrom#attribute,若爲空則再獲取aliasFrom#value
    String targetAttributeName = aliasFor.attribute();
    if (!StringUtils.hasLength(targetAttributeName)) {
        targetAttributeName = aliasFor.value();
    }
    if (!StringUtils.hasLength(targetAttributeName)) {
        targetAttributeName = attribute.getName();
    }

    // 3、從指定類中獲得別名指定指定的註解屬性對應的方法
    Method target = AttributeMethods.forAnnotationType(targetAnnotation).get(targetAttributeName);
    if (target == null) {
        // a.校驗是否能找到別名方法
        if (targetAnnotation == this.annotationType) {
            throw new AnnotationConfigurationException(String.format(
                "@AliasFor declaration on %s declares an alias for '%s' which is not present.",
                AttributeMethods.describe(attribute), targetAttributeName));
        }
        throw new AnnotationConfigurationException(String.format(
            "%s is declared as an @AliasFor nonexistent %s.",
            StringUtils.capitalize(AttributeMethods.describe(attribute)),
            AttributeMethods.describe(targetAnnotation, targetAttributeName)));
    }
    // b.校驗別名與原屬性對應的方法是否不爲一個方法
    if (target.equals(attribute)) {
        throw new AnnotationConfigurationException(String.format(
            "@AliasFor declaration on %s points to itself. " +
            "Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
            AttributeMethods.describe(attribute)));
    }
    // c.校驗別名與原屬性對應的方法返回值是否一致
    if (!isCompatibleReturnType(attribute.getReturnType(), target.getReturnType())) {
        throw new AnnotationConfigurationException(String.format(
            "Misconfigured aliases: %s and %s must declare the same return type.",
            AttributeMethods.describe(attribute),
            AttributeMethods.describe(target)));
    }
    // d.若有必要,則再校驗聲明別名方法的註解是@AliasFor指定的註解類型
    if (isAliasPair(target) && checkAliasPair) {
        AliasFor targetAliasFor = target.getAnnotation(AliasFor.class);
        if (targetAliasFor != null) {
            Method mirror = resolveAliasTarget(target, targetAliasFor, false);
            if (!mirror.equals(attribute)) {
                throw new AnnotationConfigurationException(String.format(
                    "%s must be declared as an @AliasFor %s, not %s.",
                    StringUtils.capitalize(AttributeMethods.describe(target)),
                    AttributeMethods.describe(attribute), AttributeMethods.describe(mirror)));
            }
        }
    }
    return target;
}

在這一步,他做了以下邏輯處理:

  1. 確定別名屬性所在的註解類:若 @AliasFor.annotation 屬性保持默認值 Annotation.class,則認爲別名屬性所在的註解就是當前解析的註解;
  2. 確定別名屬性對應的方法名:優先獲取 @aliasFrom.attribute 同名屬性,若 @AliasFrom.attribute 爲空則獲取 @AliasFrom.value 指定的屬性名;
  3. 從指定的註解類獲取方法名對應的屬性;
  4. 校驗該別名方法對應方法是否不是當前註解屬性的方法;
  5. 校驗別名方法返回值類型與當前註解屬性的方法返回值類型是否一致;
  6. 校驗聲明該方法的類就是註解指定的註解類;

最終,完成這一步後,將構建出以別名方法作爲 key,當前註解中對應的原始屬性的方法作爲 value的別名屬性-原始屬性映射表 aliasedBy

這裏有個比較有意思的地方,@AliasFor 註解中, valueattribute 屬性同樣存在 @AliasFor 註解,但是實際上這個註解是不生效的,因爲在 Spring 在這邊的實現實際上並沒有讓 @AliasFor 支持類似自舉的機制。

另外,更有意思是,根據這些條件,你可以看出來,@AliasFor 不是一定要成對使用的,實際只要有一個 @AliasFor 出現,鏡像關係就可以構建,如果你願意,在不違背上述條件的情況下甚至可以同時有多個關聯的別名字段:

@Retention(RetentionPolicy.RUNTIME)
@interface AttributeMetaMeta {
    String value() default "";
    @AliasFor(attribute = "value")
    String alias1() default "";
    @AliasFor(attribute = "value")
    String alias2() default "";
}

對任意一個字段賦值等同於給所有字段賦值。

四、映射屬性別名

Spring 中,支持令同一註解中的兩個屬性——不過在上文證明其實也支持多個——形成別名,即只要任意兩個屬性中的至少一個使用 @AliasFor 指向對方,則對其中一個屬性的賦值,另一個屬性也會得到。

而這些別名屬性的映射關係,都會在 processAliases 完成解析:

private void processAliases() {
    List<Method> aliases = new ArrayList<>();
    // 遍歷當前註解中的屬性,處理屬性與其相關的別名
    for (int i = 0; i < this.attributes.size(); i++) {
        aliases.clear(); // 複用集合避免重複創建
        aliases.add(this.attributes.get(i));
        // 1.收集註解
        collectAliases(aliases); 
        if (aliases.size() > 1) {
            // 2.處理註解
            processAliases(i, aliases);
        }
    }
}

在這裏,AnnotationTypeMapping 會遍歷 AnnotationAttributes ,然後一次處理每一個註解屬性,而這裏分爲對別名屬性的收集和處理過程:

  • 收集關聯屬性:從當前元註解的根註解,也就是 root 開始,一層一層的向上找,將所有直接或間接與當前註解屬性相關的,當前以及其他註解的屬性;
  • 處理關聯屬性:根據蒐集到的屬性上的 @AliasFor 註解,如果它們在同一註解中形成了別名關係,則爲它們創建 MirrorSet 集合,構建彼此間的映射關係;

接下來我們來詳細的分析這兩個過程。

1、收集關聯的別名屬性

收集註解這一步,將以當前註解的某個屬性爲根屬性,根據鏈表結構向子註解遞歸,從子註解中獲取全部與該屬性相關的註解:

private void collectAliases(List<Method> aliases) {
    AnnotationTypeMapping mapping = this;
    while (mapping != null) {
        int size = aliases.size();
        for (int j = 0; j < size; j++) {
            List<Method> additional = mapping.aliasedBy.get(aliases.get(j)); // 獲取以該屬性作爲別名的子類屬性
            if (additional != null) {
                aliases.addAll(additional);
            }
        }
        mapping = mapping.source; // 繼續向聲明當前元註解的子註解遞歸
    }
}

舉個例子,假如我們現在有如下結構:

image-20220813154058053

現在在 Annotation1AnnotationTypeMapping 中,對它的 name 屬性進行收集的時候,則最終將一路收集得到:

[name, value, value2, value3]

可見該方法會將全部關聯註解對象中,在同一條別名鏈上的註解屬性全部找出來。

2、處理別名屬性

處理關聯屬性這做了三件事:

  • 如果屬性關聯的這一組別名中,有一個別名屬性是來自於 root 的,則直接無條件使用來自 root 的別名屬性覆蓋當前屬性;
  • 使用 MirrorSet 解析並記錄彼此之間具有關係的屬性,然後根據一些規則從中選出唯一一個有效的屬性作爲它們的代表;
  • 使用通過 MirrorSet 獲得的代表屬性替換所有關聯屬性,並記錄該屬性從哪一個註解的哪一個屬性中取值;
private void processAliases(int attributeIndex, List<Method> aliases) {
    // 確認別名鏈上,是否有別名字段來自於root
    int rootAttributeIndex = getFirstRootAttributeIndex(aliases);
    AnnotationTypeMapping mapping = this;
    // 從當前註解向root遞歸
    while (mapping != null) {

        // 若有當前正在處理的註解中:
        // 1.有別名字段來自於root;
        // 2.別名鏈中有一個別名來自於該註解;
        // 則在當前處理的註解的aliasMappings上,記錄這個來自於root的別名屬性,表示它存在一個來自root的別名
        if (rootAttributeIndex != -1 && mapping != this.root) {
            for (int i = 0; i < mapping.attributes.size(); i++) {
                if (aliases.contains(mapping.attributes.get(i))) {
                    mapping.aliasMappings[i] = rootAttributeIndex;
                }
            }
        }

        // 構建MirrorSet,解析別名鏈上的屬性構建映射關係
        mapping.mirrorSets.updateFrom(aliases);
        mapping.claimedAliases.addAll(aliases);
        if (mapping.annotation != null) {
            // 根據MirrorSet,從別名鏈中選擇出唯一生效的屬性作爲它們的最終實際屬性
            int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod);
            // 遍歷當前正在處理的註解的全部屬性
            for (int i = 0; i < mapping.attributes.size(); i++) {
                // 若該屬性在別名鏈中存在
                if (aliases.contains(mapping.attributes.get(i))) {
                    // 在分別記錄該屬性的一些信息:
                    // 1.記錄該屬性應當從哪個註解中取值
                    this.annotationValueSource[attributeIndex] = mapping;
                    // 2.記錄該屬性應當從那個註解的那個屬性中取值
                    this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];
                }
            }
        }

        mapping = mapping.source;
    }
}

其中,關於:

int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod)

我們會在後續分析,這裏我們舉個例子說明一下上述過程:

一切開始前,我們從 Annotation1Annotation3 遍歷,此時我們處理 Annotation1 的屬性 value1

image-20220813163208346

當調用 processAliases 後:

  • 由於別名鏈上非根屬性在根註解 Annotation3 中都不存在,別名不動,此時三個註解的 aliasMappings 都不變;
  • 別名鏈上的三個屬性 value1value2value3 經過 MirrorSet 處理後,發現 value3 是優先級最高的,因而把它們的 annotationValueSourceannotationValueMappings 都分別更新爲 Annotation3Annotation3.value3 的下標;

image-20220813163633311

這樣處理後,當調用 Annotation1.value1 方法時:

  • 先從 AnnotationAttributes 中獲得 value1 的下標 0
  • 確認 annotationValueMapping[0] 對應位置是否爲 -1 ,不是則說明有映射關係;
  • 最後從 annotationValueSource[0] 位置取出 Annotation1 對應的 AnnotationTypeMapping,再從 annotationValueMappings[0] 的位置取出 0
  • Annotation1 對應的 AnnotationTypeMapping 中找到下標 0 對應的方法 value3,然後調用並返回值;

這樣就是完成了映射過程。

3、別名屬性關係的構建

現在我們回頭看 AnnotationTypeMapping.processAliases 方法中的兩個關於 MirrorSet 的操作:

private void processAliases(int attributeIndex, List<Method> aliases) {
    // 確認別名鏈上,是否有別名字段來自於root
    int rootAttributeIndex = getFirstRootAttributeIndex(aliases);
    AnnotationTypeMapping mapping = this;
    // 從當前註解向root遞歸
    while (mapping != null) {
        
        // ... ...

        // 構建MirrorSet,解析別名鏈上的屬性構建映射關係
        mapping.mirrorSets.updateFrom(aliases);
        if (mapping.annotation != null) {
            // 根據MirrorSet,從別名鏈中選擇出唯一生效的屬性作爲它們的最終實際屬性
            int[] resolvedMirrors = mapping.mirrorSets.resolve(null, mapping.annotation, ReflectionUtils::invokeMethod);
            
            // ... ...
        }

        mapping = mapping.source;
    }
}

從上文我們可知,MirrorSet 用於從一組存在直接或間接別名關係的不同註解屬性中,確認唯一有效的屬性。關於這個唯一有效屬性,舉個例子,比如現在有 A、B、C 多個屬性互爲別名,則最終取值的時候,只能有一個屬性的值是有效值,這個值將被同步到所有的別名屬性中,如果 A 是唯一有效屬性,則通過 A、B、C 取到的值都會來自 A。

在這之前,需要先簡單瞭解一下 MirrorSets ,和 AnnotationTypeMappings 以及 MergedAnnotations 的設計一樣,Spring 同樣以 MirrorSets 作爲 MirrorSet 的聚合對象。

先簡單看看 MirrorSet 的數據結構:

class MirrorSets {
    private MirrorSet[] mirrorSets;
    private final MirrorSet[] assigned;
    MirrorSets() {
        this.assigned = new MirrorSet[attributes.size()];
        this.mirrorSets = EMPTY_MIRROR_SETS;
    }
}

MirrorSets 在內部維護了兩個數組,分別是用於存放在不同屬性間共享的 MirrorSet 實例的 mirrorSets,以及與 AnnotationAttributes 中屬性一一對應的,用於存放該屬性對應的 MirrorSet 實例,前者用於遍歷,後者用於根據屬性的索引下標查詢關聯屬性。

當調用 MirrorSets.updateFrom 方法時:

void updateFrom(Collection<Method> aliases) {
    MirrorSet mirrorSet = null;
    int size = 0;
    int last = -1;
    // 遍歷當前註解的所有屬性
    for (int i = 0; i < attributes.size(); i++) {
        Method attribute = attributes.get(i);
        // 若有屬性在傳入的這一組別名中出現
        if (aliases.contains(attribute)) {
            size++; // 計數+1
            if (size > 1) { // 僅有一個別名的時候不創建MirrorSet實例
                if (mirrorSet == null) {
                    mirrorSet = new MirrorSet();
                    this.assigned[last] = mirrorSet; // 當發現第二次在別名組中出現的屬性時,爲上一次發現的別名屬性建立MirrorSet實例
                }
                this.assigned[i] = mirrorSet;
            }
            last = i; // 記錄最後出現那個別名數組下標
        }
    }
    if (mirrorSet != null) {
        mirrorSet.update();
        Set<MirrorSet> unique = new LinkedHashSet<>(Arrays.asList(this.assigned));
        unique.remove(null);
        this.mirrorSets = unique.toArray(EMPTY_MIRROR_SETS); // 更新mirrorSets數組
    }
}

接着我們再看看 MirrorSet.update

class MirrorSet {

    private int size;

    private final int[] indexes = new int[attributes.size()];

    void update() {
        this.size = 0;
        Arrays.fill(this.indexes, -1);
        for (int i = 0; i < MirrorSets.this.assigned.length; i++) {
            if (MirrorSets.this.assigned[i] == this) {
                this.indexes[this.size] = i;
                this.size++;
            }
        }
    }
}

簡單的來說,就是遍歷 MirrorSets.assigned 數組,看看哪些屬性是共享當前 MirrorSet 實例的,然後把註解屬性的下標給記錄到 indexes 中。

舉個例子,我們現在有一個註解,他的兩個屬性構成了互爲別名的關係,現在對其中一個進行處理,則有如下情況:

image-20220617104800021

  • valuealias 屬性在 AnnotationAttributes 中對應的下標分別是 01
  • 處理時,由於 valuealias 處於同一條別名鏈,因此 MirrorSets.updateFrom 調用時會同時傳入這兩者;
  • 由於 valuealias 都在 ValueAttributeMeta 這注解中存在,因此 MirrorSets 會分別爲它們在 assigned 數組中對應的下標位置放入 MirrorSet 實例;
  • 接着,調用 MirrorSet.update 時,發現 assigned[0]assigned[1] 共享一個 MirrorSet 實例,說明兩者是有聯繫的,然後就在該 MirrorSet 實例中的 indexes 數組記錄這兩個位置 01

4、別名屬性唯一值的確認

接上文,當 MirrorSets.updateFrom 調用完畢後,同一註解內的不同屬性該關聯的實際上都關聯上了,接着會調用 MirrorSets.resolve 爲這些關聯的屬性都找到唯一確認的最終屬性:

int[] resolve(@Nullable Object source, @Nullable Object annotation, ValueExtractor valueExtractor) {
    int[] result = new int[attributes.size()];
    for (int i = 0; i < result.length; i++) {
        // 默認情況下,每個屬性都調用他本身
        result[i] = i;
    }
    for (int i = 0; i < size(); i++) {
        MirrorSet mirrorSet = get(i);
        // 如果有MirrorSet,則調用resolve方法獲得這一組關聯屬性中的唯一有效屬性的下標
        int resolved = mirrorSet.resolve(source, annotation, valueExtractor);
        // 將該下標強制覆蓋全部關聯的屬性
        for (int j = 0; j < mirrorSet.size; j++) {
            result[mirrorSet.indexes[j]] = resolved;
        }
    }
    return result;
}

MirrorSet.resolve 則是根據一些條件確認一組關聯屬性中的唯一有效屬性的下標:

<A> int resolve(@Nullable Object source, @Nullable A annotation, ValueExtractor valueExtractor) {
    int result = -1;
    Object lastValue = null; // 最近一個的有效屬性值
    
    // 遍歷與當前註解屬性屬性互爲別名的全部屬性
    for (int i = 0; i < this.size; i++) {
        // 獲取屬性值
        Method attribute = attributes.get(this.indexes[i]);
        Object value = valueExtractor.extract(attribute, annotation);
        boolean isDefaultValue = (value == null ||
                                  isEquivalentToDefaultValue(attribute, value, valueExtractor));
        
        // 如果屬性值是默認值,或者與最後有效值相同,則記錄該屬性下標後返回
        // 以此類推,如果一組互爲別名的屬性全部都是默認值,則前面的屬性——即離根註解最近的——的默認值會作爲最終有效值
        if (isDefaultValue || ObjectUtils.nullSafeEquals(lastValue, value)) {
            if (result == -1) {
                result = this.indexes[i];
            }
            continue;
        }
        
        // 如果屬性值不是默認值,並且與最近一個的有效屬性值不同, 則拋出異常
        // 這裏實際要求一組互爲別名的屬性中,只允許一個屬性的值是非默認值
        if (lastValue != null && !ObjectUtils.nullSafeEquals(lastValue, value)) {
            String on = (source != null) ? " declared on " + source : "";
            throw new AnnotationConfigurationException(String.format(
                "Different @AliasFor mirror values for annotation [%s]%s; attribute '%s' " +
                "and its alias '%s' are declared with values of [%s] and [%s].",
                getAnnotationType().getName(), on,
                attributes.get(result).getName(),
                attribute.getName(),
                ObjectUtils.nullSafeToString(lastValue),
                ObjectUtils.nullSafeToString(value)));
        }
        result = this.indexes[i];
        lastValue = value;
    }
    return result;
}

這裏的邏輯應該是比較清晰的,首先,如果同一個註解內存在多個互爲別名的屬性,則需要有一個唯一有效的最終屬性,所有互爲別名的屬性應當以這個最終屬性的值爲準。

對應到代碼中,則就是通過遍歷 MirrorSet 中互爲別名的字段,然後根據下述規則找到最終屬性:

  • 如果所有屬性都只有默認值,則離根註解最近的屬性最爲最終屬性;
  • 如果所有屬性中存在屬性有非默認值,則該屬性就作爲默認屬性,若出現多個有非默認值的屬性,則直接報錯;

然後返回這個最終屬性的下標。

我們舉個例子,假如現在有 A,B,C,D,E 五個屬性,其中 A 和 B、C 和 D 互爲別名,則經過 MirrorSets#resolve 方法最終得到的 resolvedMirrors 如下圖:

image-20220618155125929

resolvedMirrors翻譯一下,就是 A 和 B 取值時都取 A 的值,C 和 D 取值時都取 C 的值,而 E 取值照樣取 E 的值。

4、多級別名

看到這裏,我們對別名機制大概有個輪廓了,任何關聯的註解中,只要通過 @AliasFor 能直接或者間接聯繫上,那它們就能構成別名。

因此,哪怕存在這樣的結構:

@Retention(RetentionPolicy.RUNTIME)
@interface Annotation1 {
    String alias1() default "";
    @AliasFor(attribute = "alias1")
    String alias2() default "";
}
@Annotation1
@Retention(RetentionPolicy.RUNTIME)
@interface Annotation2 {
    @AliasFor(annotation = Annotation1.class, attribute = "alias1")
    String value1() default "";
    @AliasFor(annotation = Annotation1.class, attribute = "alias2")
    String value2() default "";
}

當使用 @Annotation2 時,只要對 value1 或者 value2 進行賦值,最終都能從兩個註解的各兩個屬性中拿到一樣的值:

image-20220813174709782

五、映射屬性覆蓋

現在,通過 annotationValueMappingsannotationValueSource以及 AttributeMethods這三個成員變量,任何一個使用@AliasFor 註解配置了別名的屬性都可以找到真正對應的值。

不過在 Spring 中,還支持一種默認的屬性覆蓋機制,即當父子註解都存在一個名稱與類型皆相同的屬性時,子註解的屬性值將會覆蓋父註解的屬性值。

AnnotationTypeMapping 的構造函數中,實現該功能的代碼共分爲兩步:

// 爲元註解與根註解同名的屬性強制設置別名
addConventionMappings();
// 爲元註解與非根註解的子註解的同名的屬性設置別名
addConventionAnnotationValues();

1、根註解覆蓋元註解

addConventionMappings 用於實現根註解覆蓋所有其元註解中同名同類型屬性的邏輯:

private void addConventionMappings() {
    if (this.distance == 0) {
        return;
    }
    AttributeMethods rootAttributes = this.root.getAttributes();
    int[] mappings = this.conventionMappings;
    for (int i = 0; i < mappings.length; i++) {
        // 遍歷當前註解的屬性,判斷是否在根註解存在
        String name = this.attributes.get(i).getName();
        int mapped = rootAttributes.indexOf(name);

        // 若存在,並且該屬性不爲“value”
        MirrorSet mirrors = getMirrorSets().getAssigned(i);
        if (!MergedAnnotation.VALUE.equals(name) && mapped != -1) {
            mappings[i] = mapped;
            // 若該屬性還有別名,則讓該屬性和全部別名屬性都從根註解取值
            if (mirrors != null) {
                for (int j = 0; j < mirrors.size(); j++) {
                    mappings[mirrors.getAttributeIndex(j)] = mapped;
                }
            }
        }
    }
}

這一步將遍歷當前註解中的屬性,然後判斷是否在根註解中存在同名屬性,若存則直接將 conventionMappings 中對應下標的位置設置爲根註解對應屬性的下標。

2、子註解覆蓋父註解

addConventionAnnotationValues 用於實現子註解覆蓋父註解中同名同類型屬性的邏輯:

private void addConventionAnnotationValues() {
    // 遍歷當前註解的全部屬性
    for (int i = 0; i < this.attributes.size(); i++) {
        Method attribute = this.attributes.get(i);
        boolean isValueAttribute = MergedAnnotation.VALUE.equals(attribute.getName());
        AnnotationTypeMapping mapping = this;
        // 從當前註解向非根註解的子註解遞歸
        while (mapping != null && mapping.distance > 0) {
            // 若當前方法在子註解中存在,則將annotationValueMappings和annotationValueSource替換爲該子註解和子註解的屬性
            // 由於替換前會比較annotationValueSource中註解距離根註解的距離,
            // 所以之前設置的根註解屬性不受影響,因爲跟註解距離爲0,優先級總是最高的
            int mapped = mapping.getAttributes().indexOf(attribute.getName());
            if (mapped != -1 && isBetterConventionAnnotationValue(i, isValueAttribute, mapping)) {
                this.annotationValueMappings[i] = mapped;
                this.annotationValueSource[i] = mapping;
            }
            mapping = mapping.source;
        }
    }
}

private boolean isBetterConventionAnnotationValue(int index, boolean isValueAttribute,
                                                  AnnotationTypeMapping mapping) {

    if (this.annotationValueMappings[index] == -1) {
        return true;
    }
    int existingDistance = this.annotationValueSource[index].distance;
    return !isValueAttribute && existingDistance > mapping.distance;
}

它大概幹了這些事:

  • 若自注解中存在非 value 同名字段,則將與當前屬性對應位置的 annotationValueSourceannotationValueMappings 設置爲該子註解和該註解中同名屬性的方法下標;
  • 若子註解的子註解中仍然存在同名註解,則選擇一個離根註解最近的子註解,重複上述過程;
  • 重複上述兩步直到全部子註解遞歸完畢;

總結

回滾整個流程,我們瞭解了 Spring 中元註解的解析過程,與註解的屬性覆蓋與別名機制的實現。

當我們通過 MergedAnnotations 去從 AnnotatedElement 獲取註解的時候,會有專門的 AnnotationProcessor ——比如 MergedAnnotationFinder——去把指定類型的註解上的全部元註解解析爲 AnnotationTypeMapping,然後 AnnotationTypeMapping 在把一個一個的元註解對象轉爲 AnnotationTypeMapping,讓他們形成類似鏈表的結構,從而維持父子註解間層次關係。

而當 AnnotationTypeMapping 在創建時,會遞歸解析 AnnotationTypeMapping 鏈表結構上的全部節點,然後解析他們的屬性,讓通過 @AliasFor 構成別名關係的屬性在各個註解中以 MirrorSet 的形式存在,從實現別名機制。

並且,在完成別名映射後,AnnotationTypeMapping 還會再次遞歸解析AnnotationTypeMapping 鏈表結構上的全部節點的屬性,讓子註解中與父註解具有相同名稱、類型的非 "value" 屬性覆蓋父註解中的屬性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章