【spring解析注解之AnnotationUtils】


一、spring版本号

5.3.32-SNAPSHOT

二、关键类说明

以下所有类都在org.springframework.core.annotation包下。对每个类的总结说明都是自己的理解,如有异议可以去看源码中的注释0.0

2.1、AnnotationTypeMapping

注解映射类。 将一个注解的所有信息进行整理。没有继承或实现任何类。
主要关注的属性字段如下表:

字段说明
private final AnnotationTypeMapping source;源注解映射信息。源注解本身实例此属性为null
private final AnnotationTypeMapping root;根注解映射信息。根注解此属性就是自身实例对象
private final int distance;当前注解的AnnotationTypeMapping实例到源注解的AnnotationTypeMapping实例的层级,源注解的AnnotationTypeMapping实例本身为0
private final Class<? extends Annotation> annotationType;注解类型的Class对象,泛型为任何继承自Annotation的类
private final List<Class<? extends Annotation>> metaTypes;自身annotationType及所有上级的注解AnnotationTypeMapping实例中annotationType属性的集合
private final Annotation annotation;注解代理类生成的实例对象。因为源注解在使用的时候用户一般会指定其中某些属性的值,所以此属性为null。而源注解中使用的其他注解已经固定不变,因此会将这些注解的代理的实例存在此字段。
private final AttributeMethods attributes;注解中所有的方法集合

2.2、AttributeMethods

注解中的方法集合类。将注解中所有方法进行整理。没有继承或实现任何类。
主要关注的属性字段如下表:

字段说明
private final Class<? extends Annotation> annotationType;注解的类型Class对象,泛型为任何继承自Annotation的类
private final Method[] attributeMethods;注解中方法的数组
private final boolean hasDefaultValueMethod;是否有默认值
private final boolean hasNestedAnnotation;是否有嵌套注解

2.3、AnnotationTypeMappings

注解映射类(AnnotationTypeMapping)的集合。没有继承或实现任何类。
主要关注的属性字段如下表:

字段说明
private final List<AnnotationTypeMapping> mappings;注解映射类(AnnotationTypeMapping)的集合

2.4、TypeMappedAnnotation

提供解析注解的相关方法,构造函数中包含JDK动态代理生成的注解的实例对象。
继承自AbstractMergedAnnotation<A extends Annotation>,实现了MergedAnnotation<A extends Annotation>
类图如下:
在这里插入图片描述
主要关注的属性字段如下表:

字段说明
private final AnnotationTypeMapping mapping;2.1、AnnotationTypeMapping
private final Object rootAttributes;需要解析的注解的实例
private final ValueExtractor valueExtractor;从注解实例中获取属性值的方式

2.5、AnnotationAttributes

注解解析结果。
继承自LinkedHashMap<String, Object>。泛型指明KEY值为String类型。
类图如下:
在这里插入图片描述

2.6、小结

以上五个类中AnnotationTypeMapping、AttributeMethods、AnnotationTypeMappings存储注解的结构信息,所以spring在这三个类中加了缓存处理。
TypeMappedAnnotation类用来解析注解,所以构造函数中必须传入注解的实例。
AnnotationAttributes类使用Map来返回解析结果。

三、DEBUG源码分析

spring在解析注解的时候加入了自定义注解@AliasFor的处理,此篇文章不会对这种场景进行DEBUG跟踪。这里只对两种场景进行DEBUG代码跟踪分析。第一种是普通注解且没有嵌套注解的场景。第二种是有嵌套注解的场景。这里的嵌套注解指的是注解的某个方法的返回值是其他注解。

以下使用spring源码中的单元测试类org.springframework.core.annotation.AnnotationUtilsTests

3.1、场景1:无嵌套注解

先给出测试的注解

package org.springframework.core.testfixture.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Copy of the standard {@code Component} annotation for testing purposes.
 *
 * @author Mark Fisher
 * @since 2.5
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}

Component注解又被@Indexed注解注释(其他注解咱们可以忽略掉,因为spring解析的时候默认会忽略掉java.lang及org.springframework.lang包下的注解,后面DEBUG我们会看到)。

