0%

Java反射之AnnotatedType接口

上一篇Java反射之获取注解的AnnotatedElement接口介绍了 AnnotatedElement 接口,它是用反射获取注解的顶级接口。其实现类或子接口包括 ClassTypeVariableParameterPackageAccessibleObjectGenericDeclaration 等,这些在前边的文章中已经介绍过。另外,它还有一个重要的接口 AnnotatedType,本篇将介绍它。

1. 再说注解

JDK1.5推出了注解功能,给Java语言带来非常强大的诸多特性。Java注解有几种保留策略:源码、运行时、类,可以在创建注解时通过 @Retention 元注解来指定,它需要引用 RetentionPolicy 中定义的策略:

  • SOURCE: 注解只在源码层面保留,编译后被丢弃

  • CLASS: 默认的保留策略,注解将由编译器记录在类文件中,但在运行时不需要由 JVM 保留

  • RUNTIME: 注解不仅保留在类文件,在运行时 JVM 也会保留它,因此可以通过反射获取注解

注解的作用大概可以分为以下几种:

  1. 提供编译期特性:Java提供的一些注解,可以进行编译期特性支持,比如 @SuppressWarnings, @Deprecated@Override 等可以提供编译期检查,@FunctionalInterface 标注函数式接口等

  2. 生成帮助文档:Java的 @Documented 注解可以用来标记生成 javadoc 时保留类或接口上的注解

  3. 运行时获取注解实现特定逻辑:这个是使用最多的场景,使用注解,并通过反射获取注解,来实现自身业务逻辑,大多数框架都使用注解来实现高级特性,比如 Spring 的 @Bean@Service 等注解

一句话,有了注解,我们可以方便的按需实现自身业务逻辑。在JDK1.8之前呢,注解所能表示标注的位置有限,JDK1.8又新增了两种标注位置,支持将注解标注在任何类型上。

2. 再说注解标注的位置

还记得 上一篇 介绍注解标注的位置信息吗?文中提到,通过 ElementType 枚举来定义注解可标注的位置信息,其他包括下边两项:

  1. ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上

  2. ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上

它们都是在JDK1.8时新增的,TYPE_PARAMETER 主要目的在于支持将注解标注在泛型类型的参数上,而 TYPE_USE 范围更广,可以将注解标注在任何Java类型上。也就是说,JDK1.8之前,注解只能标注在特定的位置(类、方法、包、参数、域、构造器、本地变量、注解),不能标注在泛型参数上,JDK1.8之后,可以标注在任何类型上。

使用TYPE_USE就可以省略其他 ElementType 元素吗?

既然 TYPE_USE 可以表示标注在所有类型上,那么是不是就不需要其他 ElementType 元素了呢?肯定不是!TYPE_USE 有其特殊的目的,它表示 “类型使用” 类型,其实就是指任何Java类型,但是它只能用 AnnotatedType 来描述并获取注解信息(见这里)。

比如,有一个注解 @MyAnno2,如果它的 @Target 上仅仅加上 ElementType.TYPE_USE,虽然可以标注在任何类型上,但是却可能获取不到注解。例如,假设将其标注在成员域上,通过 Filed 上的 getXXXAnnotation 等方法不能获取到注解,此时必须加上 ElementType.FIELD 才能正常工作,其他情况类似。

另外,标注了 TYPE_USE 其实是包含了 TYPE_PARAMETER,可以略去,但是反过来却不行。

比如下边的示例:

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_PARAMETER})
@Inherited
@interface TypeParamAnno {
String[] value() default "";
}

编写了一个注解,它只能作用于 类型参数 上。什么位置是类型参数呢?类型参数:指泛型类型申明时的参数,如T、E等,只能是泛型申明时,引用泛型类型时应该叫类型变量,而不是类型参数。看下边的示例:

1
2
3
4
5
6
7
8
9
class NumberHolder<@TypeParamAnno T extends Number> { (1)
T number;
List numbers;

public <@TypeParamAnno E> void m1(E e) { (2)
}

// ...
}
1 申明泛型类,泛型类型为T
2 申明泛型方法,泛型类型为E

可以看到,标记1、2两个位置的泛型申明处可以使用 TypeParamAnno 注解,其他地方均不可以,比如:

1
2
3
4
5
6
7
8
class NumberHolder<@typeparamanno t extends *@typeparamanno* number> { (1)
/*@TypeParamAnno*/ T number; (2)
List numbers; (3)

public <@typeparamanno e> void m1(/*@TypeParamAnno*/ E e) { (4)
}
// ...
}
1 试图标注在泛型上边界类型上,编译器报告错误:'@TypeParamAnno' not applicable to type use
2 试图标注在泛型成员域的泛型变量上,编译器报告错误:'@TypeParamAnno' not applicable to field
3 试图标注在泛型成员域的通配符类型上,编译器报告错误:'@TypeParamAnno' not applicable to type use
4 试图标注在泛型方法的泛型类型参数上,编译器报告错误:'@TypeParamAnno' not applicable to parameter

注意示例中被注释的部分,如果取消注释则会抛出如标记所描述的错误。可以看到,ElementType.TYPE_PARAMETER 指示的注解可标记位置是申明泛型类型时的参数上,比如申明泛型类、泛型方法时,其他不可用,如成员域不可以申明泛型,而只能引用泛型类型,或者使用通配符来定义泛型类型。

然而,ElementType.TYPE_USE 则可以标注在任何位置,如果将上述 TypeParamAnno 注解的 @Target 改为 @Target({ElementType.TYPE_USE}),则示例代码可以成功编译。

3. AnnotatedType接口

既然注解可以标记在泛型参数上了,那么如何才能通过反射拿到这些注解信息呢?所以,JDK8又新增一个 AnnotatedType 接口,用它来描述被注解标记的泛型类型。其定义如下:

1
2
3
public interface AnnotatedType extends AnnotatedElement {
public Type getType(); (1)
}
1 获取当前元素的描述类型,如果是带有注解的泛型类型,则用 AnnotatedType 组件描述,如果是不带注解的泛型用 Type 组件(除了 Class)描述,如果不是泛型类型,则返回类型本身

它继承了 AnnotatedElement 接口,可以直接获取注解信息,并且新增了 getType,该方法返回被注解标注的具体描述类型,这些类型可以是 Type 的5大组件(看这里),也可以是 AnnotatedType 的组件。

3.1. AnnotatedType的组件

AnnotatedType 包含5大组件,它们的关系如下图所示:

annotated type class rel
Figure 1. AnnotatedType的组件

从图上可以看到,AnnotatedType 包括4个子接口和一个默认实现类。这些子接口和实现分别是:

  • AnnotatedParameterizedType:描述带注解的参数化类型

  • AnnotatedTypeVariable:描述带注解的类型变量

  • AnnotatedArrayType:描述带注解的泛型数组

  • AnnotatedWildcardType:描述带注解的通配符类型

  • AnnotatedTypeBaseImpl:其他4个子接口不能描述的,就用这个默认实现来描述,此时 getType 方法可能返回某一 Type 的5大组件,比如返回 ClassTypeVariable 等等

AnnotatedType 的这几个组件与 Type 的几个组件都有对应,不清楚的可以看这里,接口定义的方法也差不多,无非是将方法的返回类型定义为 AnnotatedType 以便可以描述带注解的泛型类型

其实,AnnotatedTypeBaseImpl 类以及4大接口的实现类都位于 AnnotatedTypeFactory 类中,这是一个JDK内部的工厂类,用来构建 AnnotatedType 实现类。

4. 获取泛型上的注解

现在,我们来看看如何获取泛型类型上的注解信息。

现在编写一个注解,代码如下:

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE
})
@Inherited
@interface MyAnno2 {
String[] value() default "";
}

