要让自定义注解支持重复使用,关键在于正确定义被重复注解与容器注解的绑定关系:@Repeatable必须精确指向容器类,容器类需满足value()字段类型严格匹配、@Target和@Retention与被重复注解完全一致。
要让自定义注解支持重复使用,关键不是“配置”容器类,而是**正确定义两者之间的绑定关系**。java 编译器和反射机制依赖这套约定来自动收集聚合注解,不能靠配置或手动注册。
容器注解必须满足三项硬性要求
它不是任意起个名、加个数组字段就行的。以下三点缺一不可:
-
名称惯例但非强制:通常用复数形式(如
Roles对应Role),但真正起作用的是@Repeatable的参数指向 -
value 字段必须存在且类型严格匹配:字段名必须是
value,返回类型必须是被重复注解类型的数组,例如RequireRole[] value() -
元注解需保持一致:容器注解的
@Target和@Retention必须与被重复注解完全相同,否则运行时读取可能失败或被忽略
@Repeatable 参数必须精确指向容器类
被重复注解上写的 @Repeatable(XXX.class),这个 XXX.class 就是唯一决定“谁来装它”的依据。编译器据此把多个同名注解打包进该容器;反射时也靠这个关联去查找对应容器实例。
例如:
@Repeatable(RequireRoles.class) // ← 这里指明:所有 @RequireRole 都归入 RequireRoles 容器
public @interface RequireRole { String value(); }
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface RequireRoles { // ← 必须与上面参数完全一致
RequireRole[] value(); // ← 字段名+类型必须精准匹配
}
运行时获取全部实例只能用 getAnnotationsByType()
直接调用 getAnnotation(RequireRole.class) 永远只返回第一个,因为 JVM 不再把它当作单例注解看待。正确方式是:
-
clazz.getAnnotationsByType(RequireRole.class)→ 返回所有@RequireRole实例组成的数组 -
method.getAnnotationsByType(RequireRole.class)→ 同样适用于方法级 - 若需访问容器本身(较少见),可用
clazz.getAnnotation(RequireRoles.class),再调用.value()取出数组
常见错误点提醒
这些细节看似微小,却常导致注解不生效或反射取不到数据:
- 容器注解漏写
@Retention(RUNTIME)→ 运行时根本看不到 - 容器的
@Target范围比被重复注解更窄(比如少了ElementType.TYPE)→ 类上标注时报错或被跳过 - 把
value()写成values()或其他名字 → 编译通过但运行时无法识别为有效容器 - 数组元素类型写错,如写成
String[] value()而非RequireRole[]→ 编译报错

















