深入理解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 是如何從 AnnotatedElement 的層級結構中完成對註解的搜索與處理的。

相關文章:

一、層級結構

當我們點開 Spring 提供兩個註解工具類 AnnotationUtils 或者 AnnotatedElementUtils 時,我們可以從類註釋上了解到,Spring 支持 findget 開頭的兩類方法:

  • get :指從 AnnotatedElement 上尋找直接聲明的註解;
  • find:指從 AnnotatedElement 的層級結構(類或方法)上尋找存在的註解;

大部分情況下,get 開頭的方法與 AnnotatedElement 本身直接提供的方法效果一致,比較特殊的 find 開頭的方法,此類方法會從AnnotatedElement 的層級結構中尋找存在的註解,關於“層級結構”,Spring 給出了一套定義:

  • AnnotatedElementClass 時:層級結構指類本身,以及類和它的父類,父接口,以及父類的父類,父類的父接口......整個繼承樹中的所有 Class 文件;
  • AnnotatedElementMethod 是:層級結構指方法本身,以及聲明該方法的類它的父類,父接口,以及父類的父類,父類的父接口......整個繼承樹中所有 Class 文件中,那些與搜索的 Method 具有完全相同簽名的方法;
  • AnnotatedElement 不是上述兩者中的一種時,它沒有層級結構,搜索將僅限於 AnnotatedElement 這個對象本身;

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

image-20220812140322452

則對 Foo.class 使用 get 開頭的方法,或者 AnnotatedElement 本身提供的方法,都只能獲得 Annotation1,而使用 find 方法除了可以獲得 Foo 本身的註解,還可以獲得 FooSuperFooInterface 上的註解。

同理,假如我們掃描的是 Foo.class 中一個名爲 foo,沒有參數且沒有返回值的方法,則 find 除了掃描 Foo.foo() 外,還會掃描器 FooSuperFooInterface 中沒有參數且沒有返回值的方法上的註解。

二、合併註解

當我們點進 AnnotationUtil 中的任意一個方法,比如 findAnnotation

public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType) {
    if (annotationType == null) {
        return null;
    }
    // 如果不需要對層級結構進行搜索也能找到註解
    if (AnnotationFilter.PLAIN.matches(annotationType) ||
        AnnotationsScanner.hasPlainJavaAnnotationsOnly(method)) {
        return method.getDeclaredAnnotation(annotationType);
    }
    // 註解無法直接獲取,需要對層級結構進行搜索
    return MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none())
        .get(annotationType).withNonMergedAttributes()
        .synthesize(MergedAnnotation::isPresent).orElse(null);
}

這個方法很清晰的描述了一個註解的查找流程:

  • 先對 AnnotatedElement 本身進行查找,並且使用註解過濾器 AnnotationFilter 進行處理;
  • 找不到再通過一個叫 MergedAnnotations 的東西對 AnnotatedElement 進行查找,這玩意需要指定三個東西:
    1. 查找的 AnnotatedElement
    2. 搜索策略 SearchStrategy
    3. 可重複註解容器 RepeatableContainers

可見,真正的搜索過程發生在合併註解 MergedAnnotations

1、註解過濾器

其中,AnnotationFilter 是我們見到的第一個組件。該類是一個函數式接口,用於匹配傳入的註解實例、類型或名稱。

@FunctionalInterface
public interface AnnotationFilter {

    // 根據實例匹配
    default boolean matches(Annotation annotation) {
        return matches(annotation.annotationType());
    }

    // 根據類型匹配
    default boolean matches(Class<?> type) {
        return matches(type.getName());
    }

    // 根據名稱匹配
    boolean matches(String typeName);

}

AnnotationFilter默認提供三個可選的靜態實例:

  • PLAIN:類是否屬於 java.langorg.springframework.lang 包;
  • JAVA:類是否屬於 javajavax包;
  • ALL:任何類;

此處過濾器選擇了 PLAIN,即當查找的註解屬於 java.langorg.springframework.lang 包的時候就不進行查找,而是直接從被查找的元素直接聲明的註解中獲取。這個選擇不難理解,java.lang包下提供的都是諸如@Resource或者 @Target 這樣的註解,而springframework.lang包下提供的則都是 @Nonnull 這樣的註解,這些註解基本不可能作爲有特殊業務意義的元註解使用,因此默認忽略也是合理的。

