注解(六):重复注解

存在一些情况,想对一个声明或类型使用应用相同的注解。随着 Java SE 8 版发布,重复注解允许你这样做。

例如,要编写代码使用计时器服务在一个给定的时间或以确定的调度运行一个方法,类似于 UNIX cron 服务。现在想设置一个计时器来运行一个方法(doPeriodicCleanup),在每个月的最后一天及在每个周五下午 11:00 点。为了设置计时器来运行,创建一个 @Schedule 注解并应用到 doPeriodicCleanup 方法两次。第一个使用指定每个月最后一天,第二个指定周五下午 11:00 点,像下面代码示例展示的:

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }

前面的例子应用一个注解到一个方法。可以在任何使用标准注解的地方重复一个注解。例如,有一个处理未授权异常的类。为管理者用一个 @Alert 注解且为管理员用另外一个 @Alert 注解:

@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }

为了兼容性原因,重复注解存储在一个容器注解中,容器注解是由 Java 编译器自动生成的。为了编译器做这个,在代码中需要两个声明。

步骤 1:声明一个可重复的注解类型

这个注解类型必须用 @Repeatable 元注解标记。下面的例子定义了一个定制的 @Schedule 重复注解类型:

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

@Repeatable 元注解的值(在括号中的)是 Java 编译器生成来存储重复注解的容器注解的类型。在这个例子中,容器注解的类型是 Schedules,因此重复 @Schedule 注解存储在一个 @Schedules 注解中。

如果不首先声明注解是可重复的就应用同一个注解到一个声明将引起一个编译器错误。

步骤 2:声明容器注解类型

容器注解类型必须有一个数组类型的 value 元素。数组中的元素类型必须是可重复注解的类型。Schedules 容器注解类型的声明如下:

public @interface Schedules {
  Schedule[] value();
}

检索注解

在反射 API 中有几种方法可以用来检索注解。这些方法的行为返回一个注解,例如 AnnotatedElement.getAnnotationByType(Class),如果一个需要类型的注解出现这些方法不会改变只返回单个注解。如果多于一个需要类型的注解出现,可以首先获取它们的容器注解来获取它们。以这种方式,继承的代码可以继续工作。Java SE 8 中引入了其他方法,可以通过扫描容器注解一次返回多个注解,像 AnnotatedElement.getAnnotations(Class)。参见 AnnotatedElement 类定义获取关于所有可获得方法的信息。

设计考量

当设计一个注解类型时,必须考虑这种类型注解的基。可能使用一个注解零次、一次,或者多次(如果注解类型被标记为 @Repeatable)。也可能通过 @Target 元注解来限制一个注解哪里可以使用。例如,可以创建一个可重复的注解类型只能用在方法和属性上。仔细地设计注解类型很重要以确保程序员尽可能使用适合的和功能强大的注解。