Java Annotation

元注解

@Target

定义 Annotation 能够被应用于源码的哪些位置:

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

例如:@Test 注解可以用在方法上。

@Target(ElementType.METHOD)
public @interface Test{
    int type() default 0;
    String level() default "info";
    String value() default "";
}

当然,@Target 注解参数可以为数组 {ElementType.METHOD, ElementType.FIELD}:

@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Test{
    ...
}

@Retention

定义了 Annotation 的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

如果 @Retention 不存在,则该 Annotation 默认为 CLASS

注意:

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

@Repeatable

使用 @Repeatable 这个元注解可以定义 Annotation 是否可重复。

@Repeatable(Tests.class)
@Target(ElementType.TYPE)
public @interface Test {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Target(ElementType.TYPE)
public @interface Tests {
    Test[] value();
}

经过 @Repeatable 修饰后,在某个类型声明处,就可以添加多个 @Test 注解:

@Test(type=1, level="debug")
@Test(type=2, level="warning")
public class Hello {
}

@Inherited

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

@Inherited
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

在使用的时候,如果一个类用到了@Report:

@Report(type=1)
public class Person {
}

则它的子类默认也定义了该注解:

public class Student extends Person {
}

如何定义注解

第一步,用 @interface 定义注解:

public @interface Report {
}

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

public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。

第三步,用元注解配置注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

其中,必须设置 @Target 和 @Retention,@Retention 一般设置为 RUNTIME,因为我们自定义的注解通常要求在运行期读取,默认情况下为 class。一般情况下,不必写 @Inherited 和 @Repeatable。

读取注解

因为注解定义后也是一种 class,所有的注解都继承自 java.lang.annotation.Annotation,因此,读取注解,需要使用反射 API。

Java 提供的使用反射 API 读取 Annotation 的方法包括:

1. 判断某个注解是否存在于 Class、Field、Method 或 Constructor:

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

例如:

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

使用反射 API 读取 Annotation

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

例如:

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

使用注解

例如 @Range 注解,它来定义一个 String 字段的规则:字段长度满足 @Range 的参数定义。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
    int min() default 0;
    int max() default 255;
}

在某个 JavaBean 中,使用该注解:

public class Person {
    @Range(min=1, max=20)
    public String name;

    @Range(max=10)
    public String city;
}

但是,定义了注解,本身对程序逻辑没有任何影响。必须自己编写代码来使用注解。这里,编写一个 Person 实例的检查方法,它可以检查 Person 实例的 String 字段长度是否满足 @Range 的定义:

void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
    // 遍历所有 Field:
    for (Field field : person.getClass().getFields()) {
        // 获取 Field 定义的 @Range:
        Range range = field.getAnnotation(Range.class);
        // 如果 @Range 存在:
        if (range != null) {
            // 获取 Field 的值:
            Object value = field.get(person);
            // 如果值是 String:
            if (value instanceof String) {
                String s = (String) value;
                // 判断值是否满足 @Range 的 min/max:
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}

这样,通过 @Range 注解,配合 check() 方法,就可以完成 Person 实例的检查。检查逻辑完全是自己编写的,JVM 不会自动给注解添加任何额外的逻辑。