JAVA提高五:注解Annotation

今天我们学习 JDK5.0 中一个非常重要的特性,叫做注解。是现在非常流行的一种方式,可以说因为配置 XML 比较麻烦或者比容易查找出错误,现在越来越多的框架开始支持注解方式,比如注明的 Spring 框架,常用的注解:@Required, @Autowired, @PostConstruct, @PreDestory;可见注解的重要性。

一、什么是注解(Annotation)和 元数据(metadata)?

Annotation(注解)就是 Java 提供了一种为程序元素关联任何信息或任何元数据(metadata)的途径和方法Annotion(注解) 是一个接口,程序可以通过反射来获取指定程序元素的 Annotion 对象,然后通过 Annotion 对象来获取注解里面的元数据。简单一点说:就是为程序打上了某种标记,可以加在类,包,字段 ,方法,方法的参数及局部变量上。

Annotation 的成员在 Annotation 类型中以无参数的方法的形式被声明(比如:String color() default "blue";)。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认语法:允许声明任何 Annotation 成员的默认值。一个 Annotation 可以将 name=value 对作为没有定义默认值的 Annotation 成员的值,当然也可以使用 name=value 对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也可以被子类覆盖。

上面讲了这么多概念,我们来看一个实际的例子:

@Override
public String toString() {
    return "This is String Representation of current object.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么加上与不加上又有什么区别呢?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。也理解了注解实际上就是在源程序上面加上了标签。

元数据从 metadata 一词译来,就是“关于数据的数据”的意思。在 Java 中元数据以标签的形式存在于 Java 代码中,元数据标签的存在并不影响程序代码的编译和执行,它只是被用来生成其它的文件或在运行时获取被运行代码的描述信息

二、注解的分类(JDK 内置系统注解、元注解、自定义注解)

 在学习注解的分类之前,我们先了解下 Annotation 和 Annotation 类型。

Annotation:

  Annotation 使用了在 java5.0 所带来的新语法,它的行为十分类似 public、final 这样的修饰符。每个 Annotation 具有一个名字和成员个数 >=0。每个 Annotation 的成员具有被称为 name=value 对的名字和值(就像 javabean 一样),name=value 装载了 Annotation 的信息。

如:@Override

Annotation 类型:

  Annotation 类型定义了 Annotation 的名字、类型、成员默认值。一个 Annotation 类型可以说是一个特殊的 java 接口,它的成员变量是受限制的,而声明 Annotation 类型时需要使用新语法。当我们通过 java 反射 api 访问 Annotation 时,返回值将是一个实现了该 annotation 类型接口的对象,通过访问这个对象我们能方便的访问到其 Annotation 成员。

如:Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解的分类:

根据注解参数的个数,我们可以将注解分为三类:

    1. 标记注解:一个没有成员定义的 Annotation 类型被称为标记注解。这种 Annotation 类型仅使用自身的存在与否来为我们提供信息。比如后面的系统注解 @Override;
    2. 单值注解
    3. 完整注解  

根据注解使用方法和用途,我们可以将 Annotation 分为三类:

    1.JDK 内置系统注解
    2. 元注解
    3. 自定义注解

1.JDK 内置系统注解:

 

注解的语法比较简单,除了 @符号的使用外,他基本与 Java 固有的语法一致,JavaSE 中内置三个标准注解,定义在 java.lang 中:
    @Override:用于修饰此方法覆盖了父类的方法;
    @Deprecated:用于修饰已经过时的方法;
    @SuppressWarnnings: 用于通知 java 编译器禁止特定的编译警告。

 

下面我们依次看看三个内置标准注解的作用和使用场景。

我们最熟悉的应该是:@Override, 它的定义如下:

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 * The method does override or implement a method declared in a
 * supertype.
 * The method has a signature that is override-equivalent to that of
 * any public method declared in Object.
 *
 * @author  Peter von der Ahé
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从注释,我们可以看出,@Override 的作用是,提示编译器,使用了 @Override 注解的方法必须 override 父类或者 java.lang.Object 中的一个同名方法。我们看到 @Override 的定义中使用到了 @Target, @Retention,它们就是所谓的“元注解”——就是定义注解的注解。我们看下@Retention

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * RetentionPolicy.CLASS.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();}