實際上,PLAIN 也是大部分情況下的使用的默認過濾器。

2、合併註解

當對 AnnotatedElement 直接搜索無法獲得符合條件的註解時,Spring 就會嘗試通過 MergedAnnotations 對層級結構進行搜索,並對獲得的註解進行聚合。在這個過程中,被聚合的註解就會被封裝爲 MergedAnnotation,而結束搜索後,獲得的全部 MergedAnnotation 又會被聚合爲 MergedAnnotations

MergedAnnotation

MergedAnnotation 直譯叫合併註解,MergedAnnotation 通常與一個註解對象一對一,但是它的屬性可能來自於子註解或者元註解,甚至是同一個註解中通過 @AliasFor 綁定其他屬性,因此稱爲“合併”註解——這裏的合併指的是屬性上的合併。

MergedAnnotations

MergedAnnotations 則用於提供一個基於 AnnotatedElement 快速掃描並創建一組 MergedAnnotation 的功能,在 AnnotationUtils.findAnnotation 中,使用了 MergedAnnotations.from 方法創建一個 TypeMappedAnnotations 實現類:

static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
                              RepeatableContainers repeatableContainers) {
    // 3、過濾屬於`java`、`javax`或者`org.springframework.lang`包的註解
    return from(element, searchStrategy, repeatableContainers, AnnotationFilter.PLAIN);
}

static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
                              RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {

    Assert.notNull(repeatableContainers, "RepeatableContainers must not be null");
    Assert.notNull(annotationFilter, "AnnotationFilter must not be null");
    return TypeMappedAnnotations.from(element, searchStrategy, repeatableContainers, annotationFilter);
}

具體細節我們我們先不看,光看最後調用的方法,我們知道 MergedAnnotations 的創建需要四樣東西:

  • 查找的 AnnotatedElement
  • 搜索策略 SearchStrategy
  • 可重複註解容器 RepeatableContainers
  • 註解過濾器 AnnotationFilter

二、註解的搜索

MergedAnnotations 被創建後,並不會立刻就觸發對 AnnotatedElement 的搜索,而是等到調用 MergedAnnotations.get 時纔開始,我們以 TypeMappedAnnotations 爲例:

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();
    }
    // 使用AnnotationScanner對元素層級結構進行查找
    MergedAnnotation<A> result = scan(annotationType,
                                      new MergedAnnotationFinder<>(annotationType, predicate, selector));
    return (result != null ? result : MergedAnnotation.missing());
}

再點開 TypeMappedAnnotations.scan 方法,我們會發現它通過創建時指定的 SearchStrategy,去使用 AnnotationScanner 對元素進行搜索:

private <C, R> R scan(C criteria, AnnotationsProcessor<C, R> processor) {
    if (this.annotations != null) {
        R result = processor.doWithAnnotations(criteria, 0, this.source, this.annotations);
        return processor.finish(result);
    }
    if (this.element != null && this.searchStrategy != null) {
        return AnnotationsScanner.scan(criteria, this.element, this.searchStrategy, processor);
    }
    return null;
}

AnnotationScanner 是 Spring 註解包使用的一個內部類,它根據 SearchStrategy 指定的搜索策略對 AnnotatedElement 的層級結構進行搜索,並且使用 AnnotationsProcessor 在獲得註解後進行回調。

1、搜索策略

其中,SearchStrategy 提供了五種搜索範圍:

  • DIRECT:只查找元素上直接聲明的註解,不包括通過@Inherited繼承的註解;

  • INHERITED_ANNOTATIONS:只查找元素直接聲明或通過@Inherited繼承的註解;

  • SUPERCLASS:查找元素直接聲明或所有父類的註解;

  • TYPE_HIERARCHY:查找元素、所有父類以及實現的父接口的全部註解;

  • TYPE_HIERARCHY_AND_ENCLOSING_CLASSES:查找查找元素、所有父類以及實現的父接口、封閉類以及其子類的全部註解。

    封閉類是 JDK17 的新特性,可參考 詳解 Java 17中的新特性:“密封類”,本章將不過多涉及該內容;

