Spring @AliasFor 实现源码分析

前言

上篇 Spring 注解编程模型 有提到,Spring 对 Java 中的注解进行了增强,使用组合注解或者属性别名,可以让注解中的属性值覆盖元注解的属性值,并且不同的属性可以互为别名,这样在使用时只需要指定其中一个属性,其别名值也间接进行了提供。这篇便从源码进行入手,尝试分析其内部的实现。

@AliasFor 在 Spring 中的应用

Spring MVC 中 @GetMapping、@PostMapping、@PutMapping 及 @DeleteMapping 注解便使用了组合注解及别名的特性,这些注解都被元注解 @RequestMapping 标注。以 @PostMapping 为例,其源码如下。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {
    
    

	@AliasFor(annotation = RequestMapping.class)
	String name() default "";

	@AliasFor(annotation = RequestMapping.class)
	String[] value() default {
    
    };

	@AliasFor(annotation = RequestMapping.class)
	String[] path() default {
    
    };


	@AliasFor(annotation = RequestMapping.class)
	String[] params() default {
    
    };


	@AliasFor(annotation = RequestMapping.class)
	String[] headers() default {
    
    };

	@AliasFor(annotation = RequestMapping.class)
	String[] consumes() default {
    
    };

	@AliasFor(annotation = RequestMapping.class)
	String[] produces() default {
    
    };

}

遵循这种方式,我们自定义的请求映射注解也可以被 Spring 获取到,Spring 在获取请求映射地址时并不关心处理器或者处理器方法上请求映射地址的注解,而是使用了统一的方式从处理器或处理器方法上获取 @RequestMapping 注解或元注解的值。为了方便的获取注解信息,Spring 提供了一个工具类 AnnotatedElementUtils,获取 @RequestMapping 元注解的代码为 RequestMapping annot = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);。后面我们就从这个方法入手,看其内部的实现流程。

@AliasFor 实现框架一览

由于其内部实现涉及的类较多,这里简单画了一个类图,可放大观看,具体如下。
Spring 注解增强类图
其中涉及的类作用如下,无需进行记忆,这里先有一个印象,知道每个类大概的作用,后面慢慢提到,重点关注 TypeMappedAnnotations、TypeMappedAnnotation、AnnotationTypeMapping。

  • MergedAnnotations:表示注解的集合,提供了创建实例、获取注解或元注解的方法。

    • MergedAnnotationSelector:注解选择器,用于从不同的注解选择合适的注解或判断注解是否为最合适的注解。MergedAnnotations 可使用它获取注解。

    • SearchStrategy:注解搜索策略,表示是否搜索父类、接口等。MergedAnnotations 从可注解元素创建实例时使用。

    • RepeatableContainers:可重复注解容器,MergedAnnotations 从可注解元素创建实例时使用。

      • AttributeMethods:提供根据注解类型获取实例、获取注解的属性方法的方法。
    • AnnotationFilter:注解过滤器,MergedAnnotations 从可注解元素创建实例时使用,匹配的注解将会跳过,不进行处理。

    • TypeMappedAnnotations:MergedAnnotations 的子类。

      • AnnotationsScanner:在可标注的元素的注解层次中搜索相关注解的扫描器。TypeMappedAnnotations 使用其判断给定的注解是否为元标注或获取 MergedAnnotation 实例。
      • AnnotationsProcessor:注解处理器,TypeMappedAnnotations 判断注解是否直接或间接存在于源上时使用。其实现为 TypeMappedAnnotations 的内部类。
    • MergedAnnotationsCollection:MergedAnnotations 的子类。

  • MergedAnnotation:表示注解集合中的一个注解,提供了创建实例、获取注解层次元信息及注解属性的方法。MergedAnnotations 中可根据注解类型获取其实例。

    • AnnotationAttributes:表示注解的所有属性,提供了获取给定名称的属性值的方法。可从 MergedAnnotation 中获取。
    • TypeMappedAnnotation:MergedAnnotation 的实现类。
    • MissingMergedAnnotation:MergedAnnotation 的实现类,表示不存在的注解。
  • AnnotationTypeMappings:注解类型映射工具类,可通过给定的注解类型实例化,提供了获取 AnnotationTypeMapping 的方法。

    • AnnotationTypeMapping:具有层次结构的注解类型映射,与可注解元素中的其中一个注解或元注解对应,持有别名信息。由 AnnotationTypeMappings 实例化并持有。@AliasFor 实现的重要组成部分。
      • MirrorSets: AnnotationTypeMapping 内部类,表示一组镜像。
        • MirrorSet: AnnotationTypeMapping 内部类,表示互为别名的属性方法的镜像。

