1. 概念及作用

  • 1.1 概念
    • 注解即元数据,就是源代码的元数据
    • 注解在代码中添加信息提供了一种形式化的方法,可以在后续中更方便的使用这些数据
    • Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。
  • 1.2 作用
    • a. 生成文档
    • b. 跟踪代码依赖性,实现替代配置文件功能,减少配置。如Spring中的一些注解
    • c. 在编译时进行格式检查,如@Override等
  • 1.3 意义
    • 注解之前,XML被广泛的应用于描述元数据,XML的维护越来越糟糕
    • 在需要紧耦合的地方,比XML该容易维护,阅读更方便
    • 在需要比较多参数设置时,使用xml更方便,而在将某个方法声明为服务时这种紧耦合的情况下,比较适合注解
    • XML是松耦合的,注解是紧耦合的
    • 对于“XML VS 注解” ,可以google了解一下
    • 对于XML和注解的使用,要具体问题具体分析
    • Java的annotation没有行为,只能有数据,实际上就是一组键值对而已。通过解析(parse)Class文件就能把一个annotation需要的键值对都找出来

2. JDK注解

  • 2.1 Override: 保证编译时 要重写方法的正确性
  • 2.2 Deprected: 提示该方法已经过时
  • 2.3 SuppressWarnings: 关闭特定警告信息,参数如下:
    • all: to suppress all warnings
    • boxing: to suppress warnings relative to boxing/unboxing operations
    • cast: to suppress warnings relative to cast operations
    • dep-ann: to suppress warnings relative to deprecated annotation
    • deprecation: to suppress warnings relative to deprecation
    • fallthrough: to suppress warnings relative to missing breaks in switch statements
    • finally: to suppress warnings relative to finally block that don’t return
    • hiding: to suppress warnings relative to locals that hide variable
    • incomplete-switch: to suppress warnings relative to missing entries in a switch statement (enum case)
    • nls: to suppress warnings relative to non-nls string literals
    • null: to suppress warnings relative to null analysis
    • rawtypes: to suppress warnings relative to un-specific types when using generics on class params
    • restriction: to suppress warnings relative to usage of discouraged or forbidden references
    • serial: to suppress warnings relative to missing serialVersionUID field for a serializable class
    • static-access: to suppress warnings relative to incorrect static access
    • synthetic-access: to suppress warnings relative to unoptimized access from inner classes
    • unchecked: to suppress warnings relative to unchecked operations
    • unqualified-field-access: to suppress warnings relative to field access unqualified
    • unused: to suppress warnings relative to unused code

3. 元注解

  • 3.1 定义
    • 负责注解其他注解
  • 3.2 四种元注解
    • @Documented
    • @Target
    • @Retention
    • @Inherited
  • 3.3 @Documented
    • 用于描述其他类型的annotation应该被作为被标注的程序成员的公共API
    • 可以用于javadoc 此类的工具文档化
    • Document是一个标记注解,没有成员
  • 3.4 @Target
    • 说明Annotation所修饰的对象范围,即描述注解的使用范围
    • Annotation可被用于packages,types(类,接口,枚举,Annotation类型),类型成员(方法,构造方法,成员变量,枚举值),方法参数和本地变量(如循环变量,catch参数)
    • 在Annotation类型的声明中使用了target可更加明晰其修饰目标
    • 取值有:

|类型|用途|
|:—-|:—-|
|CONSTRUCTOR|用于描述构造器|
|FIELD|用于描述域|
|LOCAL_VARIABLE|用于描述局部变量|
|METHOD|用于描述方法|
|PACKAGE|用于描述包|
|PARAMETER|用于描述参数|
|TYPE|用于描述类、接口(包括注解类型) 或enum声明|

  • 3.5 @Retention
    • 定义了Annotation被保留的时间长短
    • 表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即被描述的注解在什么范围内有效)
    • Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。具体实例:
类型 用途 说明
SOURCE 在源文件中有效(即源文件保留) 仅出现在源代码中,而被编译器丢弃
CLASS 在class文件中有效(即class保留) 被编译在class文件中
RUNTIME 在运行时有效(即运行时保留) 编译在class文件中
  • 3.6 @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类型被发现,或者到达类继承结构的顶层。

4. 自定义注解

  • 4.1 格式