2、註解掃描器

AnnotationScanner 掃描掃描行爲的直接發起者,它只提供了唯一允許外部調用的靜態方法 scan

static <C, R> R scan(C context, AnnotatedElement source, SearchStrategy searchStrategy,
                     AnnotationsProcessor<C, R> processor) {

    R result = process(context, source, searchStrategy, processor);
    return processor.finish(result);
}

private static <C, R> R process(C context, AnnotatedElement source,
                                SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor) {

    if (source instanceof Class) {
        return processClass(context, (Class<?>) source, searchStrategy, processor);
    }
    if (source instanceof Method) {
        return processMethod(context, (Method) source, searchStrategy, processor);
    }
    return processElement(context, source, processor);
}

掃描器從層級結構中的 AnnotatedElement 獲得註解後,會調用 AnnotationsProcessor 對註解對象進行處理。

這裏根據 AnnotatedElement 類型的不同,又區分 MethodClass 以及其他 AnnotatedElement ,這三種對象會有不同的掃描方式。 AnnotationScanner 對這三者的掃描方式大同小異,基本都是沒層級結構就直接返回,有層級結構就通過反射遍歷按深度優先掃描層級結構。

不過需要重點關注一下對方法的掃描,Spring 針對方法的掃描制定了比較嚴格的標準,假設掃描的原始方法稱爲 A,則被掃描的方法 B,要允許獲得 B 上的註解 ,則必須滿足如下規則:

  • B 不能是橋接方法;
  • A 不能是私有方法;
  • AB 的名稱、參數類型、數量皆必須相等 ==> hasSameParameterTypes / isOverride
  • AB 參數有泛型,則要求泛型也一致 ==> hasSameGenericTypeParameters()

3、註解處理器

AnnotationsProcessor 的本體是一個用於函數式接口:

interface AnnotationsProcessor<C, R> {
	@Nullable
	default R doWithAggregate(C context, int aggregateIndex) {
		return null;
	}
    // 掃描後的回調,返回的對象將用於下一次掃描,當返回爲空時中斷掃描
	@Nullable
	R doWithAnnotations(C context, int aggregateIndex, @Nullable Object source, Annotation[] annotations);
	@Nullable
	default R finish(@Nullable R result) {
		return result;
	}

}

當掃描到元素後,AnnotationScanner 會獲得該元素上的註解,將其與元素一起作爲入參傳入 doWithAnnotations,若 doWithAnnotations 返回了一個非空元素,則 AnnotationScanner 將繼續掃描元素的層級結構,然後直到doWithAnnotations 返回 null 爲止或者全部層級結構都被掃描爲止。

註解處理器是實現各種查找和收集等操作最核心的部分,這裏我們先簡單瞭解一下,知道它是個配合 AnnotationScanner 使用的回調接口即可。

4、聚合索引

AnnotationsProcessor 中, doWithAggregatedoWithAnnotations 都會傳入一個名爲 aggregateIndex 的參數,該參數用於掃描過程中掃描的層級數,通過該參數,我們可以區分註解之間被掃描到的先後順序。

比如說,我們現在還是有一個如下結構:

image-20220812173010265

AnnotationScanner 在掃描時,以 0 爲起始值,每進入一個層級就遞增,現在我們對 Foo.class 進行掃描,則有:

  • aggregateIndex= 0,掃描 Foo.class,獲得 Annotation1

  • aggregateIndex= 1,掃描 FooInterface.class,獲得 Annotation2

  • aggregateIndex= 2,掃描 FooSuper.class,獲得 Annotation3

  • aggregateIndex= 4:

    先掃描 FooSuperInterface.class,獲得 Annotation4

    再掃描 FooSuperSuper.class,獲得 Annotation5

從理論上來說,聚合索引越小,則該註解最優先被掃描到。因此聚合索引一般用於在出現重複註解的時候用來區分優先級,比如層級結構中出現了多個類型相同的註解,而用戶僅需要獲得其中一個,此時就可以通過比較聚合索引,返回其中最小或者最大的那個註解。後續我們還會看到一個叫 AnnotationSelector 的玩意,它就是用來做這個事情的。