@Retention 用于提示注解被保留多长时间(简单点说就是生命周期),有三种取值:

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
}
RetentionPolicy.SOURCE 保留在源码级别,被编译器抛弃 (@Override 就是此类); RetentionPolicy.CLASS 被编译器保留在编译后的类文件级别,但是被虚拟机丢弃;
RetentionPolicy.RUNTIME 保留至运行时,可以被反射读取。
再看 @Target:
package java.lang.annotation;

/**

  • Indicates the contexts in which an annotation type is applicable. The
  • declaration contexts and type contexts in which an annotation type may be
  • applicable are specified in JLS 9.6.4.1, and denoted in source code by enum
  • constants of java.lang.annotation.ElementType
  • @since 1.5
  • @jls 9.6.4.1 @Target
  • @jls 9.7.4 Where Annotations May Appear
    */
    @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();
      }
      复制代码
      @Target 用于提示该注解使用的地方,取值有:

复制代码
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
}

分别表示该注解可以被使用的地方:1)类, 接口,注解,enum; 2) 属性域;3)方法;4)参数;5)构造函数;6)局部变量;7)注解类型;8)包

所以:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

表示 @Override 只能使用在方法上,保留在源码级别,被编译器处理,然后抛弃掉。

@Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种 Annotation 在一个没有覆盖父类方法的方法时,java 编译器将以一个编译错误来警示。这个 annotaton 常常在我们试图覆盖父类方法而又写错了方法名时发挥威力。使用方法极其简单:在使用此 annotation 时只要在被修饰的方法前面加上 @Override 即可。

@Deprecated,标记已过时:同 样 Deprecated 也是一个标记注解。当一个类型或者类型成员使用 @Deprecated 修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的 “延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为 @Deprecated,但编译器仍然要报警。

@SuppressWarnnings,抑制编译器警告:

@SuppressWarnings 被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。在 java5.0,sun 提供的 javac 编译器为我们提供了 -Xlint 选项来使编译器对合法的程序代码提出警告,此种警告从某种程度上代表了程序错误。例如当我们使用一个 generic collection 类而又没有提供它的类型时,编译器将提示出 "unchecked warning" 的警告。通常当这种情况发生时,我们就需要查找引起警告的代码。如果它真的表示错误,我们就需要纠正它。例如如果警告信息表明我们代码中的 switch 语句没有覆盖所有可能的 case,那么我们就应增加一个默认的 case 来避免这种警告。
有时我们无法避免这种警告,例如,我们使用必须和非 generic 的旧代码交互的 generic collection 类时,我们不能避免这个 unchecked warning。此时 @SuppressWarning 就要派上用场了,在调用的方法前增加 @SuppressWarnings 修饰,告诉编译器停止对此方法的警告。

@SuppressWarning 不是一个标记注解。它有一个类型为 String[] 的成员,这个成员的值为被禁止的警告名。对于 javac 编译器来讲,被 -Xlint 选项有效的警告名也同样对 @SuppressWarings 有效,同时编译器忽略掉无法识别的警告名。

annotation 语法允许在 annotation 名后跟括号,括号中是使用逗号分割的 name=value 对用于为 annotation 的成员赋值。实例如下:

 

package study.javaenhance;

import java.util.ArrayList;
import java.util.List;

public class FruitService {
@SuppressWarnings(value
= { "rawtypes", "unchecked" })
public static List<String> getFruitList() {
List
<String> fruitList = new ArrayList();
return fruitList;
}

@SuppressWarnings( { </span>"rawtypes", "unchecked"<span style="color: rgba(0, 0, 0, 1)"> })
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> List&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)"> getFruit() {
    List</span>&lt;String&gt; fruitList = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ArrayList();
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> fruitList;
}

@SuppressWarnings(</span>"unused"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
    List</span>&lt;String&gt; strList = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)">();
}

}

 

在这个例子中 SuppressWarnings annotation 类型只定义了一个单一的成员,所以只有一个简单的 value={...} 作为 name=value 对。又由于成员值是一个数组,故使用大括号来声明数组值。注意:我们可以在下面的情况中缩写 annotation:当 annotation 只有单一成员,并成员命名为 "value="。这时可以省去 "value="。比如将上面方法 getFruit() 的 SuppressWarnings annotation 就是缩写的。

