Java Annotation对应的Retention有3种,在RetentionPolicy中定义,有3种:
- SOURCE. 注解保留在源代码中,但是编译的时候会被编译器所丢弃。比如@Override, @SuppressWarnings
- CLASS. 这是默认的policy。注解会被保留在class文件中,但是在运行时期间就不会识别这个注解。
- RUNTIME. 注解会被保留在class文件中,同时运行时期间也会被识别。所以可以使用反射机制获取注解信息。比如@Deprecated
RUNTIME
大部分情况下,我们都是使用RUNTIME这个Policy。
下面就是一个RUNTIME Annotation的例子。
先定义Annotation:
1
2
3
4
5
6
|
@Retention
(RetentionPolicy.RUNTIME)
@Target
(ElementType.TYPE)
public
@interface
MyClassRuntimeAnno {
String name();
int
level()
default
1
;
}
|
然后在CLASS前面使用这个Annotation。
1
2
3
|
@MyClassRuntimeAnno
(name =
"simple"
, level =
10
)
public
class
SimpleObj {
}
|
最后写一个testcase通过反射可以获取这个类的Annotation进行后续操作。
1
2
3
4
5
6
7
8
|
@Test
public
void
testGetAnnotation() {
Annotation[] annotations = SimpleObj.
class
.getAnnotations();
System.out.println(Arrays.toString(annotations));
MyClassRuntimeAnno myClassAnno = SimpleObj.
class
.getAnnotation(MyClassRuntimeAnno.
class
);
System.out.println(myClassAnno.name() +
", "
+ myClassAnno.level());
System.out.println(myClassAnno == annotations[
0
]);
}
|
SOURCE
SOURCE这个policy表示注解保留在源代码中,但是编译的时候会被编译器所丢弃。 由于在编译的过程中这个注解还被保留着,所以在编译过程中可以针对这个policy进行一些操作。比如在自动生成java代码的场景下使用。最常见的就是lombok的使用了,可以自动生成field的get和set方法以及toString方法,构造器等;消除了冗长的java代码。
SOURCE这个policy可以使用jdk中的javax.annotation.processing.*包中的processor处理器进行注解的处理过程。
以1个编译过程中会打印类中的方法的例子来说明SOUCRE这个policy的作用:
首先定义一个Printer注解:
1
2
3
4
|
@Retention
(RetentionPolicy.SOURCE)
@Target
(ElementType.METHOD)
public
@interface
Printer {
}
|
然后一个类的方法使用这个注解:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
SimpleObject {
@Printer
public
void
methodA() {
}
public
void
methodB() {
}
}
|
创建对应的Processor:
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
|
@SupportedAnnotationTypes
({
"me.format.annotaion.Printer"
})
public
class
PrintProcessor
extends
AbstractProcessor {
@Override
public
boolean
process(Set<?
extends
TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE,
"start to use PrintProcessor .."
);
Set<?
extends
Element> rootElements = roundEnv.getRootElements();
messager.printMessage(Diagnostic.Kind.NOTE,
"root classes: "
);
for
(Element root : rootElements) {
messager.printMessage(Diagnostic.Kind.NOTE,
">> "
+ root.toString());
}
messager.printMessage(Diagnostic.Kind.NOTE,
"annotation: "
);
for
(TypeElement te : annotations) {
messager.printMessage(Diagnostic.Kind.NOTE,
">>> "
+ te.toString());
Set<?
extends
Element> elements = roundEnv.getElementsAnnotatedWith(te);
for
(Element ele : elements) {
messager.printMessage(Diagnostic.Kind.NOTE,
">>>> "
+ ele.toString());
}
}
return
true
;
}
@Override
public
SourceVersion getSupportedSourceVersion() {
return
SourceVersion.latestSupported();
}
}
|
然后先使用javac编译Printer和PrintProcessor:
1
|
javac -d classes src/main/java/me/format/annotation/Printer.java src/main/java/me/format/annotation/PrintProcessor.java
|
最后再使用javac中的processor参数处理:
1
|
javac -cp classes -processor me.format.annotation.PrintProcessor -d classes src/main/java/me/format/annotation/SimpleObject.java
|
控制台打印出:
1
2
3
4
5
6
|
注: start to use PrintProcessor ..
注: root classes:
注: >> hello.annotation.SimpleObject
注: annotation:
注: >>> hello.annotation.Printer
注: >>>> methodA()
|
CLASS
CLASS和RUNTIME的唯一区别是RUNTIME在运行时期间注解是存在的,而CLASS则不存在。
我们通过asm来获取class文件里的annotation。
首先定义注解:
policy为CLASS的注解。
1
2
3
4
5
6
7
|
@Retention
(RetentionPolicy.CLASS)
@Target
(ElementType.TYPE)
public
@interface
Meta {
String name();
}
|
policy为RUNTIME的注解。
1
2
3
4
5
6
7
|
@Retention
(RetentionPolicy.RUNTIME)
@Target
(ElementType.TYPE)
public
@interface
Header {
int
code();
}
|
使用注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Meta
(name =
"obj"
)
@Header
(code =
200
)
public
class
AnnotationObject {
private
String val;
public
String getVal() {
return
val;
}
public
void
setVal(String val) {
this
.val = val;
}
}
|
编译这3个java文件得到字节码文件AnnotationObject.class:
1
|
javac -d classes src/main/java/me/format/annotaion/AnnotationObject.java src/main/java/me/format/annotation/Meta.java src/main/java/me/format/annotation/Header.java
|
使用asm获取字节码文件中的注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
ClassNode classNode =
new
ClassNode();
ClassReader cr =
new
ClassReader(
new
FileInputStream(
"classes/me/format/annotation/AnnotationObject.class"
));
cr.accept(classNode,
0
);
System.out.println(
"Class Name: "
+ classNode.name);
System.out.println(
"Source File: "
+ classNode.sourceFile);
System.out.println(
"invisible: "
);
AnnotationNode anNode =
null
;
for
(Object annotation : classNode.invisibleAnnotations) {
anNode = (AnnotationNode) annotation;
System.out.println(
"Annotation Descriptor : "
+ anNode.desc);
System.out.println(
"Annotation attribute pairs : "
+ anNode.values);
}
System.out.println(
"visible: "
);
for
(Object annotation : classNode.visibleAnnotations) {
anNode = (AnnotationNode) annotation;
System.out.println(
"Annotation Descriptor : "
+ anNode.desc);
System.out.println(
"Annotation attribute pairs : "
+ anNode.values);
}
|
打印出:
1
2
3
4
5
6
7
8
|
Class Name: me/format/annotation/AnnotationObject
Source File: AnnotationObject.java
invisible:
Annotation Descriptor : Lme/format/annotation/Meta;
Annotation attribute pairs : [name, obj]
visible:
Annotation Descriptor : Lme/format/annotation/Header;
Annotation attribute pairs : [code,
200
]
|
其中policy为CLASS的注解编译完后不可见,而policy为RUNTIME的注解编译后可见。
同样,我们可以使用javap查看编译后的信息:
1
|
javap -v me.format.annotation.AnnotationObject
|
会打印出注解的visible信息:
1
2
3
4
5
6
7
8
9
|
#
16
= Utf8 AnnotationObject.java
#
17
= Utf8 RuntimeVisibleAnnotations
#
18
= Utf8 Lhello/annotation/Header;
#
19
= Utf8 code
#
20
= Integer
200
#
21
= Utf8 RuntimeInvisibleAnnotations
#
22
= Utf8 Lhello/annotation/Meta;
#
23
= Utf8 name
#
24
= Utf8 obj
|