# 反射机制
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
Java 反射(Reflection)机制允许在运行时动态获取类的信息或操作对象属性、方法、构造器等。
反射为框架设计、插件机制、IoC 容器和动态代理提供了基础能力,但也带来了性能和安全上的考量。本篇将系统梳理反射流程、性能损耗、优化策略及应用示例。
# 一、基本反射流程
核心流程示例
Class<?> clazz = Class.forName("com.example.User");
// 获取构造方法并创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object user = constructor.newInstance("张三", 18);
// 获取字段
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
Object name = field.get(user);
// 获取方法并调用
Method method = clazz.getDeclaredMethod("getName");
method.setAccessible(true);
Object result = method.invoke(user);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
内部过程分析
Class.forName():通过类加载器加载.class字节码。getDeclaredField/getDeclaredMethod:通过类的 内部反射缓存 查找字段/方法。setAccessible(true):调用ReflectionFactory提升私有访问权限,绕过 Java 安全检查(在 JDK 9+ 会触发强警告)。invoke():方法反射调用,会封装为MethodAccessor并通过DelegatingMethodAccessorImpl分发。Method.invoke() ⬇ | |--> DelegatingMethodAccessorImpl.invoke() ⬇ | |--> NativeMethodAccessorImpl.invoke()(第一次调用) | |--> 调用本地代码执行实际的方法(通过 JNI 反射机制) | |--> 达到一定调用次数后(默认15次),切换为: ⬇ MethodAccessorGenerator 生成字节码 ⬇ --> GeneratedMethodAccessorXXX.invoke()(JDK动态生成类)1
2
3
4
5
6
7
8
9
10
11
12
13
# 二、性能损耗分析
Java 反射提供了极大的灵活性,但其本质仍是“间接方法调用”,相比直接调用,其性能损耗主要来源于访问控制、方法查找、调用封装、JIT 逃逸等问题。
# 为什么 Method.invoke() 会慢?
调用是通过泛型接口完成的,缺乏编译器内联优化
Method.invoke()的参数和返回值都是Object类型,JVM 无法进行静态分析和内联优化。public Object invoke(Object obj, Object... args)1基本类型要装箱(boxing)和拆箱(unboxing):
int result = (Integer) method.invoke(obj, 1); // int ➜ Integer ➜ Object,再反向拆箱1泛型擦除导致类型不安全(编译器无法内联优化调用路径)。
反射调用流程层级多,涉及委托机制
如前所述,
Method.invoke()实际上调用的是MethodAccessor接口的invoke方法,默认由DelegatingMethodAccessorImpl委托给NativeMethodAccessorImpl执行(前15次),再转为动态生成的GeneratedMethodAccessorImpl。虽然后续使用字节码生成器优化路径,但整个委托链和对象适配依然存在开销。
方法元信息查找耗时
虽然 JVM 为
Method对象缓存了元数据(通过MethodAccessor),但初次反射依然需要查找方法、参数签名、访问控制等元信息,构建成本较高。参数检查、类型转换和异常处理逻辑复杂
invoke()方法内部会逐个参数进行校验、类型强制转换。- 反射调用的异常是被统一包裹成
InvocationTargetException抛出,异常处理开销更高。
# JVM 的反射优化
为了提高反射性能,JVM 在反射调用中加入了缓存机制和动态字节码生成策略:
| 阶段 | 执行路径 | 特点 |
|---|---|---|
| 初始阶段(前15次) | NativeMethodAccessorImpl | 调用 native 方法,慢但安全 |
| 达到阈值后 | GeneratedMethodAccessorImpl | 动态生成类调用真实方法,绕过 native 和参数适配,提升性能 |
| 委托统一入口 | DelegatingMethodAccessorImpl | 委托至上面两个 |
☑️ 这些逻辑封装在 Method 类的 methodAccessor 字段中,一经初始化会缓存避免重复生成。
# setAccessible(true) 的影响
作用:跳过安全性检查
默认情况下,Java 运行时会通过
checkAccess()检查方法/字段的访问权限(public/protected/private),这会导致额外的权限校验和安全管理器检查:if (!override && !Reflection.quickCheckMemberAccess(clazz, modifiers)) { // 安全检查... }1
2
3调用
setAccessible(true)后,override标志被设置为 true,可直接访问字段/方法,省略这一块的消耗。是否能提高调用性能?
仅影响访问权限检查阶段的性能,调用执行阶段依然使用反射机制,不会减少装箱拆箱、方法封装、异常捕获等成本。
在低频反射场景下提升不明显,但在 高频调用反射私有字段或方法 时能降低开销。
从 JDK 9 起,如果使用模块系统,
setAccessible(true)会受到强限制,甚至抛出异常或警告。
# 三、MethodHandle 与反射对比
概述
MethodHandle 是 Java 7 引入的低层次方法引用机制,是 JDK 内部实现 Lambda、Stream 的基础。
- 它是 JVM 级别的直接调用接口,性能比反射高得多。
- 能被 JIT 编译优化为 几乎等同于直接调用的性能。
示例:通过 MethodHandle 调用方法
import java.lang.invoke.*;
public class MethodHandleDemo {
public static class User {
public String sayHello(String name) {
return "Hello, " + name;
}
}
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(User.class, "sayHello",
MethodType.methodType(String.class, String.class));
User user = new User();
String result = (String) mh.invokeExact(user, "张三");
System.out.println(result);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 四、典型应用:插件机制
反射是 面向接口+动态发现能力 的重要基础。在构建动态插件、框架或 IoC 容器时非常关键。
**示例:**插件注册与加载(简化版)
// 插件接口
public interface Plugin {
void execute();
}
// SPI 加载机制
public class PluginLoader {
public static void loadPlugins() throws Exception {
File pluginDir = new File("plugins/");
for (File file : pluginDir.listFiles()) {
URLClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()});
Class<?> clazz = loader.loadClass("com.plugin.MyPlugin");
if (Plugin.class.isAssignableFrom(clazz)) {
Plugin plugin = (Plugin) clazz.getDeclaredConstructor().newInstance();
plugin.execute();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SPI 打破了类加载的双亲委派机制, 具体可参考JVM相关类容
# 五、性能优化策略
| 优化点 | 说明 |
|---|---|
缓存 Method/Field 对象 | 使用 ConcurrentHashMap 缓存反射信息,避免重复查找 |
避免频繁 setAccessible(true) | 设置一次即可,多次设置无意义反而浪费性能 |
使用 MethodHandle 替代反射 | 高性能反射调用方式,支持 JIT 优化 |
| 使用动态代理(如 ByteBuddy) | 生成字节码代理,性能几乎等同手写代码 |
| 控制反射调用频率 | 核心路径禁止频繁反射,比如高频请求、日志埋点等 |
| 基于注解构建编译期处理器 | 比如 Spring + AnnotationProcessor,编译期生成代码 |
# 六、常用工具类库
| 类库 | 作用说明 |
|---|---|
Spring ReflectionUtils | 包装常用反射逻辑,简化代码编写 |
Apache BeanUtils | Bean 属性映射、动态复制 |
| ASM / ByteBuddy | 生成字节码,实现零反射的高性能代理 |
| MapStruct | 静态代码生成器,避免反射 |
JDK MethodHandle | 几乎等价于直接调用的反射方式 |