三、註解的處理

註解處理器 AnnotationsProcessor 的實現類是完成註解操作最核心的部分,前面提到 AnnotationScanner 負責提供註解掃描的功能,但是掃描到註解以後要怎麼處理?是否繼續掃描後續註解?這些都取決於使用的 AnnotationsProcessor 規定要怎麼做。

TypeMappedAnnotations 中,以內部類的形式提供了多數 AnnotationsProcessor 的實現,它們提供類三類功能:

  • TypeMappedAnnotations.IsPresent:判斷註解是否直接或間接(即不存在可重複註解,但是存在可重複註解的容器註解這種情況)存在。

    關於註解的“直接”或“間接”存在,可以參照 AnnotatedElement 類上註釋的定義;

  • TypeMappedAnnotations.MergedAnnotationFinder:用於找到一個唯一的指定類型的註解;

  • TypeMappedAnnotations.AggregatesCollector:用於收集全部掃描到的註解;

1、判斷註解是否存在

該處理器對應 TypeMappedAnnotations.IsPresent,它用於確定一個註解是否在 AnnotationScanner 支持掃描的範圍內存在。

需要注意的是,IsPresent 中支持基於兩種“存在”的定義進行查詢:

  • directly:註解在 AnnotatedElement 上直接存在,包括兩種情況
    1. 註解 X 在 A 上直接存在;
    2. 註解 X 是可重複註解,它存在可重複容器註解 Y,現在在類 A 上存在 Y 但是不存在 X,則說 X 在 A 上直接存在;
  • Indirectly:註解 X 在AnnotatedElement 上不直接存在,但是在能夠掃描到的註解的元註解中存在;

註解處理器

我們直接看看 IsPresent.doWithAnnotations 方法:

public Boolean doWithAnnotations(Object requiredType, int aggregateIndex,
                                 @Nullable Object source, Annotation[] annotations) {

    for (Annotation annotation : annotations) {
        if (annotation != null) {
            Class<? extends Annotation> type = annotation.annotationType();
            // 若註解類型不爲空,且能通過過濾器校驗,則開始判斷過程
            if (type != null && !this.annotationFilter.matches(type)) {

                // 1.該註解類型就是要查找的類型
                if (type == requiredType || type.getName().equals(requiredType)) {
                    return Boolean.TRUE;
                }
                // 2.該註解不是要查找的類型,但是它是一個容器註解,則將其全部平攤
                Annotation[] repeatedAnnotations =
                    this.repeatableContainers.findRepeatedAnnotations(annotation);
                if (repeatedAnnotations != null) {
                    // 遞歸判斷平攤後的註解是否符合條件
                    Boolean result = doWithAnnotations(
                        requiredType, aggregateIndex, source, repeatedAnnotations);
                    if (result != null) {
                        return result;
                    }
                }
                // 3.如果上述兩者情況都不滿足,則且並不限制值僅查找直接存在的註解
                // 則查找這個註解的元註解,判斷其所有元註解是否存在該指定註解
                if (!this.directOnly) {
                    AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(type);
                    for (int i = 0; i < mappings.size(); i++) {
                        AnnotationTypeMapping mapping = mappings.get(i);
                        if (isMappingForType(mapping, this.annotationFilter, requiredType)) {
                            return Boolean.TRUE;
                        }
                    }
                }
            }
        }
    }
    return null;
}

private static boolean isMappingForType(AnnotationTypeMapping mapping,
                                        AnnotationFilter annotationFilter, @Nullable Object requiredType) {

    Class<? extends Annotation> actualType = mapping.getAnnotationType();
    return (!annotationFilter.matches(actualType) &&
            (requiredType == null || actualType == requiredType || actualType.getName().equals(requiredType)));
}

這個方法依次進行三次判斷:

  • 當前註解是否就是要找的註解?
  • 當前註解如果是個可從重複註解的容器註解,則將其內部的可重複註解全部取出平攤後,是否存在要找的註解?
  • 如果當不限制只查找註解本身,則繼續搜索它的所有元註解,這些元註解是否存在要找的註解?

