概念

注解,也就是Annotation,是Java 5 开始引入的特征,它提供了一种安全的类似注释的机制,用来将任何的信息或元数据与程序元素(类、方法、属性等)进行关联。
Annotation通过Java反射机制来访问注解信息,相关类根据这些信息决定对这些程序元素采用什么行为。Java语言解释器在工作时会忽略这些注解,因此注解在JVM中是“不起作用”的,只能通过配套工具对这些注解类型的信息进行访问和处理。
在软件框架或者工具中常常用到注解,比如Struts,JUnit,TestNG,Spring等。

定义注解

注解使用关键字@interface定义,而不是interface。所有注解都是继承的java.lang.annotation.Annotation接口,但是如果直接创建一个interface继承java.lang.annotation.Annotation接口并不是定义一个注解类型。
创建的注解类型中可以定义常量、静态成员,也可以定义方法。但是这些方法的声明里必须是无参数、无抛出异常的。方法的返回值必须为primitive类型(包括String类型)、Class类型、枚举类型、注解类型中的一个或者以上之一组成的一维数组。方法的后面可以用default和一个值来表示这个方法的默认返回值,注意,默认值不能设为null。只有返回值是Class的方法可以在注解类型中使用泛型,因为该方法能够将各种类型通过类转换变成Class
举一个定义注解的例子:

public @interface Property{
    boolean nullable default false;
    String value default "";
}

public class User{
    @Property(value = "林雷")
    private String userName;
}

如以上先定义了一个注解类型@Property,然后定义一个Bean,也就是User类,其中有个userName的属性,对其用@Property注解进行了标记,那么也就是说userName被标记为值为“林雷”,而且它是不可空的,@Property后面括号里用等号连接的是个赋值操作,等号左边的内容实际上就是注解中定义的方法,等号右边的内容就是让注解中对应的方法返回一个什么样的值,如果不定义的话就采用注解里设的默认值。可以定义多个,以逗号隔开。

三种标准注解

从Java 5开始就已经自带了三种标准注解,如下:

@Override

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

@Override是一种标记型注解。表示当前的方法定义覆盖了父类中的方法,起到断言作用,方法签名必须相同(即方法名、参数类型、参数顺序、参数个数都一样),否则无法通过编译。这个注解常用作试图覆盖父类方法而又写错了方法名时的一个保障性校验。

@Deprecated

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

@Deprecated是一种标记型注解。对不应该再使用的程序元素添加该注解,当调用被注解的方法时,在编译器会显示提示信息不鼓励使用被这个注解了的程序元素。
注意,该注解与JavaDoc注释中的@deprecated标记是有区别的:前者是用于Java编译器识别的,而后者是在生成文档时被JavaDoc识别。

@SuppressWarnings

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

此注解能告诉Java编译器关闭对类、方法及成员变量的警告。有时编译时会提出一些警告,对于这些警告有的隐藏着Bug,有的是无法避免的,对于某些不想看到的警告信息,可以通过这个注解来屏蔽。@SuppressWarning不是一个marker annotation。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。

四种元注解

@Target

表示注解可以用在什么地方,它的值是ElementType枚举中的枚举类型:
CONSTRUCTOR 构造器声明;
FIELD 域声明;
METHOD 方法声明;
TYPE 类、接口或enum声明;
PARAMETER 参数声明;
LOCAL_VARIABLE 局部变量声明;
ANNOTATION_TYPE 注释类型声明
PACKAGE 包声明

@Retention

表示需要在什么级别保存该注解信息。设值时需要提供java.lang.annotation.RetentionPolicy中的枚举类型。

public enum RetentionPolicy{
    SOURCE, //编译程序处理完Annotation信息后就完成任务
    CLASS, //编译程序将Annotation储存于class中,但会被虚拟机丢弃,@Retention默认是Class级别
    RUNTIME //编译程序将Annotation储存于class中,虚拟机在运行期也保留注解,可以通过反射机制读取注解信息
}

@Documented

将此注解包含到Javadoc中。

@Inherited

允许子类继承父类的注解。


在自定义注解的时候可以综合使用这四个元注解定义自己定义的注解的作用范围等信息。

通过反射加载注解

还用之前举的例子,下面简单写个通过反射获得注解信息的例子。
先定义注解:

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

@Retention(RetentionPolicy.RUNTIME)
public @interface Property {
    boolean nullable() default false;

    String value() default "";
}

定义POJO Bean:

public class User {

    @Property(value = "李雷")
    private String username;

    @Property(nullable = true, value = "test@a.com")
    private String email;
}

接下来就是获取这个注解了:

import java.lang.reflect.Field;

public class AnnotationTest {
    public static void main(String[] args){
        //获取User类的Class实例
        Class<?> clazz = User.class;
        //获取这个类的所有属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields){
            //判断这个属性中是否有Property注解
            if (field.isAnnotationPresent(Property.class)){
                Property property = field.getAnnotation(Property.class);
                System.out.println(field.getName()+": "+property.nullable()+" "+property.value());
            }
        }
    }
}

运行之后结果为:

username: false 李雷
email: true test@a.com

从上面的结果也可以看到,即使类中的属性定义为private类型,也可以通过注解对其进行标记赋值,这个方法在Spring等框架中常常使用。

参考资料