注解(Annotation)是Java语言用于工具处理的标注:
注解可以配置参数,没有指定配置的参数使用默认值;
如果参数名称是value,且只有一个参数,那么可以省略参数名称。

1.定义注解

Java语言使用@interface语法来定义注解(Annotation),它的格式如下:

  1. public @interface Report {
  2. int type() default 0;
  3. String level() default "info";
  4. String value() default "";
  5. }

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value。

1.元注解

有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

1.@Target

最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:

  • 类或接口:ElementType.TYPE;
  • 字段:ElementType.FIELD;
  • 方法:ElementType.METHOD;
  • 构造方法:ElementType.CONSTRUCTOR;
  • 方法参数:ElementType.PARAMETER。

    2.@Retention

    另一个重要的元注解@Retention定义了Annotation的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE;

  • 仅class文件:RetentionPolicy.CLASS;
  • 运行期:RetentionPolicy.RUNTIME。

如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解。

3.@Repeatable

使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。

4.@Inherited

使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效。

2.如何定义Annotation

我们总结一下定义Annotation的步骤:
第一步,用@interface定义注解:

  1. public @interface Report {
  2. }

第二步,添加参数、默认值:

  1. public @interface Report {
  2. int type() default 0;
  3. String level() default "info";
  4. String value() default "";
  5. }

把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。
第三步,用元注解配置注解:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Report {
  4. int type() default 0;
  5. String level() default "info";
  6. String value() default "";
  7. }

其中,必须设置@Target和@Retention,@Retention一般设置为RUNTIME,因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited和@Repeatable。
Java使用@interface定义注解:
可定义多个参数和默认值,核心参数使用value名称;
必须设置@Target来指定Annotation可以应用的范围;
应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation。

3.处理注解

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

  • SOURCE类型的注解在编译期就被丢掉了;
  • CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写。
因此,我们只讨论如何读取RUNTIME类型的注解。
因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。
Java提供的使用反射API读取Annotation的方法包括:
判断某个注解是否存在于Class、Field、Method或Constructor:

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

例如:

  1. // 判断@Report是否存在于Person类:
  2. Person.class.isAnnotationPresent(Report.class);

使用反射API读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

例如:

  1. // 获取Person定义的@Report注解:
  2. Report report = Person.class.getAnnotation(Report.class);
  3. int type = report.type();
  4. String level = report.level();

使用反射API读取Annotation有两种方法。方法一是先判断Annotation是否存在,如果存在,就直接读取:

  1. Class cls = Person.class;
  2. if (cls.isAnnotationPresent(Report.class)) {
  3. Report report = cls.getAnnotation(Report.class);
  4. ...
  5. }

第二种方法是直接读取Annotation,如果Annotation不存在,将返回null:

  1. Class cls = Person.class;
  2. Report report = cls.getAnnotation(Report.class);
  3. if (report != null) {
  4. ...
  5. }

可以在运行期通过反射读取RUNTIME类型的注解,注意千万不要漏写@Retention(RetentionPolicy.RUNTIME),否则运行期无法读取到该注解。
可以通过程序处理注解来实现相应的功能:

  • 对JavaBean的属性值按规则进行检查;
  • JUnit会自动运行@Test标记的测试方法。