package org.springframework.core.testfixture.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Copy of the standard {@code Indexed} annotation for testing purposes.
 *
 * @author Stephane Nicoll
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Indexed {
}

注解Component使用的地方是单元测试中的一个静态内部类(这里我把类中的内容都删除了,只留下了注解的信息)。

@Component("webController")
static class WebController {}

然后给出spring的单元测试方法。

	@Test
	void getAnnotationAttributesWithoutAttributeAliases() {
		Component component = WebController.class.getAnnotation(Component.class);
		assertThat(component).isNotNull();

		AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(component);

		assertThat(attributes).isNotNull();
		assertThat(attributes.getString(VALUE)).as("value attribute: ").isEqualTo("webController");
		assertThat(attributes.annotationType()).isEqualTo(Component.class);
	}

启动单元测试。这里说明下,Java中的注解实例对象是通过JDK的动态代理创建的。
在这里插入图片描述
getAnnotationAttributes方法有许多的重载。咱们直接跳到目标方法查看。下图中的Adapt[] adaptations可以不用考虑。MergedAnnotation.from(annotatedElement, annotation)是接口MergedAnnotation中的方法,目的是返回TypeMappedAnnotation实例对象(2.4、TypeMappedAnnotation)。
在这里插入图片描述
在创建TypeMappedAnnotation对象前,需要先解析注解,生成AnnotationTypeMappings并缓存。
在这里插入图片描述
从缓存中获取Component注解的结构信息,如果没有拿到则创建并存入缓存。其中Cache是AnnotationTypeMappings的一个私有静态内部类。
在这里插入图片描述
没有缓存,新建Cache,然后通过Cache的get方法获取数据。咱们继续看Cache类中的get方法。get方法从缓存对象Cache中的mappings属性中获取数据,没有拿到则创建AnnotationTypeMappings(2.3、AnnotationTypeMappings)对象。
在这里插入图片描述
直接看AnnotationTypeMappings的构造方法。注意这里的构造方法没有传入注解的实例,只是传入了注解的Class对象(这里暂时不关心用户配置的注解信息,只关注注解本身的结构信息)。
在这里插入图片描述
构造方法中调用了addAllMappings(annotationType, visitedAnnotationTypes);方法。此方法中创建了一个队列。作用是循环解析Component注解需要的所有AnnotationTypeMapping(2.1、AnnotationTypeMapping),注解Component还被@Indexed注释,所以我们后面会看到创建了两个AnnotationTypeMapping实例。
在这里插入图片描述
addIfPossible方法中只是创建了AnnotationTypeMapping实例,我们直接看AnnotationTypeMapping的构造方法。
在这里插入图片描述
从入参annotationType可以看到先创建Component注解的AnnotationTypeMapping实例。
source为null。
distance为0(后面创建@Indexed注解的AnnotationTypeMapping实例时在此基础上+1)。
因为我们只关注Component注解的结构信息,因此annotation为空。
attributes是Component注解中的所有方法集合,可以看到只有一个value方法。
在这里插入图片描述
构造方法后面的操作是和嵌套注解及@AliasFor注解相关的,就不细看了。Component注解的AnnotationTypeMapping实例创建成功。返回addAllMappings方法。接下来需要创建Component注解中@Indexed注解的AnnotationTypeMapping实例。
在这里插入图片描述
addMetaAnnotationsToQueue方法入参中source参数就是刚创建的Component注解的AnnotationTypeMapping实例。通过AnnotationsScanner中的静态方法getDeclaredAnnotations获取Component注解被哪些注解注释,源码中会通过

AnnotationFilter PLAIN = packages("java.lang", "org.springframework.lang");

进行筛选,所以我们看到下面的metaAnnotations中有四个元素,前三个元素被置为null,最后一个元素就是通过动态代理生成的@Indexed注解的实例。接下来通过判断又会调用addIfPossible方法(前面通过此方法创建了Component注解的AnnotationTypeMapping实例)。

