获取Class对象是反射读取注解的第一步,常用方式有obj.getClass()、MyClass.class和Class.forName(),三者行为不完全等价;需用isAnnotationPresent()判断存在性,getAnnotation()获取实例;遍历成员须用getDeclaredXXX()并配合setAccessible(true);@Target和@Retention配置错误会导致注解不可见。

获取 Class 对象是反射读取注解的第一步
没有 Class 对象,后续所有反射操作都无从谈起。Java 提供三种常用方式,但它们行为不完全等价:
-
obj.getClass():适用于已有实例的场景,返回运行时实际类型(可能为子类) -
MyClass.class:最常用、最安全,编译期确定,不会触发类初始化 -
Class.forName("com.example.MyClass"):需传入完整类名字符串,会触发类的静态初始化,若类不存在或初始化失败会抛ClassNotFoundException或ExceptionInInitializerError
读取注解前务必确认你拿到的是目标类的 Class 对象,而不是其父类或代理类。
用 getAnnotation() 和 isAnnotationPresent() 判断并提取注解
这两个方法是读取注解的核心入口,但用途和性能有明显差异:
-
isAnnotationPresent(MyAnnotation.class):只做存在性检查,开销极小,适合做条件分支(比如“如果有 @LogExecutionTime 就记录耗时”) -
getAnnotation(MyAnnotation.class):返回注解实例,可调用其方法获取属性值;如果注解不存在,返回null,不是异常 - 注意:
getAnnotation()只能读取@Retention(RetentionPolicy.RUNTIME)的注解;CLASS或SOURCE级注解在运行时已不可见
示例:
Class<?> clazz = UserService.class;
if (clazz.isAnnotationPresent(Service.class)) {
Service service = clazz.getAnnotation(Service.class);
System.out.println("Bean name: " + service.value());
}
遍历方法和字段时,别漏掉 getDeclaredXXX() 和 setAccessible()
默认的 getMethod() / getField() 只返回 public 成员,且无法访问私有注解目标。要完整扫描,必须用 getDeclaredMethod() 和 getDeclaredField():
-
getDeclaredMethods()返回本类声明的所有方法(含 private),但不含继承的方法 -
getDeclaredFields()同理,返回本类所有字段,包括 private、protected - 若目标字段/方法是
private,调用setAccessible(true)才能绕过 Java 访问控制 —— 否则读取注解本身不会报错,但后续尝试读取字段值或调用方法时会抛IllegalAccessException
常见错误:写了 clazz.getMethods() 却发现自定义注解没被识别,其实是注解贴在 private 方法上,而 getMethods() 根本没返回它。
@Target 和 @Retention 不匹配会导致注解“消失”
这是最隐蔽也最容易忽略的问题。即使代码里明明白白写了 @MyAnnotation,反射也读不到,大概率是元注解配置错了:
- 如果注解定义时用了
@Target(ElementType.METHOD),却把它贴在类上,编译器会报错,根本过不了编译 - 如果用了
@Retention(RetentionPolicy.CLASS),那在 JVM 运行时getAnnotation()一定返回null,因为字节码里虽然保留了,但运行时 Class 对象不加载这部分信息 - 还有一种情况:注解本身没问题,但你用的类加载器不是定义注解的那个(比如 OSGi 或某些热部署环境),
MyAnnotation.class在反射时和目标类看到的不是同一个 Class 实例,导致isAnnotationPresent()返回 false
验证方式很简单:在注解定义处确认 @Target 是否覆盖你的使用位置,且 @Retention 必须是 RUNTIME —— 其他两个策略对反射无效。

