@AliasFor 相关源码分析

由于源码较多,这里把重点放在和 @AliasFor 关联较强的部分,其他源码略作提及,感兴趣的小伙伴可以自行查阅相关源码。通过序号表示相关流程,如 1,2,3 表示先执行1,再执行2,然后执行3。执行1时又有可能分成不同的小步骤,则可以用 1.1、1.2、1.3 进行表示,这样便不会迷失在代码中。 使用的 spring-framework 版本号为 5.2.6。
AnnotatedElementUtils#findMergedAnnotation 源码如下,

	/**
	 * 从可注解元素的注解层次结构中查找给定注解类型对应的注解实例
	 * 
	 * @param element 可注解的元素,如 Class 或 Method。
	 * @param annotationType 要查找的注解类型
	 * @return 合并属性后的注解
	 */
	public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
    
    
		// 如果注解类型是 java.lang 包下面或 org.springframework.lang 包下面,
		// 或者可注解元素是 java 包下面的,直接获取可注解元素上的注解
		if (AnnotationFilter.PLAIN.matches(annotationType) ||
				AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
    
    
			return element.getDeclaredAnnotation(annotationType);
		}
		// 获取合并的注解
		// 步骤1:先获取 TypeMappedAnnotations
		return findAnnotations(element)	
				// 步骤2:再获取 MergedAnnotation
				.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())  
				// 步骤3:获取合成后的注解实例
				.synthesize(MergedAnnotation::isPresent).orElse(null);	
	}

findMergedAnnotation 代码较为简单,优先获取元素直接标注的注解,如果不满足条件,则会获取合成的注解实例。跟踪findAnnotations(element)方法的源码,发现调用到的方法如下。

	// 步骤1.1:获取 TypeMappedAnnotations 实例
	static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
			RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
    
    

		if (AnnotationsScanner.isKnownEmpty(element, searchStrategy)) {
    
    
			return NONE;
		}
		return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers, annotationFilter);
	}

这里先判断是否存在可处理的注解,如果不存在则返回 NONE,否则实例化 TypeMappedAnnotations,实例化时直接把参数赋值给成员变量,TypeMappedAnnotations 是表示某一个可注解元素的注解的集合,可以通过其提供的方法获取注解或元注解。

拿到 TypeMappedAnnotations 实例后, AnnotatedElementUtils#findMergedAnnotation 方法开始调用
MergedAnnotations#get(Class<A>,Predicate<? super MergedAnnotation<A>>, MergedAnnotationSelector<A>) 方法。MergedAnnotationSelectors.firstDirectlyDeclared() 表示优先获取直接定义的注解。get 方法源码如下。

	
	public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
			@Nullable Predicate<? super MergedAnnotation<A>> predicate,
			@Nullable MergedAnnotationSelector<A> selector) {
    
    
		// 步骤2.1:不满足条件直接返回
		if (this.annotationFilter.matches(annotationType)) {
    
    
			return MergedAnnotation.missing();
		}
		// 步骤2.2:扫描注解层次结构,使用 MergedAnnotationFinder 处理所需的类型
		MergedAnnotation<A> result = scan(annotationType,
				new MergedAnnotationFinder<>(annotationType, predicate, selector));
		return (result != null ? result : MergedAnnotation.missing());
	}

