Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。 
  Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的"name=value"对中。
  注意:Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象。然后通过Annotation对象来取得注释里的元数据。需要注意本章中使用Annotation的地方,有的Annotation指的是java.lang.Annotation接口,有的指的是注释本身。
  Annotation能被用来作为程序元素(类、方法、成员变量等)设置元数据。需要指出的是:Annotation不能影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation能在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称为APT(Annotation Processing Tool)。

1 基本Annotation

  Annotation必须使用工具来处理,工具负责提取Annotation里包含的元数据,工具还会根据这些元数据增加额外的功能。在系统学习新的Annotation语法之前,先看一下Java提供的三个基本Annotation的用法:使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。
三个基本的Annotation如下

1.@Override
2.@Deprecated
3.@SuppressWarnings

限定重写父类方法:@Override
@Override就是用来指定方法覆写的,它可以强制一个子类必须要覆写父类的方法。如下程序中使用@Override指定子类Apple的info方法必须重写父类方法。

 package chapter16;

 class Fruit {
public void foo(){
System.out.println("水果的info方法....");
}
};
public class Apple extends Fruit{
//使用@Override指定下面的方法必须重写父类方法
@Override
public void foo(){
System.out.println("苹果重写水果的info方法....");
}
public static void main(String[] args){
Fruit f = new Apple();
f.foo();
}
} 输出结果:
苹果重写水果的info方法....

  编译上面程序,可能看不出程序中的@Override有何作用,因为@Override Annotation的作用是告诉编译器检查这个方法,并从父类查找是包含一个被该方法重写的方法,否则就编译出错。这个Annotation主要是帮助我们避免一些低级错误。例如我们把上面的Apple类的info方法不小心写成inf()这样的低级错误,可能会导致后期排错时的巨大障碍。但是如果你没有调用重写的方法,那就是调用了父类的方法,这样产生的结果就是另一种结果,编译会通过,但是结果是不符合预期的。

 package chapter16;

 class Fruit {
public void foo(){
System.out.println("水果的info方法....");
}
};
public class Apple extends Fruit{
//使用@Override指定下面的方法必须重写父类方法
@Override
public void fool(){
System.out.println("苹果重写水果的info方法....");
}
public static void main(String[] args){
Fruit f = new Apple();
f.foo();
}
} 预期结果:水果的info方法....

标示已过时:@Deprecated
@Deprecated用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给警告。如下程序指定Apple类中的info方法已过时,其他程序中使用Apple类的info方法时编译器将会给出警告。

那些是被@Deprecated注解标记的方法或者属性或类等,意思是“已过时”。如果你是新写代码,那么不推荐你这么做,有更好的替代方案,如果是老系统,那么告知你你这个方法已过时,不过JDK还将继续对他支持。被注解掉@Deprecated,表示是方法过时有新的方法替代,在jdk文档中可以找到相对应的新方法。

可以看到上面被deprecated的方法被划上了横线

上面应用程序使用了一个被deprecated的方法,表名该方法以及过时,在新版的jdk有替代的更好的方法。

抑制编译器警告:@SuppressWarnings
@SuppressWarnings指示被Annotation标识的程序元素(以及在该程序元素中的所有子元素)取消显示指示的编译器警告。@SuppressWarnings会一直作用于该程序元素的所有子元素,例如使用@SuppressWarnings标识一个类来取消显示某个编译器警告,同时又标识该类里某个方法取消显示另一个编译器警告,那么将在此方法同时取消这两个编译器警告。
通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用@SuppressWarningsAnnotation,下面程序取消了没有使用泛型的编译器警告。

 package chapter16;

 import java.util.*;

 @SuppressWarnings(value="unchecked");

 public class SuppressWarningsTest {
public static void main(String[] args){
List<String> l = new ArrayList();
}
}

程序使用@SuppressWarnings来关闭SuppressWarningsTest类里的所有编译器警告,编译上面程序时将不会砍掉任何编译器警告。
注意:使用@SuppressWarnings Annotation来关闭编译器警告时,一定需要在括号里使用name=value对来为该Annotation的成员变量设置值。

2 自定义Annotation

  上面介绍的3个Annotation是java.lang包下的三个标准Annotation,下面介绍自定义的Annotation,并利用Annotation完成一些实际功能。

2.1 定义Annotation

  定义新的Annotation类型使用@interface关键字(在原有的interface关键字前增加@符号),它用于定义新的Annotation类型。定义一个新的Annotation类型与定义一个接口非常像。如下即可定义一个简单的Annotation

 //定义一个简单的Annotation类型
