运行时反射读取自定义注解需满足三要素:声明必须加@retention(runtime);扫描用getdeclaredxxx()并setaccessible(true);取值只能调用注解方法而非反射字段,且须判空处理。

要在运行时通过反射读取并解析自定义注解标记的业务属性,核心是确保注解“能被读到、定位准、取值稳”。这三步缺一不可,且全程不侵入业务逻辑,只靠“打标签 + 扫描 + 解析”完成动态行为注入。
注解声明必须带 RUNTIME 保留策略
这是前提中的前提。若没加 @Retention(RetentionPolicy.RUNTIME),JVM 在加载类时根本不会把注解信息保留在运行时内存中,反射调用 getAnnotation() 永远返回 null。
- 必须显式声明:
@Retention(RetentionPolicy.RUNTIME) - 配合 @Target 明确作用位置,比如字段注入用
@Target(ElementType.FIELD),组件注册用@Target(ElementType.TYPE) - 属性类型尽量精简:推荐
String、int、boolean、Class>或String[];避免嵌套对象或泛型集合,否则解析易出错
反射扫描要精准获取目标元素
不能依赖 getFields() 或 getMethods() ——它们只返回 public 成员,且无法访问私有字段上的注解。正确做法是直接操作声明级(declared)成员:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 查类注解:用
clazz.getDeclaredAnnotation(MyConfig.class)(比getAnnotation()更准确,不沿继承链查找) - 查字段注解:用
clazz.getDeclaredFields()获取全部字段(含 private),再逐个调用field.isAnnotationPresent(MyValue.class)判断 - 对私有字段操作前,必须先
field.setAccessible(true),否则后续读注解或设值都会失败 - 跳过
static或final字段——反射无法为它们赋值,需提前过滤
安全读取注解属性值
拿到注解实例后,不能用反射字段方式取值(如 Field.get()),JVM 返回的是代理对象,唯一合法方式是调用其接口方法:
- 直接调用
anno.value()、anno.timeout()等方法名,大小写必须与注解定义完全一致 - 数组属性(如
String[] roles())直接anno.roles()即可,返回已初始化数组,无需额外处理 -
Class>类型属性(如Class extends Processor> handler())返回的就是对应 Class 对象,可继续反射调用 - 始终判空:
if (anno != null) { ... },因为未声明注解时返回 null 是正常行为,不是异常
处理重复注解(@Repeatable 场景)
当一个元素上允许标注多个同类型注解时(如日志切面多次配置),需用专用 API:
- 注解需用
@Repeatable(Container.class)标记,容器注解本身也要带@Retention(RUNTIME) - 不能用
getDeclaredAnnotation(),它只返回第一个或 null - 改用
element.getDeclaredAnnotationsByType(MyLog.class),返回注解实例数组
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