SuppressWarnings 注解的常见参数值的简单说明:

    1. deprecation:使用了不赞成使用的类或方法时的警告;
    2. unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 
    3. fallthrough:当 switch 程序块直接通往下一种情况而没有 Break 时的警告;
    4. path:在类路径、源文件路径等中有不存在的路径时的警告; 
    5. serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; 
    6. finally:任何 finally 子句不能正常完成时的警告; 
    7. all:关于以上所有情况的警告。

2. 元注解

元注解的作用就是负责注解其他注解(注解的注解)。Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。Java5.0 定义的元注解:@Target,@Retention,@Documented,@Inherited

这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Target:

@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 声明

@Retention:

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

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

取值(RetentionPoicy)有:

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

@Documented:

@Documented 用于描述其它的 annotation 类型应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。简单一点说:表示注解是否能被 javadoc 处理并保留在文档中。

@Inherited:

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

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

4. 自定义注解

本小节也是最重要的,我们通过会自己定义注解用于去为源程序打上标签,比如 toString 方法我们想要过滤掉某些敏感信息不打印,那么我们可以加上注解然后识别到这个注解的信息,我们就不去打印这个信息等等用途。

使用 @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 类型里面的参数该怎么设定:
  第一, 只能用 public 或默认 (default) 这两个访问权修饰。例如,String value(); 这里把方法设为 defaul 默认类型;   
  第二, 参数成员只能用基本类型 byte,short,char,int,long,float,double,boolean 八种基本数据类型和 String, Enum, Class, annotations 等数据类型,以及这一些类型的数组。例如 String value(); 这里的参数成员就为 String;
  第三, 如果只有一个参数成员,最好把参数名称设为 "value",后加小括号。例如下面的例子 FruitName 注解就只有一个参数成员。

简单的自定义注解和使用注解实例:

package study.javaenhance;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitName
{
String value()
default "";
}

package study.javaenhance;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitColor
{
public enum Color
{
BULE,RED,GREEN;
}

Color fruitColor() </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> Color.BULE;

}

package study.javaenhance;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitProvider
{
/**
* 供应商编号
*
@return
*/
public int id() default -1;

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 
 * 供应商名称 
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> 
 <span style="color: rgba(0, 128, 0, 1)">*/</span>  
<span style="color: rgba(0, 0, 255, 1)">public</span> String name() <span style="color: rgba(0, 0, 255, 1)">default</span> ""<span style="color: rgba(0, 0, 0, 1)">;  
  
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 
 * 供应商地址 
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> 
 <span style="color: rgba(0, 128, 0, 1)">*/</span>  
<span style="color: rgba(0, 0, 255, 1)">public</span> String address() <span style="color: rgba(0, 0, 255, 1)">default</span> ""<span style="color: rgba(0, 0, 0, 1)">;  

}

说明:

1. 注解元素的默认值:

  注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为 null。因此,使用空字符串或 0 作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,以表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。

2. 注解用处:

定义了注解,并在需要的时候给相关类,类属性加上注解信息,如果没有响应的注解信息处理流程,注解可以说是没有实用价值。如何让注解真真的发挥作用,主要就在于注解处理方法,下一步我们将学习注解信息的获取和处理!
如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5 扩展了反射机制的 API,以帮助程序员快速的构造自定义注解处理器。

5.注解处理器类库 (java.lang.reflect.AnnotatedElement):

Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。除此之外,Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

Class:类定义    AccessibleObject:访问控制   Constructor:构造器定义  Field:类的成员变量定义  Method:类的方法定义  Package:类的包定义

 

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射 API 扩充了读取运行时 Annotation 信息的能力。当一个 Annotation 类型被定义为运行时的 Annotation 后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation 才会被虚拟机读取。

AnnotatedElement 接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的如下四个个方法来访问 Annotation 信息:

案例如下:

package study.javaenhance;

import java.lang.reflect.Field;

