前言
众所周知,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"
属性覆盖父注解中的属性。