彻底搞懂Java注解Annotation

注解的定义

  • 定义

注解:提供一种为程序元素设置元数据的方法。

基本原则:注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。

注解(也被成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。 ———摘自《Thinking in Java》

简单来说注解的作用就是将我们的需要的数据储存起来,在以后的某一个时刻(可能是编译时,也可能是运行时)去调用它。
  • 元数据

元数据:就是关于数据的数据

元数据的作用:

1
2
3
1. 编写文档:通过代码里标识的元数据生成文档;
2. 代码分析:通过代码里标识的元数据对代码进行分析;
3. 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查。

为什么要学习注解?

  • 能够读懂别人使用注解实现的开源库
  • 让编程变得更加简洁

注解的功能

  • 作为特定的标记,用于告诉编译器一些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,作为额外信息的载体,如得到注解信息

注解的分类

  • 标准注解:Override、Deprecated、SuppressWarnings
    标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning;
  • 元注解:@Retention、@Target、@Inherited、@Documented
    元 Annotation 是指用来定义 Annotation 的 Annotation,在自定义注解部分将会详细介绍;
  • 自定义注解
    自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
    这里是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation,后面在自定义 Annotation 时会具体介绍。

自定义注解

因为自定义注解,是使用元注解来实现的,所以我们先详细的了解一下元注解,然后再通过一个例子来讲解如何实现和使用自定义注解。

元注解

  • @Target

说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:

  1. CONSTRUCTOR:用于描述构造器
  2. FIELD:用于描述域
  3. LOCAL_VARIABLE:用于描述局部变量
  4. METHOD:用于描述方法
  5. PACKAGE:用于描述包
  6. PARAMETER:用于描述参数
  7. TYPE:用于描述类、接口(包括注解类型) 或enum声明

@Target的源码

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
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
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
  • @Retention

定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:

  1. SOURCE:在源文件中有效(即源文件保留)
  2. CLASS:在class文件中有效(即class保留)
  3. RUNTIME:在运行时有效(即运行时保留)

@Retention的源码

1
2
3
4
5
6
7
8
9
10
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
  • @Documented

用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

@Documented的源码

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
  • @Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

@Inherited的源码

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

实现和使用自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

  • 定义注解格式

    public @interface 注解名 {定义体}

  • 注解参数的可支持数据类型

  1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
  2. String类型
  3. Class类型
  4. enum类型
  5. Annotation类型
  6. 以上所有类型的数组

Annotation类型里面的参数该怎么设定:

  1. 首先,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;   
  2. 其次,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;  
  3. 最后,如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.例:下面的例子FruitName注解就只有一个参数成员。

下面通过源码来展示自定义注解:

首先,我们自定义一个注解:AuthorAnnotation 来标记作者的信息

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
package com.akathink.annotation;
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;
/**
* 自定义注解:作者信息注解
*
* @author LiuQingJie
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthorAnnotation {
// 名字
String name();
// 年龄
int age() default 19;
// 性别
String gender() default "男";
}

其次,再定义一个注解:BookAnnotation 来标记故事书籍的内容信息

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
package com.akathink.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* 自定义注解:树的信息注解
*
* @author LiuQingJie
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface BookAnnotation {
// 书名
String bookName();
// 女主人公
String heroine();
// 书的简介
String briefOfBook();
// 书的销量
int sales() default 10000;
}

最后,我们定义一种类型的书:LoveStoryBook,类注解标记的是《泡沫之夏》,为了区分,方法注解标记的是《微微一笑很倾城》

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
package com.akathink.entity;
import com.akathink.annotation.AuthorAnnotation;
import com.akathink.annotation.BookAnnotation;
/**
*
* 爱的故事
*
* @author LiuQingJie
*
*/
@BookAnnotation(bookName = "泡沫之夏",
heroine = "尹夏沫",
briefOfBook = "淡漠安静的性格使得欧辰与洛熙为她神魂颠倒。她是冷漠与激情混合体,"
+ "只是在经过种种遭遇,饱尝了人间冷暖后。她用冷漠的外衣将自己包裹住,不愿"
+ "让人看见自己真实的性格,最后与欧辰携手走进婚姻的殿堂。",
sales = 1000000)
public class LoveStoryBook {
@AuthorAnnotation(name = "明晓溪", age = 28, gender = "女")
private String user;
@BookAnnotation(bookName = "微微一笑很倾城",
heroine = "顾漫",
briefOfBook = "《微微一笑很倾城》一书中出现的游戏叫梦游江湖,女主角PK榜上排名第六的芦"
+ "苇微微是一身劲装背着大刀的红衣女侠,男主角第一高手一笑奈何则是一身白衣纤尘不"
+ "染衣袂飘飘潇洒出尘的琴师。两人在经典地点长安城朱雀桥相遇并举行婚礼。书中游戏"
+ "既有一般游戏常见的跑环、摆摊,也有现阶段游戏不存在的宝宝结婚、抢亲等功能。",
sales = 800000)
public void getBookInfo(){
}
}

注解解析

上面已经将要注解的类和两个注解类实现了,下面定义一个类:ParseAnnotation,来解析我们自定义的注解

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
package com.akathink.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.akathink.entity.LoveStoryBook;
public class ParseAnnotation {
/**
*
* 解析类注解
* LoveStoryBook
* @throws ClassNotFoundException
*/
public static void parseTypeAnnotation() throws ClassNotFoundException{
@SuppressWarnings("rawtypes")
Class clazz = Class.forName("com.akathink.entity.LoveStoryBook");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
BookAnnotation bookAnnotation = (BookAnnotation) annotation;
System.out.println("书名:" + bookAnnotation.bookName() + "\n" +
"女主人公:" + bookAnnotation.heroine() + "\n" +
"书的简介:" + bookAnnotation.briefOfBook() + "\n"+
"书的销量:" + bookAnnotation .sales() + "\n");
}
}
/**
* 解析方法注解
* @throws ClassNotFoundException
*/
public static void parseMethodAnnotation() throws ClassNotFoundException{
Method[] methods = LoveStoryBook.class.getDeclaredMethods();
for (Method method : methods) {
/*
* 判断方法中是否有指定注解类型的注解
*/
boolean hasAnnotation = method.isAnnotationPresent(BookAnnotation.class);
if(hasAnnotation){
BookAnnotation bookAnnotation = (BookAnnotation) method.getAnnotation(BookAnnotation.class);
System.out.println("书名:" + bookAnnotation.bookName() + "\n" +
"女主人公:" + bookAnnotation.heroine() + "\n" +
"书的简介:" + bookAnnotation.briefOfBook() + "\n"+
"书的销量:" + bookAnnotation .sales() + "\n");
}
}
}
/**
* 解析域注解
* @throws ClassNotFoundException
*/
public static void parseFieldAnnotation() throws ClassNotFoundException{
Field[] fields = LoveStoryBook.class.getDeclaredFields();
for (Field field : fields) {
boolean hasAnnotation = field.isAnnotationPresent(AuthorAnnotation.class);
if(hasAnnotation){
AuthorAnnotation authorAnnotation = field.getAnnotation(AuthorAnnotation.class);
System.out.println("作者:" +authorAnnotation.name() + "\n" +
"年龄:" + authorAnnotation.age() + "\n" +
"性别:" + authorAnnotation.gender() + "\n");
}
}
}
}

