java--最全注解解析
java--最全注解解析注解标准注解元注解Target 范围Retention 声明周期Documented 说明文档Inherited 传播注解元素注解元素的值注解元素的默认名称注解不支持继承注解解析器实例--定义注解注解类构造方法属性方法参数变量实例--解析注解注解类构造方法属性方法参数变量github地址:https://github.com/a18792721831/studyjdk.gitmodel:先来一张图总览:注解Annotation(注解)是 Java 提供的一种对元程序中_最全注解分类
文章信息
- 原文链接:https://jiayq.blog.csdn.net/article/details/108039289
- 发布时间:2020-08-16 17:38:26
- 阅读量:419
- 分类:java核心同时被 3 个专栏收录, 订阅专栏, Java8, Java基础
- 标签:#Java注解原理详解, #如何解析Java注解, #如何编写注解解析器, #注解解析器解析各种注解, #注解和注解解析器
摘要
文章浏览阅读419次。java–最全注解解析注解标准注解元注解Target 范围Retention 声明周期Documented 说明文档Inherited 传播注解元素注解元素的值注解元素的默认名称注解不支持继承注解解析器实例–定义注解注解类构造方法属性方法参数变量实例–解析注解注解类构造方法属性方法参数变量github地址:https://github.com/a18792721831/studyjdk.gitmodel:先来一张图总览:注解Annotation(注解)是 Java 提供的一种对元程序中_最全注解分类
java–最全注解解析
- 注解
- 标准注解
- 元注解
- Target 范围
- Retention 声明周期
- Documented 说明文档
- Inherited 传播
- Target 范围
- 注解元素
- 注解元素的值
- 注解元素的默认名称
- 注解不支持继承
- 注解解析器
- 实例–定义注解
- 注解
- 类
- 构造方法
- 属性
- 方法
- 参数
- 变量
- 注解
- 实例–解析注解
- 注解
- 类
- 构造方法
- 属性
- 方法
- 参数
- 变量
- 注解
github地址:
https://github.com/a18792721831/studyjdk.git
model:
先来一张图总览:
注解
Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。
标准注解
- @Override:表示当前的方法定义将覆盖超类中的方法。如果子类方法拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。
- @Deprecated:如果使用了注解为它的元素,编译器会发出警告信息。
- @SuppressWWarnings:关闭不当的编译器警告信息。
元注解
元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
Target 范围
@Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标。
Retention 声明周期
Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在 class 文件中有效(即 class 保留)
- RUNTIME:在运行时有效(即运行时保留)
Documented 说明文档
@ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。
Inherited 传播
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。
注解元素
- 所有的基本类型
- String
- Class
- enum
- Annotation
注意,注解中不允许使用任何包装类型。 但是因为自动拆装箱的存在,这个基本上不是问题,只是需要注意,定义时,定义为基本类型就行。
注解元素的值
注解元素要么有值,要么有默认值。
换句话说,只要定义了注解元素,那么在使用时,就不能使注解元素空着。
注解元素的默认名称
一般来说 ,注解元素的值在指定的时候都必须指明是哪个注解元素。
唯一例外:注解元素的名称是value,而且本次使用只指定这一个注解元素(其他注解元素有默认值,或者只有一个注解元素)。
注解不支持继承
不能使用关键字extends来继承某个@interface。不过,注解元素可以是注解,可以使用注解嵌套的方式,复用已有的注解。
但是这样会大大的增加注解使用的难度。
注解解析器
注解解析器说白了就是使用反射,获取对象的注解,然后通过反射得到的注解对象,执行注解元素的方法,最终获取注解元素的值。
最后根据注解元素的值,做不同的操作。
实例–定义注解
注解
1
2
3
4
5
6
7
8
9
10
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@StudyAnno(name = "this")
public @interface StudyAnno {
public String name() default "studyAnno";
}
类
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@StudyAnno(name = "studyClass")
@StudyClass(name = "this")
public @interface StudyClass {
public String name() default "studyClass";
}
构造方法
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyCons")
@StudyClass(name = "studyCons")
public @interface StudyCons {
@StudyMethod
public String name() default "studyCons";
}
属性
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyField")
@StudyClass(name = "studyField")
public @interface StudyField {
@StudyMethod(name = "studyField")
public String name() default "studyField";
}
方法
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyMethod")
@StudyClass(name = "studyMethod")
public @interface StudyMethod {
@StudyMethod(name = "name")
public String name() default "studyMethod";
}
参数
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyParam")
@StudyClass(name = "studyParam")
public @interface StudyParam {
@StudyMethod(name = "studyParam")
public String value() default "";
}
变量
1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyVar")
@StudyClass(name = "studyVar")
public @interface StudyVar {
@StudyMethod(name = "studyVar")
public String name() default "studyVar";
}
实例–解析注解
创建普通类,也就是使用注解的类
这两个普通的类有这样的一个特点:
子类没有使用任何注解
这个主要是验证注解的继承特性。
这是注解和类的一些描述类型的关系:
可以看到最顶层的是AnnotatedElelment接口。
注解解析器的处理流程都相同:注解解析器说白了就是使用反射,获取对象的注解,然后通过反射得到的注解对象,执行注解元素的方法,最终获取注解元素的值。
所以,创建一个公共的方法,这个公共的方法,就是处理的是AnnotatedElelment的对象。
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
/**
* 注解解析
*
* @param annotatedElement 需要处理的AnnotatedElement对象
* @param annClazz 需要处理的注解类型
*/
private static void parse(AnnotatedElement annotatedElement, Class annClazz) {
// 获取使用注解的对象上面的注解 (StudyClass对象)
Annotation declaredAnnotation = annotatedElement.getAnnotation(annClazz);
// 判断是否存在继承的注解,如果有继承的注解,那么获取继承的注解对象,否则是null
Annotation annotation = annotatedElement.isAnnotationPresent(annClazz) ? annotatedElement.getAnnotation(annClazz) : null;
// 将本身的注解和继承的注解的对象,进行流化,然后去重,去除null,最后遍历处理
Arrays.asList(declaredAnnotation, annotation).stream().distinct().filter(x -> x != null).forEach(x -> {
try {
// 获取注解对象中的注解元素 (name方法)
Method name = x.getClass().getDeclaredMethod("name", null);
// 执行注解元素,获取注解元素值 ("people")
Object nameValue = name.invoke(x, null);
// 获取到注解元素值之后做的操作
System.out.println(annClazz.getSimpleName() + " , name = " + nameValue);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
});
}
这个方法是注解解析的核心,我们创建了这个公共的方法后,只需要传入需要解析的对象即可:类、注解、构造、属性、方法、参数等等。
为了方便在控制台观察,我们增加一个日志方法,日志方法是注解解析方法的前置方法。但是这两个方法同时可用:
1
2
3
4
5
6
7
8
9
10
11
/**
* 记录日志
*
* @param annotatedElement
* @param annClass
* @param objClass
*/
private static void parse(AnnotatedElement annotatedElement, Class annClass, Class objClass) {
System.out.println("---------- parse\t" + annClass.getSimpleName() + ":\t" + objClass.getSimpleName() + "----------");
parse(annotatedElement, annClass);
}
日志方法打印一些友好查看的信息,然后转调核心解析方法。
注解
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @param object 普通对象
*/
private static void parseAnno(Object object) {
// 获取普通的所有的注解
Arrays.stream(object.getClass().getAnnotations()).filter(x -> x != null).forEach(x -> {
// 获取注解的注解对象
// 注意,这里不能使用反射。因为注解本质上是接口,所以,实际使用中,都是代理对象,当你使用反射获取的时候,获取得到的代理对象,这个代理对象没有注解信息了
// 使用annotationType方法,获取的是带有注解信息的注解对象(代理对象)
// 将含有注解信息的代理对象进行解析
parse(x.annotationType(), StudyAnno.class, object.getClass());
});
}
解析注解需要注意,传入核心解析的对象应该是注解的注解对象。
在主方法调用:
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
People people = new People("xiaomeiS");
Man man = new Man("hah");
// getDeclaredAnnotations:获取直接标注在指定对象上面的注解
// getAnnotations:获取指定对象上面的注解(可以获取继承的注解)
parseAnno(people);
parseAnno(man);
}
输出如下:
类
1
2
3
4
5
6
7
/**
* @param object 普通对象
*/
private static void parseClass(Object object) {
// 获取普通对象的class对象
parse(object.getClass(), StudyClass.class, object.getClass());
}
执行结果
构造方法
1
2
3
4
5
6
7
8
9
/**
* 解析构造
*
* @param object
*/
private static void parseCons(Object object) {
// 获取普通对象的Constructor对象
Arrays.stream(object.getClass().getDeclaredConstructors()).filter(x -> x != null).forEach(x -> parse(x, StudyCons.class, object.getClass()));
}
执行结果
属性
1
2
3
4
5
6
7
8
9
10
11
/**
* 解析属性
*
* @param object
*/
private static void parseField(Object object) {
// 获取对象的全部属性对象
Arrays.stream(object.getClass().getDeclaredFields()).filter(x -> x != null).forEach(x -> {
parse(x, StudyField.class, object.getClass());
});
}
执行结果:
因为People里面的属性都是private的,Man继承于People,但是私有属性没有被继承。
我们在People中增加其他三个权限的属性:
然后重新执行解析:
方法
1
2
3
4
5
6
7
8
9
10
11
/**
* 解析方法
*
* @param object
*/
private static void parseMethod(Object object) {
// 获取对象的全部方法
Arrays.stream(object.getClass().getDeclaredMethods()).filter(x -> x != null).forEach(x -> {
parse(x, StudyMethod.class, object.getClass());
});
}
执行结果:
虽然Peopl的方法是公有的,Man继承于People,那么,Man也能够调用公有方法,但是StudyMenthod注解是不可被继承的。
换句话说:因为StudyMenthod没有使用Inherited修饰,所以Man从People继承了方法,但是没有继承得到注解。
参数
在People中,只有构造方法有参数,而且我们只在这一个地方用到了StudyParam注解。所以,我们应该首先得到构造方法,然后在获取参数对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 解析参数
*
* @param object
*/
private static void parseParam(Object object) {
// 获取普通对象的构造器、方法(全部可以有参数的方法)
Constructor<?>[] declaredConstructors = object.getClass().getDeclaredConstructors();
Method[] declaredMethods = object.getClass().getDeclaredMethods();
Arrays.stream(declaredConstructors).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
Arrays.stream(declaredMethods).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
}
执行结果
纳尼,竟然异常了。嗯嗯。
看看异常堆栈吧,是没有找到方法的异常。
为什么呢?
调试一次吧:
原来我们在注解中定义的方法名称不是name了,而是value了。
但是在代码中直接写死使用name,所以就异常了。
所以这里我们获取注解对象中全部的方法,因为注解中的方法都是没有参数的,所以获取全部的方法,过滤掉有参数的方法。
==还有一点,因为获取到的注解对象是代理对象,java中对象都继承了Object,那么就同时继承了Object的公共方法,同时还有annotationType方法。==这个方法是调试的时候发现的,可能和jdk代理有关吧。
然后我们将注解中的方法名,也就是注解参数名也输出:
再次执行
这种方式,对于原来的name也是支持的:
=前面的就是注解的元素的名字,= 后面就是注解元素的值。
(吐槽一句:高亮显示=,结果因为高亮的markdown的表示是= = ,结果直接显示5个 =?)
同样因为StudyParam注解没有被Inherited修饰,所以Man中不会继承有这个注解。
变量
因为放在变量上面的注解,在类编译的过程中,局部变量就会被编译器处理,所以,局部变量的注解,即使设置它的生命周期是RUNTIME或者是CLASS,注解实际生效的生命周期还是SOURCE。
首先我们找到class文件,然后进行反编译查看:
不要尝试使用javac编译单独的People类,会出现注解类找不到的异常。
然后使用javap -V 反编译查看:
我们的局部变量在编译时,就被处理了。也就是说,在编译时,已经没有了StudyVar注解了。
局部变量被放到了局部变量表中:
至此,全部完成。
最后附上全部解析源码:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package com.study.studyannotation;
import com.study.studyannotation.anno.*;
import com.study.studyannotation.beans.Man;
import com.study.studyannotation.beans.People;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
* @author
* @Date 2020/8/14
*/
public class Main {
public static void main(String[] args) {
People people = new People("xiaomeiS");
Man man = new Man("hah");
// getDeclaredAnnotations:获取直接标注在指定对象上面的注解
// getAnnotations:获取指定对象上面的注解(可以获取继承的注解)
parseAnno(people);
parseAnno(man);
System.out.println();
parseClass(people);
parseClass(man);
System.out.println();
parseCons(people);
parseCons(man);
System.out.println();
parseField(people);
parseField(man);
System.out.println();
parseMethod(people);
parseMethod(man);
System.out.println();
parseParam(people);
parseParam(man);
}
/**
* 解析组合注解
*
* @param object 普通对象
*/
private static void parseAnno(Object object) {
// 获取普通的所有的注解
Arrays.stream(object.getClass().getAnnotations()).filter(x -> x != null).forEach(x -> {
// 获取注解的注解对象
// 注意,这里不能使用反射。因为注解本质上是接口,所以,实际使用中,都是代理对象,当你使用反射获取的时候,获取得到的代理对象,这个代理对象没有注解信息了
// 使用annotationType方法,获取的是带有注解信息的注解对象(代理对象)
// 将含有注解信息的代理对象进行解析
parse(x.annotationType(), StudyAnno.class, object.getClass());
});
}
/**
* 解析类
*
* @param object 普通对象
*/
private static void parseClass(Object object) {
// 获取普通对象的class对象
parse(object.getClass(), StudyClass.class, object.getClass());
}
/**
* 解析参数
*
* @param object
*/
private static void parseParam(Object object) {
// 获取普通对象的构造器、方法(全部可以有参数的方法)
Constructor<?>[] declaredConstructors = object.getClass().getDeclaredConstructors();
Method[] declaredMethods = object.getClass().getDeclaredMethods();
Arrays.stream(declaredConstructors).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
Arrays.stream(declaredMethods).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
}
/**
* 解析构造
*
* @param object
*/
private static void parseCons(Object object) {
// 获取普通对象的Constructor对象
Arrays.stream(object.getClass().getDeclaredConstructors()).filter(x -> x != null).forEach(x -> parse(x, StudyCons.class, object.getClass()));
}
/**
* 解析属性
*
* @param object
*/
private static void parseField(Object object) {
// 获取对象的全部属性对象
Arrays.stream(object.getClass().getDeclaredFields()).filter(x -> x != null).forEach(x -> parse(x, StudyField.class, object.getClass()));
}
/**
* 解析方法
*
* @param object
*/
private static void parseMethod(Object object) {
// 获取对象的全部方法
Arrays.stream(object.getClass().getDeclaredMethods()).filter(x -> x != null).forEach(x -> parse(x, StudyMethod.class, object.getClass()));
}
/**
* 记录日志
*
* @param annotatedElement
* @param annClass
* @param objClass
*/
private static void parse(AnnotatedElement annotatedElement, Class annClass, Class objClass) {
System.out.println("---------- parse\t" + annClass.getSimpleName() + ":\t" + objClass.getSimpleName() + "----------");
parse(annotatedElement, annClass);
}
/**
* 注解解析
*
* @param annotatedElement 需要处理的AnnotatedElement对象
* @param annClazz 需要处理的注解类型
*/
private static void parse(AnnotatedElement annotatedElement, Class annClazz) {
// 获取使用注解的对象上面的注解 (StudyClass对象)
Annotation declaredAnnotation = annotatedElement.getAnnotation(annClazz);
// 判断是否存在继承的注解,如果有继承的注解,那么获取继承的注解对象,否则是null
Annotation annotation = annotatedElement.isAnnotationPresent(annClazz) ? annotatedElement.getAnnotation(annClazz) : null;
// 将本身的注解和继承的注解的对象,进行流化,然后去重,去除null,最后遍历处理
Arrays.asList(declaredAnnotation, annotation).stream().distinct().filter(x -> x != null).forEach(x -> {
// 获取注解对象中的注解元素 (name方法)
// 注解中的方法都是没有参数的
// 同时因为代理对象的缘故,还需要去掉Object的2个无参公共方法:toString,hashCode,以及class的annotationType方法
List<String> methodList = Arrays.asList("toString", "hashCode", "annotationType");
Arrays.stream(x.getClass().getDeclaredMethods()).filter(y -> y.getParameterCount() == 0)
.filter(y -> !methodList.contains(y.getName()))
.forEach(y -> {
// 执行注解元素,获取注解元素值 ("people")
try {
Object nameValue = y.invoke(x, null);
System.out.println(annClazz.getSimpleName() + " , " + y.getName() + " = " + nameValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 获取到注解元素值之后做的操作
});
});
}
}
执行结果






