这里开始对可注解元素的注解层次结构进行扫描,如果发现所需的注解类型,则通过 MergedAnnotationFinder 进行处理。MergedAnnotationFinder 处理流程核心代码如下。

		/**
		 * 处理数组
		 *
		 * @param type           所需的注解类型
		 * @param aggregateIndex
		 * @param source
		 * @param annotation     注解数组中的项
		 * @return 符合所需注解类型的注解
		 */
		@Nullable
		private MergedAnnotation<A> process(
				Object type, int aggregateIndex, @Nullable Object source, Annotation annotation) {
    
    
			// 从可重复的注解数组中查找所需注解
			Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
			if (repeatedAnnotations != null) {
    
    
				return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
			}
			// 步骤 2.2.3 创建 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);
					if (candidate != null && (this.predicate == null || this.predicate.test(candidate))) {
    
    
						if (this.selector.isBestCandidate(candidate)) {
    
    
							return candidate;
						}
						updateLastResult(candidate);
					}
				}
			}
			return null;
		}

MergedAnnotationFinder 先拿到 AnnotationTypeMappings 实例,AnnotationTypeMappings 表示某一个可注解元素的一组注解。然后循环处理 AnnotationTypeMappings 中的 AnnotationTypeMapping,创建 MergedAnnotation 并选择最合适的对象。AnnotationTypeMapping 是 @AliasFor 实现的重点,表示某一个具有层次结构的注解或元注解,实例化 AnnotationTypeMappings 时进行创建,其成员变量在实例化时全部进行赋值。AnnotationTypeMapping 成员变量如下。

	/**
	 * 源注解,即当前注解为元注解时,当前注解标注的注解
	 */
	@Nullable
	private final AnnotationTypeMapping source;

	/**
	 * 根注解,即当前注解为元注解时,注解层次结构中的顶层注解。
	 */
	private final AnnotationTypeMapping root;

	/**
	 * 注解距离根注解的距离,如果当前注解为根注解则为0
	 */
	private final int distance;

	/**
	 * 当前注解的类型
	 */
	private final Class<? extends Annotation> annotationType;

	/**
	 * 根注解到当前注解的列表
	 */
	private final List<Class<? extends Annotation>> metaTypes;

	/**
	 * 注解实例
	 */
	@Nullable
	private final Annotation annotation;

	/**
	 * 注解的属性方法
	 */
	private final AttributeMethods attributes;

	/**
	 * 注解中属性方法的镜像集合
	 */
	private final MirrorSets mirrorSets;

	/**
	 * 当前 attributes下标 -> root attributes 下标(是当前 attributes 下标的别名方法下标)
	 */
	private final int[] aliasMappings;

	/**
	 * 当前 attributes下标(或其别名下标) -> root attributes 同名属性方法的下标
	 */
	private final int[] conventionMappings;

	/**
	 * attributes 下标 -> 对应的(同名或别名)较高层次的 AnnotationTypeMapping 的 attributes 下标
	 */
	private final int[] annotationValueMappings;

	/**
	 * attributes 下标 -> 对应的(同名或别名)较高层次的 AnnotationTypeMapping
	 */
	private final AnnotationTypeMapping[] annotationValueSource;

	/**
	 * 注解属性方法 -> 使用 @AliasFor 标注了该注解属性方法名称的注解属性列表
	 */
	private final Map<Method, List<Method>> aliasedBy;

	/**
	 * 当前注解的属性值是否是合成的
	 */
	private final boolean synthesizable;

	/**
	 * 当前注解所有属性方法及其别名方法
	 */
	private final Set<Method> claimedAliases = new HashSet<>();

成员变量 aliasMappings 和 conventionMappings 将其属性方法的索引位置和其对应别名的属性方法索引位置建立关系,依此来实现 @AliasFor 注解。

MirrorSets 是 AnnotationTypeMapping 的内部类,表示镜像属性方法的集合,镜像属性方法是指互为别名的属性方法。
MirrorSets 成员变量如下。

		/**
		 * assigned的去重版本
		 */
		private MirrorSet[] mirrorSets;

		/**
		 * attributes下标 -> 属性对应的镜像 如注解A中,属性a和属性b互为别名,属性c和属性d互为别名,
		 * 属性e和属性f无别名, 属性a和属性b的镜像都为mirrorSet1,属性c和属性d的镜像都为mirrorSet2,
		 * 则assigned的值为[mirrorSet1,mirrorSet1,mirrorSet2,mirrorSet2,null,null]
		 */
		private final MirrorSet[] assigned;