最后的最后就是验证我们自定义的注解是否正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.akathink.annotation;
public class AnnotationDemo {
public static void main(String[] args) throws ClassNotFoundException {
//解析域的注解
System.out.println("下面是解析域的注解信息:\n\n");
ParseAnnotation.parseFieldAnnotation();
//解析方法的注解
System.out.println("下面是解析方法的注解信息:\n\n");
ParseAnnotation.parseMethodAnnotation();
//解析类的注解
System.out.println("下面是解析类的注解信息:\n\n");
ParseAnnotation.parseTypeAnnotation();
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
下面是解析域的注解信息:
作者:明晓溪
年龄:28
性别:女
下面是解析方法的注解信息:
书名:微微一笑很倾城
女主人公:顾漫
书的简介:《微微一笑很倾城》一书中出现的游戏叫梦游江湖,女主角PK榜上排名第六的芦苇微微是一身劲装背着大刀的红衣女侠,男主角第一高手一笑奈何则是一身白衣纤尘不染衣袂飘飘潇洒出尘的琴师。两人在经典地点长安城朱雀桥相遇并举行婚礼。书中游戏既有一般游戏常见的跑环、摆摊,也有现阶段游戏不存在的宝宝结婚、抢亲等功能。
书的销量:800000
下面是解析类的注解信息:
书名:泡沫之夏
女主人公:尹夏沫
书的简介:淡漠安静的性格使得欧辰与洛熙为她神魂颠倒。她是冷漠与激情混合体,只是在经过种种遭遇,饱尝了人间冷暖后。她用冷漠的外衣将自己包裹住,不愿让人看见自己真实的性格,最后与欧辰携手走进婚姻的殿堂。
书的销量:1000000

注意

  • 对局部变量的注解只能在源码级别上进行处理,class文件并不描述局部变量。因此,所有的局部变量注解在编译完一个类的时候就会被遗弃掉。同样的,对包的注解不能在源码级别之外存在。
  • 一条没有@Target限制的注解可以应用于任何项上。
  • @Inherited元注解只能应用于对类的注解

参考资料

《Java核心技术卷二》
《Thinking in Java》
http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html
http://a.codekk.com/detail/Android/Trinea/公共技术点之%20Java%20注解%20Annotation
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html