大家好,又見面了,我是你們的朋友全棧君。
前言
衆所周知,
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
,實作各種别名功能。
相關文章:
- 深入了解Spring注解機制(一):注解的搜尋與處理機制;
- 深入了解Spring注解機制(二):元注解解析與屬性映射;
- 深入了解Spring注解機制(三):合并注解的合成;
一、建立合并注解聚合
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
- 然後将該合并注解通過
方法合成為一個符合條件的普通注解,該過程将基于處理後的合并注解,使用 JDK 動态代理生成一個指定注解類型的代理對象;synthesize
這裡我們重點關注
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
- 确認
對應位置是否為annotationValueMapping[0]
,不是則說明有映射關系;-1
- 最後從
位置取出annotationValueSource[0]
對應的Annotation1
,再從AnnotationTypeMapping
的位置取出 ;annotationValueMappings[0]
- 從
對應的Annotation1
中找到下标 對應的方法AnnotationTypeMapping
,然後調用并傳回值;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
;1
- 處理時,由于
和value
處于同一條别名鍊,是以alias
調用時會同時傳入這兩者;MirrorSets.updateFrom
- 由于
和value
都在alias
這注解中存在,是以ValueAttributeMeta
會分别為它們在MirrorSets
數組中對應的下标位置放入assigned
執行個體;MirrorSet
- 接着,調用
時,發現MirrorSet.update
與assigned[0]
共享一個assigned[1]
執行個體,說明兩者是有聯系的,然後就在該MirrorSet
執行個體中的MirrorSet
數組記錄這兩個位置 和indexes
;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"
屬性覆寫父注解中的屬性。
釋出者:全棧程式員棧長,轉載請注明出處:https://javaforall.cn/170764.html原文連結:https://javaforall.cn