在这里插入图片描述
准备生成@Indexed注解的AnnotationTypeMapping实例。注意构造函数的入参,与Component注解不同的是,
1、入参source不再是null,而是刚才生成的Component注解的AnnotationTypeMapping实例,说明本次生成的AnnotationTypeMapping实例来源属于Component注解。(这样如果@Indexed注解再被其他注解注释,通过source属性就形成了类似链表式的数据结构)
2、入参ann也不再是null,而是动态代理生成的@Indexed注解的实例,因为在Component注解中@Indexed注解已经确定不会再有任何改变。
在这里插入图片描述
接下来生成@Indexed注解AnnotationTypeMapping的构造方法就不用细看了。
我们一直是在创建AnnotationTypeMappings实例。下图中AnnotationTypeMappings构造方法中创建Component注解相关AnnotationTypeMapping实例已经完成。后面为每个AnnotationTypeMapping实例执行其中的afterAllMappingsSet也是与别名相关的,这里不用看。
在这里插入图片描述
接下来创建TypeMappedAnnotation实例,TypeMappedAnnotation的构造方法中只获取AnnotationTypeMappings实例中第一个AnnotationTypeMapping实例(也就是Component注解的)。
参数annotation是通过动态代理生成的Component注解实例。
参数AnnotationUtils::invokeAnnotationMethod是指定如何调用注解中的每个方法的。
在这里插入图片描述
TypeMappedAnnotation实例创建成功后,我们就回到了最初的getAnnotationAttributes方法,直接看asMap方法。
在这里插入图片描述
先看下asMap方法的定义,如下:

	/**
	 * Create a new {@link Map} instance of the given type that contains all the annotation
	 * attributes.
	 * <p>The {@link Adapt adaptations} may be used to change the way that values are added.
	 * @param factory a map factory
	 * @param adaptations the adaptations that should be applied to the annotation values
	 * @return a map containing the attributes and values
	 */
	<T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations);

入参我们只关注Function<MergedAnnotation<?>, T> factory。这是java中的函数式编程,Function接口的定义如下(这里只列出了需要实现的apply方法)。可以看到Function的泛型要求apply方法入参为T类型,返回值为R类型。再看一下asMap方法的第一个入参:

mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType(), true)

这行代码的意思是匿名实现了Function接口且T为TypeMappedAnnotation,R为AnnotationAttributes,意味着R apply(T t);方法的入参为TypeMappedAnnotation类型,返回值为AnnotationAttributes类型,同时AnnotationAttributes继承自LinkedHashMap<String, Object>(2.5、AnnotationAttributes)。

@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

然后直接DEBUG跳到asMap方法的具体实现。第一行就是生成AnnotationAttributes对象实例,准备接收注解的解析结果。
AttributeMethods attributes = this.mapping.getAttributes();这一行代码就是从Component注解的AnnotationTypeMapping实例中拿到Component注解的所有方法,然后遍历获取每一个方法的返回值并存储到AnnotationAttributes实例中。可以看到KEY值为注解中的方法名,VALUE为处理后的结果。
在这里插入图片描述
直接看具体的getValue方法。
this.valueExtractor.extract(attribute, this.rootAttributes);就是获取注解中方法的具体值。
this.valueExtractor是我们在创建TypeMappedAnnotation对象实例时传入的获取注解中方法返回值的方式,这里使用的是JDK的动态代理获取的。
attribute参数是Component注解中的value()方法。
this.rootAttributes参数是通过JDK动态代理生成的Component注解实例。
在这里插入图片描述
后面源码还会对返回值类型进行处理,这里就不再展示了。
最后我们看一下解析结果,注意map的类型是AnnotationAttributes。
在这里插入图片描述

3.2、场景2:有嵌套注解