MirrorSet 是 MirrorSets 的内部类,表示一组镜像方法的集合,其成员变量如下。

			/**
			 * 镜像的属性数量
			 */
			private int size;

			/**
			 * 注解属性互为别名的索引列表 注解A中,属性a和属性b互为别名,属性c和属性d互为别名,属性e和属性f无别名,
			 * <p>
			 * 当前MirrorSet可表示属性a和属性b的镜像mirrorSet1,取值为[0,1,-1,-1,-1]
			 */
			private final int[] indexes = new int[attributes.size()];

AnnotationTypeMapping、MirrorSets、MirrorSet 中的成员变量是实现 @AliasFor 重点中的重点,相关方法都依赖这些成员变量。

AnnotatedElementUtils#findMergedAnnotation 方法拿到 MergedAnnotation 后开始合成所需的注解。创建注解实例时又会调用到TypeMappedAnnotation#createSynthesized方法,其源码如下。

	// 步骤 3.1 创建合成注解对象
	protected A createSynthesized() {
    
    
		if (getType().isInstance(this.rootAttributes) && !isSynthesizable()) {
    
    
			return (A) this.rootAttributes;
		}
		return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
	}

这里使用到了 Java JDK 的动态代理,处理方法如下。

	public Object invoke(Object proxy, Method method, Object[] args) {
    
    
		if (ReflectionUtils.isEqualsMethod(method)) {
    
    
			return annotationEquals(args[0]);
		}
		if (ReflectionUtils.isHashCodeMethod(method)) {
    
    
			return annotationHashCode();
		}
		if (ReflectionUtils.isToStringMethod(method)) {
    
    
			return annotationToString();
		}
		if (isAnnotationTypeMethod(method)) {
    
    
			return this.type;
		}
		if (this.attributes.indexOf(method.getName()) != -1) {
    
    
			return getAttributeValue(method);
		}
		throw new AnnotationConfigurationException(String.format(
				"Method [%s] is unsupported for synthesized annotation type [%s]", method, this.type));
	}

代理持有 MergedAnnotation 对象,当调用合成注解的方法时又会调用到TypeMappedAnnotation#getValue(int, boolean, boolean)方法,其源码如下。

	@Nullable
	private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) {
    
    
		AnnotationTypeMapping mapping = this.mapping;
		if (this.useMergedValues) {
    
    
			int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
			if (mappedIndex == -1 && useConventionMapping) {
    
    
				mappedIndex = this.mapping.getConventionMapping(attributeIndex);
			}
			if (mappedIndex != -1) {
    
    
				// 优先从属性对应的较高层次结构中别名属性中获取值
				mapping = mapping.getRoot();
				attributeIndex = mappedIndex;
			}
		}
		if (!forMirrorResolution) {
    
    
			attributeIndex =
					(mapping.getDistance() != 0 ? this.resolvedMirrors : this.resolvedRootMirrors)[attributeIndex];
		}
		if (attributeIndex == -1) {
    
    
			return null;
		}
		if (mapping.getDistance() == 0) {
    
    
			Method attribute = mapping.getAttributes().get(attributeIndex);
			Object result = this.valueExtractor.extract(attribute, this.rootAttributes);
			return (result != null ? result : attribute.getDefaultValue());
		}
		return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
	}

这里主要就是根据属性之间的别名关系,获取到对应的较高层次的别名方法,因为较高层次的别名方法将会重写元注解的属性值,@AliasFor 和组合注解得以实现。至此相关源码已经简单进行了分析。

总结

本篇通过分析 Spring 注解相关源码了解到,Spring @AliasFor 和组合注解的实现,主要是解析注解的层次结构以及属性方法上的 @AliasFor 注解,使得元注解的属性和对应的注解的属性建立关联关系,在获取元注解的属性值时根据关联关系取到注解的属性值,从而实现了 @AliasFor 以及注解重写了元注解的属性值。由于源码较多,本篇只是选择较为重要的部分进行展示,源码分析的注释已上传到 Github,感兴趣的同学可下载获取。

猜你喜欢

转载自blog.csdn.net/zzuhkp/article/details/107748781