上一篇Java反射之获取注解的AnnotatedElement接口介绍了 AnnotatedElement
接口,它是用反射获取注解的顶级接口。其实现类或子接口包括 Class
、TypeVariable
、Parameter
、Package
、AccessibleObject
、GenericDeclaration
等,这些在前边的文章中已经介绍过。另外,它还有一个重要的接口 AnnotatedType
,本篇将介绍它。
1. 再说注解
JDK1.5推出了注解功能,给Java语言带来非常强大的诸多特性。Java注解有几种保留策略:源码、运行时、类,可以在创建注解时通过 @Retention
元注解来指定,它需要引用 RetentionPolicy
中定义的策略:
SOURCE
: 注解只在源码层面保留,编译后被丢弃CLASS
: 默认的保留策略,注解将由编译器记录在类文件中,但在运行时不需要由 JVM 保留RUNTIME
: 注解不仅保留在类文件,在运行时 JVM 也会保留它,因此可以通过反射获取注解
注解的作用大概可以分为以下几种:
提供编译期特性:Java提供的一些注解,可以进行编译期特性支持,比如
@SuppressWarnings
,@Deprecated
和@Override
等可以提供编译期检查,@FunctionalInterface
标注函数式接口等生成帮助文档:Java的
@Documented
注解可以用来标记生成 javadoc 时保留类或接口上的注解运行时获取注解实现特定逻辑:这个是使用最多的场景,使用注解,并通过反射获取注解,来实现自身业务逻辑,大多数框架都使用注解来实现高级特性,比如 Spring 的
@Bean
、@Service
等注解
一句话,有了注解,我们可以方便的按需实现自身业务逻辑。在JDK1.8之前呢,注解所能表示标注的位置有限,JDK1.8又新增了两种标注位置,支持将注解标注在任何类型上。
2. 再说注解标注的位置
还记得 上一篇 介绍注解标注的位置信息吗?文中提到,通过 ElementType
枚举来定义注解可标注的位置信息,其他包括下边两项:
ElementType.TYPE_PARAMETER
: 标注在类型参数上,比如泛型参数T、E上ElementType.TYPE_USE
: 使用类型,注解可以标注在任何类型变量上
它们都是在JDK1.8时新增的,TYPE_PARAMETER
主要目的在于支持将注解标注在泛型类型的参数上,而 TYPE_USE
范围更广,可以将注解标注在任何Java类型上。也就是说,JDK1.8之前,注解只能标注在特定的位置(类、方法、包、参数、域、构造器、本地变量、注解),不能标注在泛型参数上,JDK1.8之后,可以标注在任何类型上。
使用TYPE_USE就可以省略其他 ElementType 元素吗?既然 比如,有一个注解 另外,标注了 |
比如下边的示例:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_PARAMETER})
@Inherited
@interface TypeParamAnno {
String[] value() default "";
}
编写了一个注解,它只能作用于 类型参数
上。什么位置是类型参数呢?类型参数:指泛型类型申明时的参数,如T、E等,只能是泛型申明时,引用泛型类型时应该叫类型变量,而不是类型参数。看下边的示例:
class NumberHolder<@TypeParamAnno T extends Number> { (1)
T number;
List<? extends Number> numbers;
public <@TypeParamAnno E> void m1(E e) { (2)
}
// ...
}
1 | 申明泛型类,泛型类型为T |
2 | 申明泛型方法,泛型类型为E |
可以看到,标记1、2两个位置的泛型申明处可以使用 TypeParamAnno
注解,其他地方均不可以,比如:
class NumberHolder<@TypeParamAnno T extends /*@TypeParamAnno*/ Number> { (1)
/*@TypeParamAnno*/ T number; (2)
List</*@TypeParamAnno */ ? extends Number> 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
接口,用它来描述被注解标记的泛型类型。其定义如下:
public interface AnnotatedType extends AnnotatedElement {
public Type getType(); (1)
}
1 | 获取当前元素的描述类型,如果是带有注解的泛型类型,则用 AnnotatedType 组件描述,如果是不带注解的泛型用 Type 组件(除了 Class )描述,如果不是泛型类型,则返回类型本身 |
它继承了 AnnotatedElement
接口,可以直接获取注解信息,并且新增了 getType
,该方法返回被注解标注的具体描述类型,这些类型可以是 Type
的5大组件(看这里),也可以是 AnnotatedType
的组件。
3.1. AnnotatedType的组件
AnnotatedType
包含5大组件,它们的关系如下图所示:
从图上可以看到,AnnotatedType
包括4个子接口和一个默认实现类。这些子接口和实现分别是:
AnnotatedParameterizedType
:描述带注解的参数化类型AnnotatedTypeVariable
:描述带注解的类型变量AnnotatedArrayType
:描述带注解的泛型数组AnnotatedWildcardType
:描述带注解的通配符类型AnnotatedTypeBaseImpl
:其他4个子接口不能描述的,就用这个默认实现来描述,此时getType
方法可能返回某一Type
的5大组件,比如返回Class
、TypeVariable
等等
AnnotatedType
的这几个组件与 Type
的几个组件都有对应,不清楚的可以看这里,接口定义的方法也差不多,无非是将方法的返回类型定义为 AnnotatedType
以便可以描述带注解的泛型类型。
其实,AnnotatedTypeBaseImpl
类以及4大接口的实现类都位于 AnnotatedTypeFactory
类中,这是一个JDK内部的工厂类,用来构建 AnnotatedType
实现类。
4. 获取泛型上的注解
现在,我们来看看如何获取泛型类型上的注解信息。
现在编写一个注解,代码如下:
@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
属性可以用来设置一些描述信息。
然后,准备一个测试类:
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<I> 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增加了两个方法:
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
// ...
public AnnotatedType getAnnotatedSuperclass() {
// ...
}
public AnnotatedType[] getAnnotatedInterfaces() {
// ...
}
}
它们用来获取当前类的父类或接口,它们可以用 AnnotatedType
来描述,也就是说它们申明了泛型并且被注解标注。
看一个示例,现在编写一个子类:
class AnnotationSubClass1<T extends Number> extends AnnotationClass<@MyAnno2("super-class-1") T> {
}
子类也定义为泛型(不是泛型也可以, 主要继承的父类申明了泛型都可以获取),然后想要获取这里的注解,可以这样:
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 | 通过 AnnotatedParameterizedType 的 getAnnotatedActualTypeArguments 方法获取实际类型参数的 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
类上增加新的方法:
public final class Field extends AccessibleObject implements Member {
// ...
public AnnotatedType getAnnotatedType() {
// ...
}
}
这个 getAnnotatedType()
方法用来获得当前域的 AnnotatedType
类型。
代码如下:
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 获取的 AnnotatedType 是 AnnotatedTypeVariable ,因为类型变量是 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")
),就需要用到 AnnotatedTypeVariable
的 getAnnotatedBounds()
方法了。
示例代码同基于上一节,如下所示:
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 获取的 AnnotatedType 是 AnnotatedTypeVariable |
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()
方法,用来获取数组类型的潜在注释通用组件类型。
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
组件实例。
示例代码如下:
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 在 Method
、Constructor
的超类 Executable
上增加了如下方法:
AnnotatedType getAnnotatedReturnType()
: 获取方法的带注解的泛型返回类型的描述AnnotatedType
对象AnnotatedType[] getAnnotatedParameterTypes()
: 获取方法的带注解的泛型参数类型的 描述AnnotatedType
对象AnnotatedType[] getAnnotatedExceptionTypes()
: 获取方法申明的带注解的泛型异常类型的描述AnnotatedType
对象
类 Parameter
也增加一个 getAnnotatedType()
方法来获取参数的 AnnotatedType
,实际上底层还是使用的 getAnnotatedParameterTypes()
方法。
比如,想要获取示例类中标记8处的3个注解,代码如下:
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<Method>[] typeParameters = method.getTypeParameters();
for (TypeVariable<Method> 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.