1
2
3
public @interface 注解名{
定义体
}
  • 4.2 注解参数的可支持数据类型:
    • 所有基本数据类型(int,float,double,boolean,byte,char,long,short)
    • String 类型
    • Class类型
    • enum类型
    • Annotation类型
    • 以上所有类型的数组
  • 4.3 要求
    • 修饰符只能是public 或默认(default)
    • 参数成员只能用基本类型byte,short,int,long,float,double,boolean八种基本类型和String,Enum,Class,annotations及这些类型的数组
    • 如果只有一个参数成员,最好将名称设为”value”
    • 注解元素必须有确定的值,可以在注解中定义默认值,也可以使用注解时指定,非基本类型的值不可为null,常使用空字符串或0作默认值
    • 在表现一个元素存在或缺失的状态时,定义一下特殊值来表示,如空字符串或负值
  • 4.4 示例:
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
package 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;

/**
* test注解
* @author zlw
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
/**
* id
* @return
*/
public int id() default -1;

/**
* name
* @return
*/
public String name() default "";

}

5. 注解处理器

  • 5.1 释义
    • 使用注解的过程,重要的是创建注解处理器
    • Java SE5 扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器
  • 5.2 注解处理器类库
    • java.lang.reflect.AnnotatedElement
    • Java使用Annotation接口来代表程序元素面前的注解,该接口是所有Annotation类型的接口。
    • java.lang.annotation.Annotation 是所有Annotation类型的父接口
    • java.lang.reflect.AnnotatedElement 可以接受注解的程序元素,该接口主要有Class(类定义),Constructor(构造器定义),Filed(类的成员变量定义),Method(类的方法定义),Package(类的包定义),
    • java.lang.reflect包所有提供的反射API扩充了读取运行时Annotation信息的能力,当一个Annotation类型被定义为运行时Annotation后,该注解才能运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取
    • AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
        方法1 T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
        方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
        方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
        方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响
  • 5.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
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
/***********注解声明***************/

/**
* 水果名称注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}

/**
* 水果颜色注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};

/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.GREEN;

}

/**
* 水果供应者注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
* @return
*/
public int id() default -1;

/**
* 供应商名称
* @return
*/
public String name() default "";

/**
* 供应商地址
* @return
*/
public String address() default "";
}

/***********注解使用***************/

public class Apple {

@FruitName("Apple")
private String appleName;

@FruitColor(fruitColor=Color.RED)
private String appleColor;

@FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider;

public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}

public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}

public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}

public void displayName(){
System.out.println("水果的名字是:苹果");
}
}

/***********注解处理器***************/

public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){

String strFruitName=" 水果名称:";
String strFruitColor=" 水果颜色:";
String strFruitProvicer="供应商信息:";

Field[] fields = clazz.getDeclaredFields();

for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}

/***********输出结果***************/
public class FruitRun {

/**
* @param args
*/
public static void main(String[] args) {

FruitInfoUtil.getFruitInfo(Apple.class);

}

}

====================================
水果名称:Apple
水果颜色:RED
供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦

6. Java 8 中注解新特性

  • 6.1 @Repeatable 元注解,表示被修饰的注解可以用在同一个声明式或者类型加上多个相同的注解(包含不同的属性值)
  • 6.2 @Native 元注解,本地方法
  • 6.3 java8 中Annotation 可以被用在任何使用 Type 的地方
  • 6.4 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //初始化对象时
    String myString = new @NotNull String();
    //对象类型转化时
    myString = (@NonNull String) str;
    //使用 implements 表达式时
    class MyList<T> implements @ReadOnly List<@ReadOnly T>{
    ...
    }
    //使用 throws 表达式时
    public void validateValues() throws @Critical ValidationFailedException{
    ...
    }

7. 注解的优缺点及与XML的比较

  • 7.1 优:
    • 方便,简洁,配置信息和 Java 代码放在一起,有助于增强程序的内聚性
    • 若要对配置项进行修改,不得不修改 Java 文件,重新编译打包应用
  • 7.2 缺:
    • 分散到各个class文件中,维护性较差
    • 配置项编码在 Java 文件中,可扩展性差
  • 7.3 与XML比较:
    • 简洁
    • 没有XML配置更强大
    • 不便于修改,不便于统一管理

8. 参考引用

  1. 深入理解Java:注解(Annotation)–注解处理器
  2. 深入理解Java:注解(Annotation)自定义注解入门
  3. 深入理解Java:注解(Annotation)基本概念
  4. Java annotation的实例是什么类的?
  5. java中的注释
  6. JAVA 注解的几大作用及使用方法详解
  7. Java中的注解是如何工作的?
  8. Java中使用注解和使用配置文件各有什么优缺点
  9. JAVA注解