1258 字
6 分钟
java反射机制

反射机制#

1. 反射是什么#

反射(Reflection)是指程序在运行时获取自身结构信息(类、方法、字段、构造器等)并对其进行操作的能力。

一句话本质:反射把”编译时确定调用目标”变成了”运行时动态确定调用目标”,代价是牺牲了编译期类型检查和一部分性能,换来的是极高的灵活性

理解这句话你就能明白,为什么上一节讲的”反序列化重建对象不调用构造器”能实现——那正是反射体系里 sun.reflect.ReflectionFactory 提供的特殊能力。反射不只是”调用一个方法”这么简单,它还包括绕过语言层面限制、动态创建对象等一整套运行时操作 JVM 的手段。

2. 为什么需要#

  • 框架解耦:框架作者在编译时不可能知道使用者会写出什么类,反射让框架能够”发现”并操作使用者提供的任意类型(比如 Spring 的依赖注入,本质是反射扫描并调用构造器/setter)。

  • 动态性:插件化架构、SPI 机制、ORM 字段映射,都依赖运行时按名字找到类/方法并调用。

  • 访问私有成员的能力:这本不是反射诞生的初衷(反射最初是为了支持 IDE、调试器、序列化框架这类”元编程”工具),但正是这个能力,让反射成了安全领域绕过封装的核心手段。

3. 核心机制拆解#

3.1 反射的入口 对象#

java

Class<?> clazz = Class.forName("java.lang.Runtime");

Class 对象是反射体系的起点——每个被加载的类,在方法区(元空间)里都有唯一对应的一个 Class 实例,你可以通过 .class 字面量、Object#getClass()、Class.forName(String) 三种方式拿到它。

值得注意的是 Class.forName 有个重载:

java

Class.forName(String name, boolean initialize, ClassLoader loader)

第二个参数 initialize 决定了拿到 Class 对象的同时是否触发类的初始化(即执行静态代码块、给静态变量赋非零初值)。这个参数在安全利用链里经常被显式指定为 true,因为攻击者往往就是要借助 Class.forName 顺带触发一次类初始化

3.2 Method、Field、Constructor:反射对象也是对象#

Method m = clazz.getMethod("exec", String.class);
Object result = m.invoke(runtimeInstance, "whoami");

Method、Field、Constructor本身也是普通的 Java 对象(分别是 java.lang.reflect.Method 等类的实例),它们持有对目标类、目标成员的元信息引用。这意味着:

  • 你可以把一个 Method 对象像普通对象一样传递、存储、序列化

  • getMethod 和 getDeclaredMethod 的区别:前者只能拿到 public 方法(包括继承来的),后者能拿到当前类自己声明的所有方法(不管访问修饰符),但不包括继承的。这个区别在写利用链/绕过某些防御检测时经常用到——很多防御代码只检查了 getMethod 能拿到的方法名。

3.3 setAccessible(true):绕的是谁的检查#

这是这一节的核心,也是安全视角下最值得深挖的一点。

Field f = obj.getClass().getDeclaredField("value");
f.setAccessible(true);
f.set(obj, "modified");

setAccessible绕过的是 Java 语言规范层面的访问控制检查,这个检查原本是由Reflection#getCallerClass结合AccessibleObject内部的override标志位来做的,并不是 JVM 字节码校验器层面的检查。也就是说:

  • private/protected/包私有 这些访问修饰符,本质上是编译器和反射 API 在语言层面自愿遵守的约定,而不是 JVM 执行引擎层面不可逾越的硬限制

  • setAccessible(true) 做的事情,就是把 AccessibleObject 内部一个叫 override 的 boolean 字段设为 true,后续该反射对象在 invoke/get/set 时,会跳过 Reflection.ensureMemberAccess 这一层检查

3.4 MethodAccessor:反射调用的两种实现路径#

Method.invoke() 的调用不是直接走 native 方法完事的,它内部委托给一个 MethodAccessor 接口的实现,而这个实现有两种,JVM 会动态切换:

  1. NativeMethodAccessorImpl:底层直接调用 JVM 的 native 方法(invoke0),每次反射调用都要经过 native 层的一次”翻译”,有比较固定的开销(参数装箱/拆箱、访问检查等)。

  2. GeneratedMethodAccessorXXX:这是运行时动态生成的一个类,它不走 native 层,而是用直接的 Java 字节码去调用目标方法,相当于把一次反射调用”编译”成了一次接近原生的方法调用,性能远高于第一种方式。

3.5 JPMS(模块化)之后的变化#

JDK 9 引入模块系统后,setAccessible 不再万能了。

  • 模块之间默认是强封装的:一个模块的包,除非在 module-info.java 里显式 exports 或 opens,否则模块外部即使用反射也无法访问其内部类的非公开成员。

  • 对于 exports 的包:外部可以正常反射调用 public 成员,但 setAccessible(true) 访问其私有成员依然会被拒绝(抛 InaccessibleObjectException),除非该包被 opens。

  • 对于 opens 的包(深度开放):setAccessible 才能正常工作。

java反射机制
https://lab404.cn/posts/java-reflection/
作者
见山
发布于
2026-07-19
许可协议
CC BY-NC-SA 4.0