为了方便,我将该注解设置为可以标记在任何位置,该注解的 value 属性可以用来设置一些描述信息。

然后,准备一个测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class AnnotationClass<@myanno2("type-parameter-class") t extends @myanno2("type-use-class-number") number & @myanno2("type-use-serializable") serializable> { (1)
@MyAnno2("item")
private Object item; (2)

@MyAnno2("field")
private T type; (3)

private List<@myanno2("type-use") t> list; (4)

private @MyAnno2("type-use-array") T[] array; (5)

private List<@myanno2("type-use-wildcard") ? extends @myanno2("type-use-number") number> list1; (6)

@MyAnno2("constructor")
public AnnotationClass() { (7)

}

public <@myanno2("type-parameter-method") i> void method(@MyAnno2("parameter") List<@myanno2("type-use-method") t> list, List list1) { (8)
@MyAnno2("local-variable")
int var = 1; (9)
}
}
1 泛型类型上以及其上边界类型上都标注了注解
2 普通成员域上标注了注解
3 泛型成员域上标注了注解
4 List泛型成员域上标注了注解
5 泛型数组成员域上标注了注解
6 通配符的List泛型成员域上标注了注解
7 构造函数上标注了注解
8 泛型方法上的泛型类型、泛型方法参数上都标注了注解
9 本地变量上标注了注解

可以看到,AnnotationClass 类标注了很多 @MyAnno2 注解,并都设置了描述信息。接下来,我们来看看如何获取这些注解。

4.1. 获取成员域上的注解

成员域(标记2和3上的注解)上获取注解前边的文章说过了,就是通过 Field 对象来获取注解,这里就不再多说了。

4.2. 获取类泛型参数上的注解

现在,想要获取泛型类上泛型参数的注解信息(标记1处的3个注解),应该如何实现呢?

4.2.1. 获取泛型父类的注解

翻遍了 Class 的api,并没有可以直接获取类上的泛型类型,但是JDK1.8增加了两个方法:

1
2
3
4
5
6
7
8
9
10
11
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
// ...

public AnnotatedType getAnnotatedSuperclass() {
// ...
}

public AnnotatedType[] getAnnotatedInterfaces() {
// ...
}
}

它们用来获取当前类的父类或接口,它们可以用 AnnotatedType 来描述,也就是说它们申明了泛型并且被注解标注。

看一个示例,现在编写一个子类:

1
2
class AnnotationSubClass1 extends AnnotationClass<@myanno2("super-class-1") t> {
}

子类也定义为泛型(不是泛型也可以, 主要继承的父类申明了泛型都可以获取),然后想要获取这里的注解,可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AnnotatedTypeDemo {
// ...

public static void annotatedTypeVariable() {
AnnotatedType annotatedType1 = AnnotationSubClass1.class.getAnnotatedSuperclass(); (1)
Assert.isTrue(annotatedType1 instanceof AnnotatedParameterizedType);
AnnotatedParameterizedType apt = (AnnotatedParameterizedType) annotatedType1;
AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments(); (2)
// 只有一个泛型参数
AnnotatedTypeVariable annotatedTypeVariable1 = (AnnotatedTypeVariable) annotatedActualTypeArguments[0];
Annotation[] annotations = annotatedTypeVariable1.getAnnotations(); (3)
System.out.println(Arrays.toString(annotations));
}

// ...
}
1 获取被注解标注的泛型父类的 AnnotatedType 类型,它必定是一个 AnnotatedParameterizedType,因为申明了泛型参数(参数化类型)而且带注解
2 通过 AnnotatedParameterizedTypegetAnnotatedActualTypeArguments 方法获取实际类型参数的 AnnotatedType 类型,很明显,它应该是 AnnotatedTypeVariable (被注解标注的类型变量)
3 获取注解信息

上边的示例成功获取到了类型,输出结果如下:

[@com.belonk.lang.reflect.MyAnno2(value=[super-class-1])]

4.2.2. 获取类上的泛型参数的注解

回到上一个问题:如何获取泛型类上泛型参数的注解信息呢?比如想要获取示例类中标记1处的 @MyAnno2("type-parameter-class") 这个注解。

既然 Class 上没有对应的api,我们可以通过泛型成员域来获取(示例类 的标记3),因为成员域 type 的类型为 T , 我们可以通过这个泛型类型来获取它上标注的注解。

JDK1.8在 Feild 类上增加新的方法:

1
2
3
4
5
6
7
public final class Field extends AccessibleObject implements Member {
// ...

public AnnotatedType getAnnotatedType() {
// ...
}
}

这个 getAnnotatedType() 方法用来获得当前域的 AnnotatedType 类型。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AnnotatedTypeDemo {
// ...

public static void annotatedTypeVariable() {
Field field = AnnotationClass.class.getDeclaredField("type");
AnnotatedType annotatedType = field.getAnnotatedType();
// 是一个AnnotatedTypeVariable类型,表示被注解标注的类型变量
Assert.isTrue(annotatedType instanceof AnnotatedTypeVariable); (1)
AnnotatedTypeVariable annotatedTypeVariable = (AnnotatedTypeVariable) annotatedType;
// 获取字段上定义的注解:@MyAnno2("field")
declaredAnnotations = annotatedTypeVariable.getDeclaredAnnotations(); (2)
Assert.isTrue(declaredAnnotations.length == 1);
System.out.println(Arrays.toString(declaredAnnotations));
// 获取类型变量 T 上的注解,getType获取到的是一个TypeVariable,通过它可以获取注解
TypeVariable typeVariable = (TypeVariable) annotatedTypeVariable.getType(); (3)
System.out.println(Arrays.toString(typeVariable.getAnnotations())); (4)
}

// ...
}
1 通过 Field 获取的 AnnotatedTypeAnnotatedTypeVariable,因为类型变量是 T, 它在类上声明了注解
2 获取成员域上的注解,这里显然是 @MyAnno2("field")
3 进一步获取描述类型变量 T 的具体类型,这里是 TypeVariable 实例
4 通过 TypeVariable 获取注解信息

示例成功获取到目标注解,输出如下:

[@com.belonk.lang.reflect.MyAnno2(value=[field])]
[@com.belonk.lang.reflect.MyAnno2(value=[type-parameter-class])]

4.2.3. 获取类的泛型参数上边界的注解

如果还想要获取示例类中标记1处的上边界上的两个注解(@MyAnno2("type-use-class-number")@MyAnno2("type-use-serializable")),就需要用到 AnnotatedTypeVariablegetAnnotatedBounds() 方法了。

示例代码同基于上一节,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AnnotatedTypeDemo {
// ...

public static void annotatedTypeVariable() {
Field field = AnnotationClass.class.getDeclaredField("type");
AnnotatedType annotatedType = field.getAnnotatedType();
// 是一个AnnotatedTypeVariable类型,表示被注解标注的类型变量
Assert.isTrue(annotatedType instanceof AnnotatedTypeVariable); (1)
AnnotatedTypeVariable annotatedTypeVariable = (AnnotatedTypeVariable) annotatedType;
// 获取定义的泛型变量T的边界的注解类型
AnnotatedType[] annotatedBounds = annotatedTypeVariable.getAnnotatedBounds(); (2)
for (AnnotatedType annotatedBound : annotatedBounds) {
// 注解类型
System.out.print(annotatedBound + " - ");
// 打印边界的基础类型
System.out.print(annotatedBound.getType() + " - ");
// 查看定义的注解信息
declaredAnnotations = annotatedBound.getDeclaredAnnotations(); (3)
Assert.isTrue(declaredAnnotations.length == 1);
System.out.println(Arrays.toString(declaredAnnotations));
}
}

// ...
}
1 通过 Field 获取的 AnnotatedTypeAnnotatedTypeVariable
2 获取描述边界类型的 AnnotatedType 数组
3 获取申明的注解