public @interface Test{ }

  定义了该Annotation之后,就可以在程序任何地方来使用该Annotation,使用Annotation时的语法非常类似于public、final这样的修饰符。通常可用于修饰程序中的类、方法、变量、接口等定义,通常我们会把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为其成员变量指定值,因而Annotation的长度可能较长,所以通常把Annotation另放一行。如下所示。

//使用@Test修饰类定义
@Test
public class MyClass{
.....
}

  默认情况下,Annotation可用于修饰任何程序元素,包括类、接口、方法等,如下程序使用@TestAnnotation来修饰方法。

 public class MyClass{
//使用@Test Annotation修饰方法
@Test
public void info(){
.....
}
.....
}

  Annotation不仅可以是这种简单的Annotation,Annotation还可以带成员变量,Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。如下代码可以定义一个有成员变量的Annotation:

 package chapter10;

 public @interface MyTag {
//定义了两个成员变量的Annotation
//Annotation的成员变量定义方式类似于传统的方法
String name();
int age();
}

  上面的注释和接口定义是很像的,注释使用@interface来定义,而接口用interface定义。但是对于变量的定义是有点区别的,上面定义的是变量,但不是方法。一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值,如下所示:

 public class Test{
//使用带成员变量的Annotation时,需要为成员变量赋值
@MyTag(name="xx", age=6)
public void info(){
.....
}
.....
}

  我们还可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字。如下代码定义了MyTag Annotation,该Annotation里包含了两个成员变量:name和age,这两个成员变量使用了default指定了默认值。

 package chapter10;

 public @interface MyTag {
//定义了两个成员变量的Annotation
//Annotation的成员变量定义方式类似于传统的方法
String name() default "yeeku";
int age() default 3;
}
如果为Annotation的成员变量指定了默认值,使用该Annotation则可以不为这些成员变量指定值,而是直接使用默认值,如下代码所示:
public class Test{
//使用带成员变量的Annotation
//因为它的成员变量有默认值,所以可以无须为成员变量指定值
@MyTag
public void info(){
...
}
....
}

  当然我们也可以在使用MyTagAnnotation时为成员变量指定值,如果为MyTag的成员变量指定了值,则默认值不起作用。根据我们介绍的Annotation是否可以包含成员变量,可以把Annotation分成两类:
1.标记Annotation:一个没有成员定义的Annotation类型被称为标记。这种Annotation仅使用自身的存在与否来为我们提供信息。如前面介绍的@Override、@Test等Annotation
2.元数据Annotation,那些包含成员变量的Annotation。因为它们可接受更多元数据,所以也被称为元数据Annotation

2.2 提取Annotation的信息

  Java使用Annotation接口来代表程序元素前面的注释,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接收注释的程序元素,该接口主要由如下几个实现类:
Class:类定义
Constructor:构造器定义
Field:类的成员变量定义
Method:类的方法定义
Package:类的包定义
  java.lang.reflect包下主要包含了一些实现反射功能工具类,实际上,java.lang.reflect包所提供的反射APL扩充了读取运行时Annotation的能力。当一个Annotation类型被定义为运行时Annotation后,该注释才是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
  AnnotatedElement接口是所有程序元素(如Class、Method、Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象如Class、Method、Constructor之后,程序就可以调用该对象的如下三个方法来访问Annotation信息:

1.getAnnotation(Class<T> annotationClass):返回该程序元素上存在的、指定类型的注释,如果该类型的注释不存在,则返回null。
2.Annotation[] getAnnotation():返回该程序元素上存在的所有注释
3.boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注释,存在则返回true,否则返回false

  为了获取程序中程序元素如Class、Method等,必须使用反射知识。

下面程序片段获取Test类的info方法里的所有注释,并将这些注释打印出来:

 //获取Test类的info方法的所有注释
Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations();
//遍历所有注释
for(Annotation an : aArray){
System.out.println(an);
}
如果我们需要获取某个注释里的元数据,则可以将注释强制类型转换成所需的注释类型,然后通过注释对象的抽象方法来访问这些元数据,如下所示:
//获取tt对象的info方法所包含的所有注释
Annotation[] annotation = tt.getClass().getMethod("info").getAnnotation();
//遍历每个注释对象
for(Annotation tag:annotation){
//如果tag注释是MyTag1类型
if(tag instanceof MyTag1){
System.out.println("Tag is: " + tag);
//将tag强制类型转换为MyTag1,
//并调用tag对象的method1和method2两个方法
System.out.println("tag.name(): " + ((MyTag1)tag).method1());
System.out.println("tag.age(): " + ((MyTag1)tag).method2());
}
//如果tag注释是MyTag2类型
if(tag instanceof MyTag2){
System.out.println("Tag is: " + tag);
//将tag强制类型转换为MyTag2,
//并调用tag对象的method1和method2两个方法
System.out.println("tag.name(): " + ((MyTag2)tag).method1());
System.out.println("tag.age(): " + ((MyTag2)tag).method2());
}
}

使用Annotation的例子
  下面介绍两个使用Annotation的例子,第一个Annotation Testable没有任何成员变量,仅是一个标记Annotation,它的作用是标记哪些方法是可测试的。

 package chapter10;

 import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; //使用JDK的元数据Annotation:Retention
@Retention(RetentionPolicy.RUNTIME)
//使用JDK的元数据Annotation:Target
@Target(ElementType.METHOD)
public @interface Testable{ }

  上面程序定义了一个标记TestableAnnotation,定义该Annotation时使用了@Retention和@Target两个系统元注释,其中@Retention注释指定Testable注释可以保留多久,而@Target注释指定Testable注释能修饰的目标(只能是方法)。

  上面的Testable Annotation用于标识哪些方法是可测试的,该Annotation可以作为JUnit测试框架的补充,在JUnit框架中它要求测试用例的测试方法必须以test开头。如果使用Testable注释则可把任何方法标记为可测试的。
  如下MyTest测试用例里定义了8个方法,这8个方法没有太大的区别,其中4个方法使用@Testable注释来标记这些方法是可测试的。

 public class MyTest{
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m1(){
}
public static void m2(){
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m3(){
throw new RuntimeException("Boom");
}
public static void m4(){
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m5(){
}
public static void m6(){
}
//使用@Testable标记注释指定该方法是可测试的
@Testable
public static void m7(){
throw new RuntimeException("Crash");
}
public static void m8(){
}
}

  如前所述,仅仅使用注释来标识程序元素对程序是不会有任何影响的,这也是Java注释的一条重要原则,为了让程序中这些注释起作用,我们必须为这些注释提供一个注释处理工具。
下面的注释处理工具会分析目标类,如果目标类中方法使用了@Testable注释修饰,则通过反射来运行该测试方法

 package chapter10;

 import java.lang.reflect.*;

 public class TestProcessor{
public static void process(String clazz)
throws ClassNotFoundException{
int passed = 0;
int failed = 0;
//遍历obj对象的所有方法
for (Method m : Class.forName(clazz).getMethods()){
//如果包含@Testable标记注释
if (m.isAnnotationPresent(Testable.class)){
try{
//调用m方法
m.invoke(null);
//passed加1
passed++;
}
catch (Exception ex){
System.out.printf("方法" + m + "运行失败,异常:" + ex.getCause() + "\n");
failed++;
}
}
}
//统计测试结果
System.out.printf("共运行了:" + (passed + failed)+ "个方法,其中:\n" +
"失败了:" + failed + "个,\n" +
"成功了:" + passed + "个!\n");
}
}

  TestProcessor类里只包含一个process方法,该方法可接受一个字符串参数,该方法将会分析clazz参数所代表的类,并运行该类里的、使用了@Testable注释修饰的方法。
  该程序的主类非常简单,提供主方法,使用TestProcessor来分析目标类即可。

 package chapter10;

 import java.lang.reflect.*; 

 public class RunTests{
public static void main(String[] args) throws Exception{
//处理MyTest类
TestProcessor.process("MyTest");
}
}

  通过这个运行结果可以看出,程序中的@Testable Annotation起作用了,MyTest类里以@Testable注释修饰的方法被正常测试了。
  通过上面例子可以看出,JDK的注释很简单,我们为源代码中添加一些特殊标记,这些特殊标记可通过反射获取,一旦程序访问到这些标记后,程序就可以做出相应的处理。

3 JDK的元Annotation

  JDK除了在java.lang下提供了3个基本Annotation之外,还在java.lang.annotation包下提供四个Meta Annotation(元Annotation),这四个Annotation都是用于修饰其他Annotation定义

3.1 使用@Retention

  @Retention只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。
value成员变量的值只能是如下三个:
1.RetentionPolicy.CLASS:编译器将把注释记录在class文件中。当运行Java程序时,JVM不再保留注释,这是默认值
2.RetentionPolicy.RUNTIME:编译器把注释记录在class文件中。当运行Java程序时,JVM也会保留注释,程序可以通过反射获取该注释
3.RetentionPolicy.SOURCE:编译器直接丢弃这种策略的注释。
  在前面程序中因为我们要通过反射获取注释信息,所以我们制定value属性值为RetentionPolicy.RUNTIME。使用@Retention元数据Annotation可采用如下代码为value指定值:

//定义下面的Testable Annotation的保留到运行时
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Testable{}

也可采用如下代码来为value指定值

//定义下面的Testable Annotation将被编译器直接丢弃
@Retention(RetentionPolicy.SOURCE)
public @interface Testable{}

  上面代码使用@Retention元数据Annotation时,并未直接通过value=RetentionPolicy.SOURCE的方式来为成员变量指定值,这是因为如果Annotation的成员变量名为value时,程序可以直接在Annotation后的括号里指定该成员变量的值,无需使用name=value的形式。

3.2 使用@Target

@Target也是用于修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个:
1.ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation
2.ElementType.CONSTRUCTOR:指定该策略的Annotation能修饰构造器
3.ElementType.FIELD:指定该策略的Annotation只能修饰成员变量
4.ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰具备变量
5.ElementType.METHOD:指定该策略的Annotation只能修饰方法定义
6.ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义
7.ElementType.PARAMETER:指定该策略的Annotation可以修饰参数
8.ElementType.TYPE:指定该策略的Annotation可以修饰类、接口(包括注释类型)或枚举定义
  与使用@Retention类似的是,使用@Target也可以直接在括号里指定value值,可以无须使用name=value的形式。

3.3 使用@Documented

@Documented用于指定该元Annotation修饰的Annotation类将被javadoc工具提成文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档将会包含该Annotation说明。
下面代码定义了一个Testable Annotation,使用@Documented来修饰@Testable Annotation定义,所以该Annotation将被javadoc工具所提取。

 import java.lang.annotation.*;

 @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义Testable Annotation将被javadoc工具提取
//@Documented
public @interface Testable{
}
所有使用@Testable Annotation的地方都会被javadoc工具提取到API文档中。 public class MyTest{
// 使用@Test修饰info方法
@Testable
public void info(){
System.out.println("info方法...");
}
}

3.4 使用@Inherited

  @Inherited元Annotation指定被它修饰的Annotation将具有继承性:如果某个类使用了A Annotation(定义该Annotation使用了@Inherited修饰)修饰,则其子类将自动具有A注释
  下面使用@Inherited元数据注释定义一个Inheritable Annotation,该Annotation将具有继承性。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Inheritable{ }

  上面代码表明@Inheritable Annotation具有继承性,如果某个类使用了该Annotation修饰,则该类的子类将自动具有@Inheritable Annotation
下面程序定义一个Base基类,该基类使用了@Inheritable修饰,则Base类的子类将自动具有@Inheritable Annotation

//使用@Inheritable修饰的Base类
class Base{ }
//TestInheritable类只是继承了Base类
//并未直接使用@Inheritable Annotation修饰
public class TestInheritable extends Base{
public static void main(String[] args){
//打印TestInheritable类是否具有Inheritable Annotation
System.out.println(TestInheritable.class.isAnnotationPresent(Inheritable.class))
}
}
运行结果是:true,表面TestInheritable具有@Inheritable Annotation

Java程序设计16——Annotatio注释的更多相关文章

  1. 20145325张梓靖 《Java程序设计》第16周课程总结

    20145325张梓靖 <Java程序设计>第16周课程总结 实验报告链接汇总 实验一 "Java开发环境的熟悉" 实验二 "Java面向对象程序设计&quo ...

  2. Java程序设计 第16周 课堂实践 —— 数据库4

    Java程序设计 第16周 课堂实践 -- 数据库4 课堂实践任务4 查询world数据库,查询哪个国家的平均寿命最长. 代码分析 实现查询数据库需要我们修改Message.java,MessageD ...

  3. Java程序设计 第16周 课堂实践 —— 数据库3

    Java程序设计 第16周 课堂实践 -- 数据库3 课堂实践任务3 查询world数据库,获得New Jessey州所有城市的总人口数. 代码分析 实现查询数据库需要我们修改MessageDAO.j ...

  4. Java程序设计 第16周 课堂实践

    Java程序设计 第16周 课堂实践 -- 数据库2 课堂实践任务2 查询world数据库,获得人口超过500万的所有城市的列表. 代码分析 实现查询数据库需要我们修改Message.java,Mes ...

  5. 20145219 《Java程序设计》第16周课程总结

    20145219 <Java程序设计>第16周课程总结 每周读书笔记(即学习总结)链接汇总 第0周问卷调查 第1周读书笔记 第2周读书笔记 第3周读书笔记 第4周读书笔记 第5周读书笔记 ...

  6. 20145212 《Java程序设计》第9周学习总结

    20145212 <Java程序设计>第9周学习总结 教材学习内容总结 一.JDBC架构 1.数据库驱动 这里的驱动的概念和平时听到的那种驱动的概念是一样的,比如平时购买的声卡,网卡直接插 ...

  7. 20145206《Java程序设计》实验三实验报告

    20145206<Java程序设计>实验三实验报告 实验内容 XP基础 XP核心实践 相关工具 实验步骤 (一)敏捷开发与XP 软件工程是把系统的.有序的.可量化的方法应用到软件的开发.运 ...

  8. 20145308刘昊阳 《Java程序设计》第2周学习总结

    20145308刘昊阳 <Java程序设计>第2周学习总结 教材学习内容总结 第三章 基础语法 3.1 类型.变量与运算符 类型 基本类型 整数(short/int/long) short ...

  9. Java程序设计 实验三

    北京电子科技学院(BESTI) 实     验    报     告 课程:Java程序设计   班级:1353       姓名:李海空  学号:20135329 成绩:             指 ...

随机推荐

  1. VMware 11 安装 Mac OS X10.10

    一.下载好以下软件--->http://pan.baidu.com/s/1qWDkTbe 1,VMware 11 2,unlocker203(装好VMware11后需要安装补丁unlocker才 ...

  2. oracle 脚本创建数据库的相关文章,教程,源码

    学步园推荐专题: 关于oracle 脚本创建数据库的相关文章 文章标题 文章链接 文章简介 oracle命令行创建数据库的示例脚本 http://www.xuebuyuan.com/964527.ht ...

  3. 在 Windows 下安装 Oracle 11g XE (Express Edition)

    Oracle 11g XE 是 Oracle 数据库的免费版本,支持标准版的大部分功能,11g XE 提供 Windows 和 Linux 版本. 做为免费的 Oracle 数据库版本,XE 的限制是 ...

  4. 浅析网站建设的PHP,JAVA语言分析

    编程绝对是一件不轻松的活儿.随着电子商务在国内成功的推广,京东.苏宁等大型B2C综合网上商城的成功运营,一批批以产业分类的独立网店也如火如荼发展起来.伴随着这股热潮,网店系统等相关衍生开店平台行业也出 ...

  5. 解决移动端H5海报滑动插件适应大部分手机问题 手机端高度自适应

    Html5微信端滑屏海报在各种尺寸的手机上总会有这样那样的问题,经过多次制作总结出来一些小心得,分享下. 我使用的是jquery插件swiper.min.js,动画可以利用animate.css,如果 ...

  6. VS2013编译64位OpenSSL(附32位)

    安装ActivePerl 这个没什么好说的,直接运行msi即可. 编译OpenSSL 1.使用Visual Studio Tool中的“VS2013 x64 本机工具命令提示”来打开控制台:也可以打开 ...

  7. 在 Laravel 5 中使用 Laravel Excel 实现 Excel/CSV 文件导入导出功能

    1.简介 Laravel Excel 在 Laravel 5 中集成 PHPOffice 套件中的 PHPExcel ,从而方便我们以优雅的.富有表现力的代码实现Excel/CSV文件的导入和 导出  ...

  8. 测试Linux端口的连通性的四种方法

    Linux系统有时候需要测试某个端口的连通性,用户可以参考如下方法来测试.   方法一.telnet法 telnet为用户提供了在本地计算机上完成远程主机工作的能力,因此可以通过telnet来测试端口 ...

  9. 使用docker快速搭建环境-安装mysql

    install docker sudo apt-get install -y docker.io download mysql sudo docker pull mysql start mysql s ...

  10. 公共文件模块include

    首先新建一个include 把所有引入的文件放入公共文件里 function getBaseURL() { var pathName = window.document.location.pathna ...