← Backend / Java

22_Java 注解反射与运行时类型

反射调用、注解保留策略、泛型擦除及框架运行时边界。

Java 注解、反射与运行时类型

学习目标:理解框架如何在运行时读取类信息,以及注解、反射和泛型擦除各自的边界。基线为 Java 21。

1. 编译期类型与运行时对象

Java 源码经编译成为字节码。运行时可以通过 Class<?> 查看类名、构造器、字段和方法,也能用反射调用方法。反射(reflection)适合框架、序列化和工具,但业务代码若大量依赖字符串查找方法,重构时更容易漏掉错误。

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<String> type = String.class;
        Method method = type.getMethod("substring", int.class);
        String result = (String) method.invoke("hello", 2);
        System.out.println(result); // llo
    }
}

getMethod 查找可访问的公共方法;invoke 可能因目标方法自身抛异常而包装为 InvocationTargetException。反射调用通常比普通调用更难阅读,且依赖运行时权限与模块开放规则,不应为普通业务分支滥用。

2. 注解的三个维度

注解(annotation)是在代码元素上附加元数据。定义注解时要考虑放在哪里(@Target)、保留到何时(@Retention)、由谁解释。SOURCE 只在源码阶段,CLASS 留在字节码,RUNTIME 可在运行时经反射读取。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Audited { String action(); }

class NoteService {
    @Audited(action = "create-note")
    void create() {}
}

注解本身不会自动执行审计逻辑;必须有编译器、注解处理器或运行时框架读取它。Spring 的 @Transactional 也依赖框架代理等机制,不能把“写了注解”直接等同于“任何调用都会开启事务”。

3. 泛型擦除与框架映射

List<String> 的泛型参数主要在编译期检查;运行时不能写 value instanceof List<String>。反射可以读取某些声明位置的泛型签名,但不能从一个空 ArrayList 实例恢复实际元素类型。JSON 反序列化复杂泛型时,库可能要求显式类型描述。

4. 实践与易错点

  1. 用反射列出 NoteService 的方法与 @Audited 的 action;如果去掉 RUNTIME 保留策略,观察运行时读取结果。
  2. 解释普通方法调用、反射调用和框架代理调用分别在何处发现错误。
  3. 不要随意 setAccessible(true) 绕过封装;模块系统与安全策略可能限制访问,而且会让代码依赖内部实现。
易错点:运行时注解只是元数据;没有处理器或框架读取时,不会产生业务行为。