输出:

sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@816f27d - class java.lang.Number - [@com.belonk.lang.reflect.MyAnno2(value=[type-use-class-number])]
sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@87aac27 - interface java.io.Serializable - [@com.belonk.lang.reflect.MyAnno2(value=[type-use-serializable])]

4.3. 获取泛型数组中泛型参数上的注解

想要获取示例类中标记5处的 @MyAnno2("type-use-array") 这个注解,需要使用 AnnotatedArrayType 组件,它只定义 getAnnotatedGenericComponentType() 方法,用来获取数组类型的潜在注释通用组件类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AnnotatedTypeDemo {
// ...

public static void annotatedArrayType() {
AnnotatedType annotatedType = AnnotationClass.class.getDeclaredField("array").getAnnotatedType(); (1)
Assert.isTrue(annotatedType instanceof AnnotatedArrayType);
AnnotatedArrayType annotatedArrayType = (AnnotatedArrayType) annotatedType;
AnnotatedTypeVariable annotatedTypeVariable = (AnnotatedTypeVariable) annotatedArrayType.getAnnotatedGenericComponentType(); (2)

Annotation[] declaredAnnotations = annotatedTypeVariable.getDeclaredAnnotations(); (3)
Assert.isTrue(declaredAnnotations.length == 1);
System.out.println(Arrays.toString(declaredAnnotations));
}

// ...
}
1 拿到数组属性上的AnnotatedType,数组类型并且标记了注解,因此它是 AnnotatedArrayType 实例
2 获取数组类型的潜在注释通用组件类型,数组使用类型变量T,该类型变量定义在Class上,故这里为 AnnotatedTypeVariable 类型
3 获取泛型类型上标注的注解信息

输出如下:

[@com.belonk.lang.reflect.MyAnno2(value=[type-use-array])]

同上一节的示例一样,获取到了 AnnotatedTypeVariable,同样可以继续深入获取泛型 T 的上边界注解,这里不再啰嗦了。

4.4. 获取泛型通配符上的注解

想要获取示例类中标记6处的 @MyAnno2("type-use-wildcard") 这个注解,需要使用 AnnotatedWildcardType 组件,它定义获取通配符上边界的 getAnnotatedUpperBounds() 方法和获取通配符下边界的 getAnnotatedLowerBounds() 方法,同样都返回 AnnotatedType 组件实例。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class AnnotatedTypeDemo {
// ...

public static void annotatedArrayType() {
AnnotatedType list1 = AnnotationClass.class.getDeclaredField("list1").getAnnotatedType();
AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) list1; (1)

AnnotatedType[] annotatedActualTypeArguments = annotatedParameterizedType.getAnnotatedActualTypeArguments();
AnnotatedWildcardType annotatedWildcardType = (AnnotatedWildcardType) annotatedActualTypeArguments[0]; (2)

Annotation[] declaredAnnotations = annotatedWildcardType.getDeclaredAnnotations(); (3)
Assert.isTrue(declaredAnnotations.length == 1);
System.out.println(Arrays.toString(declaredAnnotations));

AnnotatedType[] annotatedLowerBounds = annotatedWildcardType.getAnnotatedLowerBounds(); (4)
Assert.isTrue(annotatedLowerBounds.length == 0);

AnnotatedType[] annotatedUpperBounds = annotatedWildcardType.getAnnotatedUpperBounds(); (5)
Assert.isTrue(annotatedUpperBounds.length == 1);
for (AnnotatedType annotatedUpperBound : annotatedUpperBounds) {
declaredAnnotations = annotatedUpperBound.getDeclaredAnnotations(); (6)
Assert.isTrue(declaredAnnotations.length == 1);
System.out.println(Arrays.toString(declaredAnnotations));
}
}

