为什么要引入注解?

在没有注解前,在描述程序中的元数据(配置参数,函数信息等)时大多通过 XML 文件的方式实现。XML 文件将元数据描述和代码完全解耦,当所需描述的元数据过多时,程序出错时难定位,维护起来很困难。

注解则将代码和元数据耦合到了一起,程序出错时定位方便,维护起来也很方便,只要找到其对应的注解去配置即可。

借用 Stack Overflow 上的一个例子 来理解,如果我们想要介绍一个类,可以在类的头部添加一些描述信息(作者,版本,上次修改日期等):

public class Generation3List extends Generation2List {

   // Author: John Doe
   // Date: 3/17/2002
   // Current revision: 6
   // Last modified: 4/12/2004
   // By: Jane Doe
   // Reviewers: Alice, Bill, Cindy

   // class code goes here

}

如果我们想要获取该类的上述信息,则只能肉眼去看,不能通过程序来获取。

引入注解后,可以先自定义一个注解:

 @interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

然后把这个注解放到所需描述的类的头部:

@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

以后想要获取这个类的相关描述信息,通过反射获取相关参数即可,后续维护时也可以直接顺手修改,非常方便。

定义注解

定义注解和定义一个类非常相似,只需要通过@interface来声明即可:

MyAnnotation.java

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
    String myAnnotationValue();
}

其中@Retention指明了该注解如何保存,即RetentionPolicy

  • SOURCE – 在*.java文件中保留;
  • CLASS – 在编译得到的*.class文件中保留;
  • RUNTIME – 在程序运行时可被 JVM 获取,通过反射可以获取。

@Target则指明了该注解可以在源代码的哪些地方使用:

  • TYPE – 类、接口(包括自定义的注解)或者枚举类的声明位置,即源代码的声明首部;
  • FIELD – 字段或者枚举类中的常量;
  • METHOD – 方法(函数)的声明位置;
  • PARAMETER – 方法(函数)的参数位置;
  • CONSTRUCTOR – 构造方法;
  • LOCAL_VARIABLE – 局部变量;
  • ANNOTATION_TYPE – 所声明的注解上方,即注解可以用来注解注解,实际上@Retention@Target也是注解,我们上面自定义注解的时候也用到了它们;
  • PACKAGE – 包;
  • TYPE_PARAMETER – 类型参数声明(1.8 新加入),任意使用类型的地方;
  • TYPE_USE – 类型使用声明(1.8 新加入),任意声明类型的地方。

诸如@Retention@Target这类注解属于元注解,用于注解其它的注解,它们的@Target的值均为ANNOTATION_TYPE

Java 中内置了四种元注解,除了上述的@Retention@Target,还包括:

  • @Documented – 标记这些注解是否包含在用户文档中;
  • @Inherited – 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)。

除此之外还有三个特殊的注解:

  • @Override – 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误;
  • @Deprecated – 标记过时方法。如果使用该方法,会报编译警告;
  • @SuppressWarnings – 指示编译器去忽略注解中声明的警告。

获取注解

通过 Java 的反射特性可以获取注解的相关信息,比如注解类型、注解的参数值等。

AnnotationTest.java

@MyAnnotation(myAnnotationValue = "TYPE")
public class AnnotationTest {
    @MyAnnotation(myAnnotationValue = "HELLO")
    public int number = 123;
    @MyAnnotation(myAnnotationValue = "WORLD")
    public String str = "hola";
}

Main.java

public class Main {
    public static void main(String[] args) {
        Class<?> cls = AnnotationTest.class;
        // 获取字段中的注解
        for (Field field : cls.getFields()) {
            for (Annotation annotation : field.getAnnotations()) {
                if (field.isAnnotationPresent(MyAnnotation.class)) {
                    MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
                    System.out.println(myAnnotation.myAnnotationValue());
                }
            }
        }
        // 获取类或接口上的注解
        if (cls.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = cls.getAnnotation(MyAnnotation.class);
            System.out.println(annotation.myAnnotationValue());
        }
    }
}

输出

HELLO
WORLD
TYPE