@Repeatable通过编译器自动封装为容器注解并在运行时用getAnnotationsByType()解包实现重复标注,字节码中仅存容器注解,原始注解不直接出现,且容器需满足Retention、Target、value()方法等硬性约束。

@Repeatable不是让注解“绕过限制”,而是通过编译器自动封装 + 反射语义增强,实现语法简洁、运行时可控的重复标注。
编译期:容器注解是唯一真实存在的注解
Java字节码规范明确要求:同一元素的 RuntimeVisibleAnnotations 属性中,同类型注解最多只允许出现一次。@Repeatable 严格遵守这一规则——它不改变字节码结构,而是由编译器完成三步转换:
- 识别被 @Repeatable(Roles.class) 标注的注解(如 @Role),并验证 Roles 是否为合法容器(含 value() 方法,返回 Role[])
- 将源码中多个 @Role("A")、@Role("B") 合并为单个 @Roles({@Role("A"), @Role("B")})
- 最终字节码里只记录 @Roles 注解;原始 @Role 不会直接出现在注解表中
用 javap -v 查看编译后 class 文件,你会看到的只有 @Roles,其 value 字段才真正承载了所有重复实例。
运行时:必须用 getAnnotationsByType() 才能拿到重复项
普通反射方法 getAnnotation(Role.class) 返回 null,因为它只查找“直接存在”的注解——而 @Role 在字节码中并不存在。真正能还原重复语义的是:
立即学习“Java免费学习笔记(深入)”;
- getAnnotationsByType(Role.class):自动识别容器(如 @Roles),展开 value() 数组,并合并父类/接口上同类型的重复注解
- getDeclaredAnnotationsByType(Role.class):行为同上,但不向上继承,仅限当前声明位置
这两个方法是唯一推荐方式;其余反射调用(如 getAnnotations() 或 isAnnotationPresent())无法自动解包,需手动处理容器注解。
容器注解必须满足硬性约束
自定义可重复注解时,容器类写错会导致编译失败,关键校验包括:
- @Retention 必须与重复注解完全一致(例如都为 RUNTIME),否则反射无法还原
- @Target 范围不能比重复注解更窄(如 @Role 标在 METHOD 上,则 @Roles 的 @Target 至少也要包含 METHOD)
- 必须声明 public Role[] value() 方法,类型严格为数组,不可用 List 或 Object[] 替代
- 容器注解自身不能被 @Repeatable 标注(禁止嵌套容器)
实战建议:优先用标准方式,避免手动解包
虽然可通过 getAnnotation(Roles.class) 获取容器再遍历 value(),但这绕过了 Java 设计的统一语义,容易遗漏继承链上的注解,也不支持泛型擦除后的类型安全。正确做法是:
- 始终使用 getAnnotationsByType(Role.class) 获取 Role[] 数组
- 若需区分当前类声明 vs 继承来的重复注解,选用 getDeclaredAnnotationsByType()
- Spring 等主流框架(如 @PropertySource / @ComponentScan)均基于此机制实现,无需额外适配
不复杂但容易忽略:写对容器、用对方法,就能自然享受重复注解的表达力和反射一致性。


