// ...
}
1 获取到域上的 AnnotatedType 实例,这里肯定是 AnnotatedParameterizedType 类型
2 获取实际的注解泛型类型,域上申明了通配符,因此这里肯定是 AnnotatedWildcardType 类型
3 获取泛型参数注解,就是 @MyAnno2("type-use-wildcard")
4 获取通配符的下边界类型,这里未定义
5 获取通配符的上边界类型,
6 获取上边界类型上定义的注解,就是 @MyAnno2("type-use-number")

示例代码输出结果如下:

[@com.belonk.lang.reflect.MyAnno2(value=[type-use-wildcard])]
[@com.belonk.lang.reflect.MyAnno2(value=[type-use-number])]

4.5. 获取泛型方法和泛型参数上的注解

获取构造器、方法、方法参数等泛型类型上的注解,思路大同小异。JDK1.8 在 MethodConstructor 的超类 Executable 上增加了如下方法:

  • AnnotatedType getAnnotatedReturnType(): 获取方法的带注解的泛型返回类型的描述 AnnotatedType 对象

  • AnnotatedType[] getAnnotatedParameterTypes(): 获取方法的带注解的泛型参数类型的 描述 AnnotatedType 对象

  • AnnotatedType[] getAnnotatedExceptionTypes(): 获取方法申明的带注解的泛型异常类型的描述 AnnotatedType 对象

Parameter 也增加一个 getAnnotatedType() 方法来获取参数的 AnnotatedType,实际上底层还是使用的 getAnnotatedParameterTypes() 方法。

比如,想要获取示例类中标记8处的3个注解,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class AnnotatedTypeDemo {
// ...

public static void annotatedArrayType() {
// 获取返回类型,这里为void
Method method = AnnotationClass.class.getMethod("method", List.class, List.class);
AnnotatedType annotatedReturnType = method.getAnnotatedReturnType();
System.out.println(annotatedReturnType.getType()); // void

// 获取方法泛型类型上的注解
TypeVariable[] typeParameters = method.getTypeParameters();
for (TypeVariable typeParameter : typeParameters) {
System.out.println(Arrays.toString(typeParameter.getAnnotations()));
}

// 获取参数注解
AnnotatedType[] annotatedParameterTypes = method.getAnnotatedParameterTypes();
for (AnnotatedType annotatedParameterType : annotatedParameterTypes) {
AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) annotatedParameterType;
System.out.println(Arrays.toString(annotatedParameterizedType.getAnnotations()));
AnnotatedType[] annotatedActualTypeArguments = annotatedParameterizedType.getAnnotatedActualTypeArguments();
for (AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments) {
AnnotatedTypeVariable annotatedTypeVariable = (AnnotatedTypeVariable) annotatedActualTypeArgument;
System.out.println(Arrays.toString(annotatedTypeVariable.getAnnotations()));
}
}
}

// ...
}

获取方法泛型类型申明上的注解,只需要先通过 method.getTypeParameters() 获取到 TypeVariable 即可,获取泛型方法参数上的注解,通过 method.getAnnotatedParameterTypes() 拿到泛型参数描述对象,然后便可以层层获取。

输出结果如下:

void
[@com.belonk.lang.reflect.MyAnno2(value=[type-parameter-method])]
[@com.belonk.lang.reflect.MyAnno2(value=[parameter])]
[@com.belonk.lang.reflect.MyAnno2(value=[type-use-method])]
[]
[]

5. 总结

JDK1.8注解可以标注在任何类型上,获取注解的方式也随之扩展了。AnnotatedType 接口的目的在于获取泛型类型上的注解,包括泛型参数、通配符、上下边界等,前提是目标元素声明了泛型类型并且标注了注解,否则其 getType 方法将返回原始类型。

本文示例代码见: github.

~赞赏是不耍流氓的鼓励😄~

欢迎关注我的其它发布渠道