只要這三個條件任意一個滿足,則就會認爲該註解在 AnnotationScanner 的掃描範圍內存在。

AnnotationTypeMappings

這裏引入了一個新類 AnnotationTypeMappings,它是一組 AnnotationTypeMapping 的聚合。

後續講解屬性映射與合併註解的合成的時候會具體介紹這兩者,現在先簡單的認爲一個註解對應一個 AnnotationTypeMapping,而一個註解與它的元註解的聚合對應一個 AnnotationTypeMappings 即可。

可重複容器

此處第一次用到的可重複註解容器 RepeatableContainers,這個它表示一個可以容納可重複註解的容器註解與該可重複註解的嵌套關係,比如我們現在 a -> b -> c 的套娃關係,則通過 RepeatableContainers 註解,我們可以直接通過它從將 c 轉成 b,然後再把 b 轉成 a,這就是上文所說的“平攤”過程。

2、收集註解

TypeMappedAnnotations.AggregatesCollector 用於收集 AnnotationScanner 掃描到的註解。

我們關注其 doWithAnnotations 方法的實現:

@Override
@Nullable
public List<Aggregate> doWithAnnotations(Object criteria, int aggregateIndex,
                                         @Nullable Object source, Annotation[] annotations) {
    // 創建一個Aggregate
    this.aggregates.add(createAggregate(aggregateIndex, source, annotations));
    return null;
}

private Aggregate createAggregate(int aggregateIndex, @Nullable Object source, Annotation[] annotations) {
    List<Annotation> aggregateAnnotations = getAggregateAnnotations(annotations);
    return new Aggregate(aggregateIndex, source, aggregateAnnotations);
}

private List<Annotation> getAggregateAnnotations(Annotation[] annotations) {
    List<Annotation> result = new ArrayList<>(annotations.length);
    addAggregateAnnotations(result, annotations);
    return result;
}

private void addAggregateAnnotations(List<Annotation> aggregateAnnotations, Annotation[] annotations) {
    for (Annotation annotation : annotations) {
        if (annotation != null && !annotationFilter.matches(annotation)) {
            // 若是容器註解就全部攤平
            Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
            if (repeatedAnnotations != null) {
                addAggregateAnnotations(aggregateAnnotations, repeatedAnnotations);
            }
            else {
                // 收集該註解
                aggregateAnnotations.add(annotation);
            }
        }
    }
}

這段方法跳了好多層,不過基本流程還是很清晰的,即獲取掃描到的註解數組,然後再把數組中那些容器註解攤平,全部處理完以後封裝爲一個 Aggregate 對象。

Aggregate

Aggregate 也是 TypeMappedAnnotations 的內部類,它的代碼和功能一樣簡單:

  • 接受一組註解數組 annotations
  • 遍歷註解數組,解析這些註解的元註解,並將其與解析得到的元註解轉爲 AnnotationTypeMappings
  • AnnotationTypeMappings 放到對應的源註解在 annotations 中的下標的位置;
private static class Aggregate {

    private final int aggregateIndex;

    @Nullable
    private final Object source;

    private final List<Annotation> annotations;

    private final AnnotationTypeMappings[] mappings;

    Aggregate(int aggregateIndex, @Nullable Object source, List<Annotation> annotations) {
        this.aggregateIndex = aggregateIndex;
        this.source = source;
        this.annotations = annotations;
        this.mappings = new AnnotationTypeMappings[annotations.size()];
        for (int i = 0; i < annotations.size(); i++) {
            this.mappings[i] = AnnotationTypeMappings.forAnnotationType(annotations.get(i).annotationType());
        }
    }

    int size() {
        return this.annotations.size();
    }

    @Nullable
    AnnotationTypeMapping getMapping(int annotationIndex, int mappingIndex) {
        AnnotationTypeMappings mappings = getMappings(annotationIndex);
        return (mappingIndex < mappings.size() ? mappings.get(mappingIndex) : null);
    }

    AnnotationTypeMappings getMappings(int annotationIndex) {
        return this.mappings[annotationIndex];
    }