public class FruitInfoUtil
{
public static void getFruitInfo(Class<?> clazz) {
String strFruitName
= "水果名称:";
String strFruitColor
= "水果颜色:";
String strFruitProvicer
= "供应商信息:";
Field[] fileds
= clazz.getDeclaredFields();
for (Field field : fileds) {
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 = (FruitColor) field
.getAnnotation(FruitColor.
class);
strFruitColor
= strFruitColor
+ fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if (field.isAnnotationPresent(FruitProvider.class)) { // 获取 FruitProvider 注解
FruitProvider fruitProvider = (FruitProvider) field
.getAnnotation(FruitProvider.
class);
strFruitProvicer
= "供应商编号:" + fruitProvider.id() + "供应商名称:"
+ fruitProvider.name() + "供应商地址:"
+ fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}

}

三、为什么使用注解?

 在 JAVA 应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个 JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用 annotation 对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
另外,一些 API 需要使用与程序代码同时维护的附属文件。例如,JavaBeans 需要一个 BeanInfo Class 与一个 Bean 同时使用 / 维护,而 EJB 则同样需要一个部署描述符。此时在程序中使用 annotation 来维护这些附属文件的信息将十分便利而且减少了错误。

四、注解的工作方式和使用方法

在 5.0 版之前的 Java 平台已经具有了一些 ad hocannotation(即时注解) 机制。比如,使用 transient 修饰符来标识一个成员变量在序列化子系统中应被忽略。而 @deprecated 这个 javadoc tag 也是一个 ad hocannotation 用来说明一个方法已过时。从 Java5.0 版发布以来,5.0 平台提供了一个正式的 annotation 功能:允许开发者定义、使用自己的 annoatation 类型。此功能由一个定义 annotation 类型的语法和一个描述 annotation 声明的语法,读取 annotaion 的 API,一个使用 annotation 修饰的 class 文件,一个 annotation 处理工具(apt)组成。
annotation 并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。annotation 可以从源文件、class 文件或者以在运行时反射的多种方式被读取。
当然 annotation 在某种程度上使 javadoc tag 更加完整。一般情况下,如果这个标记对 java 文档产生影响或者用于生成 java 文档的话,它应该作为一个 javadoc tag;否则将作为一个 annotation。

Annotation 使用方法:

1、类型声明方式:
  通常,应用程序并不是必须定义 annotation 类型,但是定义 annotation 类型并非难事。Annotation 类型声明于一般的接口声明极为类似,区别只在于它在 interface 关键字前面使用“@”符号。
  annotation 类型的每个方法声明定义了一个 annotation 类型成员,但方法声明不必有参数或者异常声明;方法返回值的类型被限制在以下的范围:primitives、String、Class、enums、annotation 和前面类型的数组;方法可以有默认值。

public @interface RequestForEnhancement {  
    int    id();  
    String synopsis();  
    String engineer() default "[unassigned]";   
    String date();    default "[unimplemented]";   
}  

2、annotation 使用时候声明方式:

annotation 是一种修饰符,能够如其它修饰符(如 public、static、final)一般使用。习惯用法是 annotaions 用在其它的修饰符前面。annotations 由“@+annotation 类型 + 带有括号的成员 - 值列表”组成。这些成员的值必须是编译时常量(即在运行时不变)。

@RequestForEnhancement( id= 2868724, synopsis = "Enable time-travel", engineer = "Mr. Peabody",  date     = "4/1/3007" )  
public static void travelThroughTime(Date destination) 
{
    ... 

}

当声明一个没有成员的 annotation 类型声明时,可使用以下方式:

/** 
 * Indicates that the specification of the annotated API element 
 * is preliminary and subject to change. 
 */  
public @interface Preliminary { }  

如果在 annotations 中只有唯一一个成员,则该成员应命名为 value:

public @interface Copyright {String value();  
}  

更为方便的是对于具有唯一成员且成员名为 value 的 annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):

@Copyright("2002 Yoyodyne Propulsion Systems")  
public class OscillationOverthruster {...}  

下面是一个复杂的 Annotataion 类型声明,其成员是 Annotation 类型的数组:

import java.lang.annotation.*;  

/**

  • Reviews annotation 类型只有一个成员,
  • 由 Review annotation 组成的数组
    */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Reviews {
    Review[] value();
    }

/**

  • Review annotation 类型有 3 个成员:

  • 枚举类型成员 grade

  • 表示 Review 名称的字符串类型成员 Reviewer

  • 具有默认值的字符串类型成员 Comment。
    */
    public @interface Review {
    // 内嵌的枚举类型
    public static enum Grade {EXCELLENT, SATISFACTORY, UNSATISFACTORY};

