应优先使用getdeclaredannotation()获取类上显式声明的自定义注解实例,因其不向上查找继承链、语义准确;需确保注解设@retention(runtime),并通过接口方法(如anno.value())安全读取属性值,避免反射字段访问。

获取类上的自定义注解实例(getAnnotation() vs getDeclaredAnnotation())
直接调用 clazz.getAnnotation(YourAnnotation.class) 是最常用方式,但它只查找**继承链上**的注解(即带 @Inherited 且父类声明了该注解时才可能回溯)。绝大多数自定义注解不继承,所以实际应优先用 clazz.getDeclaredAnnotation(YourAnnotation.class) —— 它只查当前类显式声明的注解,不向上查找,语义更准确、行为更可预测。
常见错误:用 getAnnotations() 返回 Annotation[] 后遍历匹配类型,效率低且容易漏掉重复注解(如用了 @Repeatable)。
- 确保注解声明时设置了
@Retention(RetentionPolicy.RUNTIME),否则反射拿不到 - 若注解在接口上,
getDeclaredAnnotation()仍有效;但接口的默认方法上的注解需用getMethod().getDeclaredAnnotation() - 返回
null表示没声明该注解,不是异常情况,必须判空
读取注解属性值(通过代理对象调用 getter)
反射拿到的注解实例是 JVM 生成的动态代理对象,其所有属性访问都通过 InvocationHandler 转发。不能用反射字段读取,也不能强转成实现类——唯一合法方式是直接调用注解接口定义的 getter 方法。
例如注解定义为:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
@interface MyLog { String value() default ""; int level() default 1; }那么拿到 MyLog anno = clazz.getDeclaredAnnotation(MyLog.class) 后,就写 anno.value() 和 anno.level(),不要试图用 Field.get() 去取 value 字段。
- 属性名必须和注解接口中方法名完全一致,大小写敏感
- 如果属性是数组类型(如
String[] tags()),直接调用anno.tags()返回的就是已初始化的数组,无需额外处理 - 对
Class类型属性(如Class> service()),返回的是对应类的Class对象,可继续反射操作
处理重复注解(@Repeatable 场景)
当注解被 @Repeatable(ContainerAnnotation.class) 标记后,多个同类型注解不会合并,而是被收集到容器注解里。此时 getDeclaredAnnotation(MyAnnotation.class) 只返回第一个(或 null),必须改用 getDeclaredAnnotationsByType(MyAnnotation.class)。
这个方法返回 MyAnnotation[],每个元素都是独立注解实例,可分别调用属性方法。注意它不等价于 getAnnotations() 的过滤,而是 JVM 特殊支持的重复注解提取机制。
- 容器注解(
ContainerAnnotation)本身也必须声明@Retention(RetentionPolicy.RUNTIME) -
getDeclaredAnnotationsByType()在 Java 8+ 才可用;Java 7 及之前不支持重复注解 - 即使只写了一个重复注解,
getDeclaredAnnotationsByType()仍返回长度为 1 的数组,别误以为要特殊处理单例
泛型与注解属性中的 Class 引用
注解里不能直接写泛型类型字面量(如 List<string>.class</string> 是非法的),所以常见模式是用 Class> value() 存原始类型,再靠运行时检查。但如果需要保留泛型信息,得靠额外约定:比如把泛型签名存成字符串,或配合 TypeReference 类在外部解析。
更现实的问题是:通过 anno.value() 拿到 Class 对象后,如果该类尚未加载(比如在模块隔离环境或 ClassLoader 分离场景),会触发类加载失败。这时 anno.value() 抛 ClassNotFoundException,不是注解读取失败,而是目标类不可见。
- 避免在注解中存非 public 类或包私有类的
Class引用 - 若必须引用第三方库类,确保该库在当前 ClassLoader 的 classpath 中
- 测试时容易忽略类加载器差异——单元测试用的 ClassLoader 和 Spring Boot 启动时的不一样,可能导致本地 OK、线上 NPE
@Retention 和 @Target 是否符合预期,比事后调试快得多。Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










