前言
衆所周知,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);
}
在前文我們知道,TypeMappedAnnotations
是 MergedAnnotations
接口的默認實現,他表示由 AnnotationScanner
從同一個 AnnotatedElement
上掃描出來的註解們轉爲的一批合併註解 MergedAnnotation
。
舉個例子,假如現有 AnnotatedElement
對象 Foo.class
,他上面有一些註解,則理論上轉爲 MergedAnnotations
的過程如下:
不過當 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;
}
這裏我們需要重點關注 AnnotationTypeMappings
和 AnnotationTypeMapping
的創建,這兩者纔是真正用於解析與維護原始註解對象信息的主題。
1、創建元註解聚合體
首先先給出定義,AnnotationTypeMappings
用於表示某一個註解類上全部元註解,對應的還有一個 AnnotationTypeMapping
,它表示一個具體的元註解對象。
AnnotationTypeMappings
與 MergedAnnotations
的設計思路一樣,它表示一組 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
大概可以參考下圖:
不過這個圖仍然不夠準確,因爲 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
之間其實會形成一個類似單向鏈表的結構,我們根據此調整上一節末尾給出的圖例:
至此,通過 AnnotationTypeMappings
可以直接管理所有的 AnnotationTypeMapping
,而通過獨立的 AnnotationTypeMapping
,又可以追溯元註解之間的父子關係。
三、屬性解析
通過上文,我們分析完元註解的解析問題,通過 AnnotationTypeMappings
或 AnnotationTypeMapping
都可以完成的元註解樹結構的訪問,不過仍然還沒說清楚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();
}
關於屬性解析部分,大概分爲五部分內容:
- 解析註解屬性;解析註解的屬性,將其轉爲
AttributeMethods
對象; - 解析
@AliasFor
註解:基於AttributeMethods
對象,解析註解帶有@AliasFor
註解的屬性; - 映射互爲別名的屬性:爲該註解內通過
@AliasFor
形成互爲別名關係的屬性設置對應的MirrorSet
; - 映射子註解對元註解屬性的別名關係:將子註解中通過
@AliasFor
指向父註解的屬性的屬性值,覆蓋到父註解的對應屬性上; - 令子註解覆蓋父註解的同名屬性:將子註解中與父註解同名的屬性的屬性值,覆蓋到父註解的對應屬性上;
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
中的數組下標訪問和調用。
在構造函數中,我們也能看到提前聲明瞭好幾個數組:
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;
}
在這一步,他做了以下邏輯處理:
- 確定別名屬性所在的註解類:若
@AliasFor.annotation
屬性保持默認值Annotation.class
,則認爲別名屬性所在的註解就是當前解析的註解; - 確定別名屬性對應的方法名:優先獲取
@aliasFrom.attribute
同名屬性,若@AliasFrom.attribute
爲空則獲取@AliasFrom.value
指定的屬性名; - 從指定的註解類獲取方法名對應的屬性;
- 校驗該別名方法對應方法是否不是當前註解屬性的方法;
- 校驗別名方法返回值類型與當前註解屬性的方法返回值類型是否一致;
- 校驗聲明該方法的類就是註解指定的註解類;
最終,完成這一步後,將構建出以別名方法作爲 key
,當前註解中對應的原始屬性的方法作爲 value
的別名屬性-原始屬性映射表 aliasedBy
。
這裏有個比較有意思的地方,@AliasFor
註解中, value
和 attribute
屬性同樣存在 @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; // 繼續向聲明當前元註解的子註解遞歸
}
}
舉個例子,假如我們現在有如下結構:
現在在 Annotation1
的 AnnotationTypeMapping
中,對它的 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)
我們會在後續分析,這裏我們舉個例子說明一下上述過程:
一切開始前,我們從 Annotation1
向 Annotation3
遍歷,此時我們處理 Annotation1
的屬性 value1
:
當調用 processAliases
後:
- 由於別名鏈上非根屬性在根註解
Annotation3
中都不存在,別名不動,此時三個註解的aliasMappings
都不變; - 別名鏈上的三個屬性
value1
、value2
和value3
經過MirrorSet
處理後,發現value3
是優先級最高的,因而把它們的annotationValueSource
、annotationValueMappings
都分別更新爲Annotation3
和Annotation3.value3
的下標;
這樣處理後,當調用 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
中。
舉個例子,我們現在有一個註解,他的兩個屬性構成了互爲別名的關係,現在對其中一個進行處理,則有如下情況:
value
和alias
屬性在AnnotationAttributes
中對應的下標分別是0
和1
;- 處理時,由於
value
和alias
處於同一條別名鏈,因此MirrorSets.updateFrom
調用時會同時傳入這兩者; - 由於
value
和alias
都在ValueAttributeMeta
這注解中存在,因此MirrorSets
會分別爲它們在assigned
數組中對應的下標位置放入MirrorSet
實例; - 接着,調用
MirrorSet.update
時,發現assigned[0]
與assigned[1]
共享一個MirrorSet
實例,說明兩者是有聯繫的,然後就在該MirrorSet
實例中的indexes
數組記錄這兩個位置0
和1
;
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
如下圖:
把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
進行賦值,最終都能從兩個註解的各兩個屬性中拿到一樣的值:
五、映射屬性覆蓋
現在,通過 annotationValueMappings
,annotationValueSource
以及 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
同名字段,則將與當前屬性對應位置的annotationValueSource
和annotationValueMappings
設置爲該子註解和該註解中同名屬性的方法下標; - 若子註解的子註解中仍然存在同名註解,則選擇一個離根註解最近的子註解,重複上述過程;
- 重複上述兩步直到全部子註解遞歸完畢;
總結
回滾整個流程,我們瞭解了 Spring 中元註解的解析過程,與註解的屬性覆蓋與別名機制的實現。
當我們通過 MergedAnnotations
去從 AnnotatedElement
獲取註解的時候,會有專門的 AnnotationProcessor
——比如 MergedAnnotationFinder
——去把指定類型的註解上的全部元註解解析爲 AnnotationTypeMapping
,然後 AnnotationTypeMapping
在把一個一個的元註解對象轉爲 AnnotationTypeMapping
,讓他們形成類似鏈表的結構,從而維持父子註解間層次關係。
而當 AnnotationTypeMapping
在創建時,會遞歸解析 AnnotationTypeMapping
鏈表結構上的全部節點,然後解析他們的屬性,讓通過 @AliasFor
構成別名關係的屬性在各個註解中以 MirrorSet
的形式存在,從實現別名機制。
並且,在完成別名映射後,AnnotationTypeMapping
還會再次遞歸解析AnnotationTypeMapping
鏈表結構上的全部節點的屬性,讓子註解中與父註解具有相同名稱、類型的非 "value"
屬性覆蓋父註解中的屬性。