    // 下面的方法定义了 annotation 的成员
    Grade grade();
    String reviewer();
    String comment()
    default "";
    }

Reviews annotation 类型只有一个成员,但是这个成员的类型是复杂的:由 Review annotation 组成的数组。Review annotation 类型有 3 个成员:枚举类型成员 grade、表示 Review 名称的字符串类型成员 Reviewer、具有默认值的字符串类型成员 Comment。

Annotation 类型的成员不能是 generic。只有返回值类型是 Class 的方法可以在 annotation 类型中使用 generic,因为此方法能够用类转换将各种类型转换为 Class。

最后,我们来定义一个 annotation 方法用于罗列出类运行中所有的 unchecked 异常(这种情况不一定是错误)。这个 annotation 类型将一个数组作为了唯一的成员。数组中的每个元素都是异常类。为了加强对未检查的异常(此类异常都是在运行时抛出)进行报告,我们可以在代码中对异常的类型进行限制:

 

五、注解 Annotation 实例分析

 结合上面所讲的,我们在这里建立一个简单的基于 annotation 测试框架。首先我们需要一个 annotation 类型来表示某个方法是一个应该被测试工具运行的测试方法。

import java.lang.annotation.*;  

/**

  • Indicates that the annotated method is a test method.
  • This annotation should be used only on parameterless static methods.
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test { }

值得注意的是 annotaion 类型声明是可以标注自己的,这样的 annotation 被称为“meta-annotations”。
在上面的代码中,@Retention(RetentionPolicy.RUNTIME) 这个 meta-annotation 表示了此类型的 annotation 将被虚拟机保留使其能够在运行时通过反射被读取。而 @Target(ElementType.METHOD) 表示此类型的 annotation 只能用于修饰方法声明。

下面是一个简单的程序,其中部分方法被上面的 annotation 所标注:

public class Foo {  

@Test
public static void m1(){}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m2() { }  
  
@Test 
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m3() {  
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> RuntimeException("Boom"<span style="color: rgba(0, 0, 0, 1)">);  
}  
  
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m4() { }  
  
@Test

public static void m5(){}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m6() { }  
  
@Test 
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m7() {  
    </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> RuntimeException("Crash"<span style="color: rgba(0, 0, 0, 1)">);  
}  
  
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m8() { }  

}

使用测试:

import java.lang.reflect.*;  

public class RunTests {
public static void main(String[] args) throws Exception {
int passed = 0, failed = 0;
for (Method m : Class.forName(args[0]).getMethods()) {
if (m.isAnnotationPresent(Test.class)) {
try {
m.invoke(
null);
passed
++;
}
catch (Throwable ex) {
System.out.printf(
"Test %s failed: %s %n", m, ex.getCause());
failed
++;
}
}
}
System.out.printf(
"Passed: %d, Failed %d%n", passed, failed);
}
}

这个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试 annotation 类型标注过的方法。在此过程中为了找出哪些方法被 annotation 类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运行通过 / 失败的方法数量。

注解在很多框架、类库中有广泛的应用。如 JUnit 测试框架, Spring, Hibernate, EJB 等等。这也是开发人员所常常用到的一种方式。

上面的介绍说明了 annotation 的使用方法、定义方式、分类。初学者可以通过以上的说明制作简单的 annotation 程序,但是对于一些高级的 annotation 应用(例如使用自定义 annotation 生成 javabean 映射 xml 文件)还需要进一步的研究和探讨。同时,annotation 运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的 annotation 应用,但在编译时的 annotation 应用还没有涉及,因为编译时的 annotation 要使用 annotation processing tool(APT)
annotation 本身使用时十分简便。例如一个本地变量可以被一个以 NonNull 命名的 annotation 类型所标注,来作为对这个本地变量不能被赋予 null 值的断言。而我们可以编写与之配套的一个 annotation 代码分析工具,使用它来对具有前面变量的代码进行解析,并且尝试验证这个断言。当然这些代码并不必自己编写。在 JDK 安装后,在 JDK/bin 目录中可以找到名为“apt”的工具,它提供了处理 annotation 的框架:它启动后扫描源代码中的 annotation,并调用我们定义好的 annotation 处理器完成我们所要完成的工作(比如验证前面例子中的断言)。说到这里,annotation 的强大功能似乎可以替代 XDoclet 这类的工具了,随着我们的深入,大家会更加坚信这一点。
注:详细描述请参看 JSR 250 规范 http://www.jcp.org/aboutJava/communityprocess/pfd/jsr250/

七、总结

引用网上的一张图:

注解总结

 

 

参考资料:

http://blog.csdn.net/zhoudaxia/article/details/33456147

http://blog.csdn.net/zhoudaxia/article/details/33731583