跳到主要内容

Java 注解重复

什么是重复注解?

在Java 8之前,同一个注解不能在同一个位置(类、字段、方法等)上重复使用。然而,在实际编程中,有时我们需要对同一个元素应用多个相同类型的注解。Java 8引入了重复注解特性,允许在同一个声明上多次使用同一个注解。

备注

重复注解是Java 8的新特性,因此需要使用Java 8或更高版本才能使用此功能。

为什么需要重复注解?

考虑一个场景:假设你正在创建一个资源管理系统,需要为一个类定义多个角色的访问权限。如果没有重复注解,你可能需要创建一个包含角色列表的单一注解,或者创建一个专门的容器注解。有了重复注解,代码会更加直观和易于维护。

如何定义重复注解

定义重复注解需要三个步骤:

  1. 定义一个注解,使用@Repeatable元注解标记它
  2. 定义一个容器注解,用于存储重复注解的实例
  3. @Repeatable元注解中指定容器注解的类

让我们通过一个例子来理解:

java
// 步骤1:定义可重复的注解
@Repeatable(Roles.class) // 指定容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Role {
String name();
String description() default "";
}

// 步骤2:定义容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}

如何使用重复注解

一旦定义了重复注解,你可以在同一个声明上多次使用它:

java
@Role(name = "admin", description = "可以访问所有功能")
@Role(name = "editor", description = "可以编辑内容")
@Role(name = "viewer", description = "只能查看内容")
public class ResourceManager {
// 类的实现
}

如何访问重复注解

可以通过反射API来访问重复注解。Java提供了两种方式:

  1. 使用getAnnotationsByType()方法获取所有重复注解实例
  2. 使用getAnnotation()方法获取容器注解,然后访问其value()方法

下面是一个完整的示例:

java
import java.lang.annotation.*;
import java.lang.reflect.Method;

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

@Retention(RetentionPolicy.RUNTIME)
@interface Schedules {
Schedule[] value();
}

public class RepeatingAnnotationsExample {

@Schedule(dayOfMonth = "last")
@Schedule(dayOfWeek = "Fri", hour = 20)
public void doPeriodicCleanup() {
System.out.println("执行周期性清理任务");
}

public static void main(String[] args) {
try {
Method method = RepeatingAnnotationsExample.class.getMethod("doPeriodicCleanup");

// 方式1:直接获取所有重复注解实例
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);
System.out.println("方式1:找到 " + schedules.length + " 个 @Schedule 注解");
for (Schedule schedule : schedules) {
System.out.println(" 月份日期: " + schedule.dayOfMonth() +
", 星期几: " + schedule.dayOfWeek() +
", 时间: " + schedule.hour() + "点");
}

// 方式2:先获取容器注解,再获取所有重复注解实例
Schedules schedulesContainer = method.getAnnotation(Schedules.class);
if (schedulesContainer != null) {
System.out.println("方式2:通过容器找到 " + schedulesContainer.value().length + " 个 @Schedule 注解");
for (Schedule schedule : schedulesContainer.value()) {
System.out.println(" 月份日期: " + schedule.dayOfMonth() +
", 星期几: " + schedule.dayOfWeek() +
", 时间: " + schedule.hour() + "点");
}
}

} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}

输出结果:

方式1:找到 2 个 @Schedule 注解
月份日期: last, 星期几: Mon, 时间: 12点
月份日期: first, 星期几: Fri, 时间: 20点
方式2:通过容器找到 2 个 @Schedule 注解
月份日期: last, 星期几: Mon, 时间: 12点
月份日期: first, 星期几: Fri, 时间: 20点

重复注解的工作原理

当Java编译器看到多个相同类型的注解应用于同一个声明时,它会自动创建容器注解的实例,并将所有重复注解作为其值。在运行时,JVM提供了APIs来直接访问重复注解,甚至可以绕过容器注解。

下面是重复注解处理的工作流程图:

实际应用场景

场景一:多角色权限控制

在权限管理系统中,一个资源可能需要多种角色的权限:

java
@Role(name = "admin")
@Role(name = "manager")
public void deleteUser(String userId) {
// 删除用户的实现
}

在执行此方法前,系统可以检查当前用户是否拥有必要的角色权限。

场景二:测试多种条件

在单元测试中,可能需要针对同一个测试方法测试多种场景:

java
@Test
@TestCase(input = "hello", expected = "HELLO")
@TestCase(input = "", expected = "")
@TestCase(input = null, expected = "")
public void testToUpperCase() {
// 测试代码
}

场景三:多重过滤器

在Web开发中,可以为同一个API端点定义多个过滤器:

java
@Filter(pattern = "*.jpg", maxSize = "5MB")
@Filter(pattern = "*.png", maxSize = "2MB")
@Filter(pattern = "*.pdf", maxSize = "10MB")
@RequestMapping("/upload")
public String uploadFile(MultipartFile file) {
// 处理文件上传
}

注意事项

  1. 重复注解需要Java 8或更高版本才能支持。
  2. 定义重复注解时必须同时定义容器注解。
  3. 容器注解必须有一个名为value的数组类型元素,其类型是可重复的注解。
  4. 重复注解和容器注解的保留策略(Retention Policy)应该相同,否则可能会导致运行时无法正确访问注解。
警告

如果你只定义了重复注解但没有定义容器注解,或者在@Repeatable中指定了错误的容器注解类,编译时会出错。

总结

Java重复注解是一个强大的特性,它允许我们在同一个位置多次应用相同类型的注解,使代码更加清晰和表达力更强。要使用重复注解,需要:

  1. 使用@Repeatable标记注解类
  2. 定义一个容器注解类来存储重复注解的多个实例
  3. 在同一个位置多次应用该注解

通过重复注解,我们可以更优雅地处理需要多次应用相同类型注解的场景,例如权限控制、测试用例定义、过滤器配置等。

练习

  1. 创建一个名为@Schedule的重复注解,可以为某个方法设置多个调度时间。
  2. 实现一个简单的权限系统,使用重复注解@Permission来定义不同的访问权限。
  3. 编写代码,通过反射获取类或方法上的重复注解信息,并进行相应的处理。

进一步学习资源

通过掌握重复注解特性,你将能够编写更加简洁、表达力更强的代码,特别是在框架设计和API开发方面。