    @Nullable
    <A extends Annotation> MergedAnnotation<A> createMergedAnnotationIfPossible(
        int annotationIndex, int mappingIndex, IntrospectionFailureLogger logger) {

        return TypeMappedAnnotation.createIfPossible(
            this.mappings[annotationIndex].get(mappingIndex), this.source,
            this.annotations.get(annotationIndex), this.aggregateIndex, logger);
    }
}

簡而言之,通過 AggregatesCollector + Aggregate,我們最終就可以得到:

  • 所有 AnnotationScanner 掃描到的註解;
  • 所有 AnnotationScanner 掃描到的註解中的容器註解攤平後的可重複註解;
  • 上述註解的元註解;

可謂應有盡有。

3、查找註解

TypeMappedAnnotations.MergedAnnotationFinder 用於從 AnnotationScanner 掃描的註解中,找到一個指定類型的註解/合併註解:

private class MergedAnnotationFinder<A extends Annotation>
    implements AnnotationsProcessor<Object, MergedAnnotation<A>> {

    private final Object requiredType;

    @Nullable
    private final Predicate<? super MergedAnnotation<A>> predicate;

    // 合併註解選擇器
    private final MergedAnnotationSelector<A> selector;

    @Nullable
    private MergedAnnotation<A> result;

    MergedAnnotationFinder(Object requiredType, @Nullable Predicate<? super MergedAnnotation<A>> predicate,
                           @Nullable MergedAnnotationSelector<A> selector) {

        this.requiredType = requiredType;
        this.predicate = predicate;
        this.selector = (selector != null ? selector : MergedAnnotationSelectors.nearest());
    }

    @Override
    @Nullable
    public MergedAnnotation<A> doWithAggregate(Object context, int aggregateIndex) {
        return this.result;
    }

    @Override
    @Nullable
    public MergedAnnotation<A> doWithAnnotations(Object type, int aggregateIndex,
                                                 @Nullable Object source, Annotation[] annotations) {

        for (Annotation annotation : annotations) {
            if (annotation != null && !annotationFilter.matches(annotation)) {
                MergedAnnotation<A> result = process(type, aggregateIndex, source, annotation);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

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

        // 若註解是可重複註解的容器註解則平攤
        Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
        if (repeatedAnnotations != null) {
            return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
        }

        // 獲取這個註解的元註解,並將自己及這些元註解都轉爲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);
                // 如果當前已經存在符合條件的合併註解了,則使用選擇器判斷兩個註解到底誰會更合適,然後將其更新到成員變量result中
                if (candidate != null && (this.predicate == null || this.predicate.test(candidate))) {
                    // 判斷該註解是否是最符合的結果,如果是就沒必要再比較了,直接返回
                    if (this.selector.isBestCandidate(candidate)) {
                        return candidate;
                    }
                    updateLastResult(candidate);
                }
            }
        }
        return null;
    }

    private void updateLastResult(MergedAnnotation<A> candidate) {
        MergedAnnotation<A> lastResult = this.result;
        this.result = (lastResult != null ? this.selector.select(lastResult, candidate) : candidate);
    }

    @Override
    @Nullable
    public MergedAnnotation<A> finish(@Nullable MergedAnnotation<A> result) {
        return (result != null ? result : this.result);
    }
}

// 判斷AnnotationTypeMapping對應的註解是否符合要求
private static boolean isMappingForType(AnnotationTypeMapping mapping,
                                        AnnotationFilter annotationFilter, @Nullable Object requiredType) {
    Class<? extends Annotation> actualType = mapping.getAnnotationType();
    return (!annotationFilter.matches(actualType) &&
            (requiredType == null || actualType == requiredType || actualType.getName().equals(requiredType)));
}

MergedAnnotationFinder 主要乾了這幾件事:

  • 遍歷入參的數組 annotations,如果存在容器註解則將其全部平攤爲可重複註解;
  • 遍歷上述註解,解析它們的元註解,將全部的元註解與該註解都轉爲 AnnotationTypeMapping
  • AnnotationTypeMapping 轉爲合併註解 MergedAnnotation,然後判斷這個合併註解是否符合要求;
  • 若該合併註解經過合併註解選擇器 MergedAnnotationSelector確認,是最符合條件的結果,則直接返回;
  • 若該合併註解符合條件但是不是最符合條件的結果,則使用合併註解選擇器 MergedAnnotationSelector 判斷該合併註解與上一個找到的符合條件的合併註解到底誰更合適一點;
  • 將更合適的合併註解更新到成員變量 result 上;

