反射的用途

Java 反射机制是一个非常强大的功能,在很多的项目比如 Spring,MyBatis 都都可以看到反射的身影。通过反射机制,我们可以在运行期间获取对象的类型信息。利用这一点我们可以实现工厂模式和代理模式等设计模式,同时也可以解决 Java 泛型擦除等令人苦恼的问题。

把”用途”落到具体框架行为上会更直观:

  • Spring IoC:读取 XML/注解中的类名 → Class.forName 加载 → 反射构造器实例化 → 反射 setter/字段注入依赖。没有反射,容器就无法在”不知道具体类”的前提下创建对象
  • MyBatis:把 ResultSet 的列映射到 POJO 字段、把 #{param} 绑定到方法参数,全靠反射读写属性
  • JDK 动态代理:Proxy.newProxyInstance 在运行期生成类并转发调用,AOP 的基石
  • 泛型擦除补偿:运行期通过 getGenericReturnType 等取回声明时的泛型信息(Jackson 反序列化 List<User> 就依赖它)
  • 测试与工具:JUnit 反射调用测试方法;Lombok、序列化框架、IDE 提示都建立在类型 introspection 上

获取 Class 对象的四种方式

  1. 通过 getClass() 方法:已有实例时,obj.getClass()
  2. 通过 Class.forName() 方法:只有全限定类名时(配置驱动加载,会触发静态初始化)
  3. 使用 类.class:编译期已知类型时(不触发静态初始化,最轻量)
  4. 通过类加载器实现,getClassLoader():loader.loadClass(name),与 forName 的区别是默认不初始化类
Class<?> c1 = user.getClass();                 // 实例 → Class
Class<?> c2 = Class.forName("com.x.User"); // 类名 → Class(初始化)
Class<?> c3 = User.class; // 字面量 → Class(不初始化)
Class<?> c4 = User.class.getClassLoader()
.loadClass("com.x.User"); // 类加载器 → Class(不初始化)

反射能做什么:核心 API

  • 构造:clazz.getDeclaredConstructor(String.class).newInstance(...)(JDK9 起推荐,取代 Class.newInstance)
  • 方法:getDeclaredMethod(name, paramTypes) → method.invoke(obj, args)
  • 字段:field.setAccessible(true) 后可读写私有字段(注入与序列化框架的常规操作)
  • 注解:method.getAnnotation(Log.class),注解驱动的入口
  • 泛型:getGenericSuperclass / getGenericParameterTypes

实现原理:从 Class 文件到 MethodAccessor

JVM 视角下,类加载器把 Class 文件的二进制流解析为方法区的类型数据,并在堆中生成对应的 java.lang.Class 对象作为访问入口。反射调用 method.invoke 时:

  1. 权限检查(非 public 或未 setAccessible 会抛 IllegalAccessException)
  2. 获取 MethodAccessor:JDK 默认实现是委派式——前 15 次调用用 NativeMethodAccessorImpl(JNI,启动快),超过阈值(sun.reflect.inflationThreshold=15)后inflation 动态生成字节码版 DelegatingClassLoader 实现,之后调用接近直接调用速度
  3. 参数装箱/拆箱与返回值装箱带来额外开销

这解释了反射慢的两个来源:元数据解析与安全检查、基本类型装箱。也解释了为什么”反射慢”要加限定:热点路径上经过 inflation 后的反射调用开销已很小,真正贵的是首次解析与装箱。

使用注意

  • 性能:缓存 Method/Field 对象避免重复查找;热点循环慎用反射或改用 MethodHandle/LambdaMetafactory
  • 安全:setAccessible(true) 绕过封装,JDK9+ 模块系统对 JDK 内部 API 的反射访问默认拒绝(–add-opens 例外)
  • 破坏不可变性:反射改 final 字段、改枚举实例是重大隐患,序列化框架也只在必要时这么做
  • 异常语义:反射把目标异常包进 InvocationTargetException,排查时要拆 cause

反射是”把类型信息变成数据”的机制:框架因此获得了对未知类型的操作能力,代价是封装被打开、性能有边界。理解 inflation 与装箱这两个细节,就能准确判断反射在自己的代码里到底贵不贵。