前言
衆所周知,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 支持 find
和 get
開頭的兩類方法:
get
:指從AnnotatedElement
上尋找直接聲明的註解;find
:指從AnnotatedElement
的層級結構(類或方法)上尋找存在的註解;
大部分情況下,get
開頭的方法與 AnnotatedElement
本身直接提供的方法效果一致,比較特殊的 find
開頭的方法,此類方法會從AnnotatedElement
的層級結構中尋找存在的註解,關於“層級結構”,Spring 給出了一套定義:
- 當
AnnotatedElement
是Class
時:層級結構指類本身,以及類和它的父類,父接口,以及父類的父類,父類的父接口......整個繼承樹中的所有Class
文件; - 當
AnnotatedElement
是Method
是:層級結構指方法本身,以及聲明該方法的類它的父類,父接口,以及父類的父類,父類的父接口......整個繼承樹中所有Class
文件中,那些與搜索的Method
具有完全相同簽名的方法; - 當
AnnotatedElement
不是上述兩者中的一種時,它沒有層級結構,搜索將僅限於AnnotatedElement
這個對象本身;
舉個例子,假設我們現在有如下結構:
則對 Foo.class
使用 get
開頭的方法,或者 AnnotatedElement
本身提供的方法,都只能獲得 Annotation1
,而使用 find
方法除了可以獲得 Foo
本身的註解,還可以獲得 FooSuper
和 FooInterface
上的註解。
同理,假如我們掃描的是 Foo.class
中一個名爲 foo
,沒有參數且沒有返回值的方法,則 find
除了掃描 Foo.foo()
外,還會掃描器 FooSuper
和 FooInterface
中沒有參數且沒有返回值的方法上的註解。
二、合併註解
當我們點進 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
進行查找,這玩意需要指定三個東西:- 查找的
AnnotatedElement
; - 搜索策略
SearchStrategy
; - 可重複註解容器
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.lang
、org.springframework.lang
包;JAVA
:類是否屬於java
、javax
包;ALL
:任何類;
此處過濾器選擇了 PLAIN
,即當查找的註解屬於 java.lang
、org.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
類型的不同,又區分 Method
、Class
以及其他 AnnotatedElement
,這三種對象會有不同的掃描方式。 AnnotationScanner
對這三者的掃描方式大同小異,基本都是沒層級結構就直接返回,有層級結構就通過反射遍歷按深度優先掃描層級結構。
不過需要重點關注一下對方法的掃描,Spring 針對方法的掃描制定了比較嚴格的標準,假設掃描的原始方法稱爲 A
,則被掃描的方法 B
,要允許獲得 B
上的註解 ,則必須滿足如下規則:
B
不能是橋接方法;A
不能是私有方法;A
和B
的名稱、參數類型、數量皆必須相等 ==>hasSameParameterTypes
/isOverride
;- 若
A
和B
參數有泛型,則要求泛型也一致 ==>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
中, doWithAggregate
和 doWithAnnotations
都會傳入一個名爲 aggregateIndex
的參數,該參數用於掃描過程中掃描的層級數,通過該參數,我們可以區分註解之間被掃描到的先後順序。
比如說,我們現在還是有一個如下結構:
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
上直接存在,包括兩種情況- 註解 X 在 A 上直接存在;
- 註解 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);
}
它默認提供了 FirstDirectlyDeclared
和 Nearest
兩個實現,它們都需要藉助上文提到的聚合索引:
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 爲我們提供的註解支持 get
和 find
兩者語義的查詢:
get
與AnnotatedElement
本身提供的方法類似,用於從元素本身直接搜索註解;find
除了與get
一樣搜索元素本身外,若AnnotatedElement
是Class
或者Method
,還會搜索的它們的類/聲明類的層級結構;
當我們通過 find
語義的方法搜索層級結構時,實際上會先生成一個合成註解聚合對象 MergedAnnotations
,它會在被操作時,根據我們在創建時傳入的註解過濾器 AnnotationFilter
及註解搜索策略 SearchStrategy
,使用註解掃描器 AnnotationScanner
對目標元素進行掃描。因此,find
和 get
區別只是來自於註解掃描時使用的搜索策略的不同。
而當 AnnotationScanner
掃描到註解後,會根據操作類型,調用指定的註解處理器 AnnotationProcessor
,Spring 默認提供了三種類型的處理器,以便支持三種不同類型的操作:
TypeMappedAnnotations.IsPresent
:判斷註解是否存在;TypeMappedAnnotations.AggregatesCollector
:收集全部被掃描到的註解;TypeMappedAnnotations.MergedAnnotationFinder
:從元素被掃描的註解中找到符合條件的唯一合併註解;
此外,在上述過程中,Spring 還考慮到的可重複註解,在進行上述處理的時候,若操作的註解是可重複註解的容器註解,則 Spring 還會將其展開攤平後,再對最終獲得的可重複註解進行處理。