先给出测试的注解

	/**
	 * Mock of {@code org.springframework.context.annotation.ComponentScan}.
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@interface ComponentScan {
		Filter[] excludeFilters() default {};
	}

嵌套注解如下:

	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {
		String pattern();
	}

注解使用如下:

	@ComponentScan(excludeFilters = {@Filter(pattern = "*Foo"), @Filter(pattern = "*Bar")})
	static class ComponentScanClass {
	}

然后给出spring的单元测试方法。

	@Test
	void getAnnotationAttributesWithNestedAnnotations() {
		ComponentScan componentScan = ComponentScanClass.class.getAnnotation(ComponentScan.class);
		assertThat(componentScan).isNotNull();

		AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanClass.class, componentScan);
		assertThat(attributes).isNotNull();
		assertThat(attributes.annotationType()).isEqualTo(ComponentScan.class);

		Filter[] filters = attributes.getAnnotationArray("excludeFilters", Filter.class);
		assertThat(filters).isNotNull();

		List<String> patterns = stream(filters).map(Filter::pattern).collect(toList());
		assertThat(patterns).isEqualTo(asList("*Foo", "*Bar"));
	}

这里我们DEBUG跟踪的时候只关注和3.1、场景1:无嵌套注解有差异的地方。
在创建ComponentScan注解中与方法相关的AttributeMethods对象实例时,参数hasNestedAnnotation为true,表示有嵌套注解。
在这里插入图片描述
还有就是在创建ComponentScan注解的AnnotationTypeMapping实例时,构造方法中会执行如下代码,如果发现嵌套注解,则生成嵌套注解的AnnotationTypeMapping实例。
在这里插入图片描述
在解析注解中的返回值的时候,返回值类型如果是注解类型或者注解数组。则会将注解值封装成TypeMappedAnnotation对象实例。如下图:
在这里插入图片描述
之后还会对返回值继续处理,最终返回的值还是注解的动态代理实例。
在这里插入图片描述
看一下解析的返回结果。
在这里插入图片描述
可以使用AnnotationAttributes提供的方法获取注解中的嵌套注解实例对象。
看一下getAnnotationArray(“excludeFilters”, Filter.class);方法,只是通过Map的get方法获取到数据,然后根据入参Class expectedType进行类型强转。

	@SuppressWarnings("unchecked")
	public <A extends Annotation> A[] getAnnotationArray(String attributeName, Class<A> annotationType) {
		Object array = Array.newInstance(annotationType, 0);
		return (A[]) getRequiredAttribute(attributeName, array.getClass());
	}
	@SuppressWarnings("unchecked")
	private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
		Assert.hasText(attributeName, "'attributeName' must not be null or empty");
		Object value = get(attributeName);
		assertAttributePresence(attributeName, value);
		assertNotException(attributeName, value);
		if (!expectedType.isInstance(value) && expectedType.isArray() &&
				expectedType.getComponentType().isInstance(value)) {
			Object array = Array.newInstance(expectedType.getComponentType(), 1);
			Array.set(array, 0, value);
			value = array;
		}
		assertAttributeType(attributeName, value, expectedType);
		return (T) value;
	}

在这里插入图片描述
可以看到内存中的对象实例都是同一份,只是进行了类型强转。
在这里插入图片描述

3.3、@AliasFor的处理

这里大概说一下spring对其自定义注解@AliasFor的处理。
@AliasFor注解会指定被标注的注解方法可以共用另一个方法的返回结果。同时spring将注解中的所有方法按顺序存储。在解析注解的时候会进行判断,被@AliasFor注解注释的方法是使用自己的方法返回值,还是使用@AliasFor注解指向的数据。

举一个例子:
定义注解

	/**
	 * Mock of {@code org.springframework.web.bind.annotation.RequestMapping}.
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@interface WebMapping {

		String name();

		@AliasFor("path")
		String[] value() default "";

		@AliasFor(attribute = "value")
		String[] path() default "";

		RequestMethod[] method() default {};
	}

使用注解

		@WebMapping(value = "/test", name = "foo")
		public void handleMappedWithValueAttribute() {
		}

可以看到用户将WebMapping注解中value配置为“/test”。同时path方法被@AliasFor(attribute = “value”)指向value方法。下图是debug看到的结果,WebMapping注解解析后各方法的排序结果为:path下标为2,value的下标为3。所有可以看到各方法在获取值的时候实际应该使用的方法为[0,1,3,3]。意味着当解析value和path方法的时候都会使用value的方法返回结果。

在这里插入图片描述

四、总结

看spring解析注解的源码就像在看一份需求的实现。而且解析注解这份“需求”并不简单。
spring对注解的抽象,以及哪里该加缓存值得我们学习借鉴。
如果我们项目中依赖了spring的core包,解析注解的方法就不需要自己再实现了,使用现成的即可。

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值