合併註解選擇器

這裏又出現了一個新類 MergedAnnotationSelector,直譯叫做合併註解選擇器,它用於在 MergedAnnotationFinder 找到了多個符合條件的結果時,從這些結果中挑選出最優的作爲 MergedAnnotationFinder 的最終返回值。

public interface MergedAnnotationSelector<A extends Annotation> {
    // 該合併註解是否是最符合的結果,如果是直接跳過select
	default boolean isBestCandidate(MergedAnnotation<A> annotation) {
		return false;
	}
    // 二選一
	MergedAnnotation<A> select(MergedAnnotation<A> existing, MergedAnnotation<A> candidate);
}

它默認提供了 FirstDirectlyDeclaredNearest 兩個實現,它們都需要藉助上文提到的聚合索引:

private static class Nearest implements MergedAnnotationSelector<Annotation> {
    // 聚合索引爲0,說明註解直接出現在被掃描的AnnotatedElement上,是最符合的
    @Override
    public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {
        return annotation.getDistance() == 0;
    }
    // 聚合索引不爲0,則挑一個聚合索引比較小的,也就是先被掃描的
    @Override
    public MergedAnnotation<Annotation> select(
        MergedAnnotation<Annotation> existing, MergedAnnotation<Annotation> candidate) {
        if (candidate.getDistance() < existing.getDistance()) {
            return candidate;
        }
        return existing;
    }
}

private static class FirstDirectlyDeclared implements MergedAnnotationSelector<Annotation> {
    // 聚合索引爲0,說明註解直接出現在被掃描的AnnotatedElement上,是最符合的
    @Override
    public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {
        return annotation.getDistance() == 0;
    }
    // 僅當已有註解沒有直接出現在被掃描的AnnotatedElement上,但是新註解直接出現在了掃描的AnnotatedElement上的時候,才返回新註解
    @Override
    public MergedAnnotation<Annotation> select(
        MergedAnnotation<Annotation> existing, MergedAnnotation<Annotation> candidate) {
        if (existing.getDistance() > 0 && candidate.getDistance() == 0) {
            return candidate;
        }
        return existing;
    }

}

兩個選擇器判斷規則不盡相同,但是大體思路是相同的,即:越接近被 AnnotationScanner 掃描的 AnnotatedElement 的註解,優先級越高

總結

本篇文章主要分析的 Spring 對 AnnotatedElement 層級結構中註解的搜索與處理機制。

Spring 爲我們提供的註解支持 getfind 兩者語義的查詢:

  • getAnnotatedElement 本身提供的方法類似,用於從元素本身直接搜索註解;
  • find 除了與 get 一樣搜索元素本身外,若 AnnotatedElementClass 或者 Method,還會搜索的它們的類/聲明類的層級結構;

當我們通過 find 語義的方法搜索層級結構時,實際上會先生成一個合成註解聚合對象 MergedAnnotations,它會在被操作時,根據我們在創建時傳入的註解過濾器 AnnotationFilter 及註解搜索策略 SearchStrategy,使用註解掃描器 AnnotationScanner 對目標元素進行掃描。因此,findget 區別只是來自於註解掃描時使用的搜索策略的不同。

而當 AnnotationScanner 掃描到註解後,會根據操作類型,調用指定的註解處理器 AnnotationProcessor,Spring 默認提供了三種類型的處理器,以便支持三種不同類型的操作:

  • TypeMappedAnnotations.IsPresent:判斷註解是否存在;
  • TypeMappedAnnotations.AggregatesCollector:收集全部被掃描到的註解;
  • TypeMappedAnnotations.MergedAnnotationFinder:從元素被掃描的註解中找到符合條件的唯一合併註解;

此外,在上述過程中,Spring 還考慮到的可重複註解,在進行上述處理的時候,若操作的註解是可重複註解的容器註解,則 Spring 還會將其展開攤平後,再對最終獲得的可重複註解進行處理。

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