反射机制与动态代理
# 26.反射机制与动态代理
# 目录介绍
# 7.1 开篇疑问
疑惑:Java 反射号称能"运行时操作任意类",它是怎么做到的?性能开销到底有多大?Spring 的 AOP 是怎么在方法执行前后"插入"逻辑的?JDK 动态代理和 CGLIB 有什么区别?为什么 JDK 9 之后反射受到了限制?
答疑:反射和动态代理是 Java 框架体系的两大基石。Spring IoC 用反射创建 Bean,Spring AOP 用动态代理实现切面,MyBatis 用动态代理把接口变成实现类。理解它们的底层原理,就能理解整个 Java 框架生态的运行机制。
# 7.2 反射的本质
# 7.2.1 什么是反射
反射(Reflection)是指程序在运行时能够获取类的信息并操作类或对象的能力。正常写代码是"编译时确定类型",反射是"运行时确定类型"。
// 正常方式——编译时确定
User user = new User();
user.setName("张三");
// 反射方式——运行时确定
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod("setName", String.class);
method.invoke(obj, "张三");
2
3
4
5
6
7
8
9
反射的核心能力:
- 运行时获取类信息:字段、方法、构造器、注解、泛型参数
- 运行时创建对象:不需要
new,通过 Class 对象构造 - 运行时调用方法:不需要编译时知道方法名
- 运行时访问/修改字段:包括私有字段
# 7.2.2 获取Class对象的三种方式
// 方式1:类名.class(编译期确定)
Class<User> c1 = User.class;
// 方式2:对象.getClass()(运行期确定)
User user = new User();
Class<?> c2 = user.getClass();
// 方式3:Class.forName()(运行期动态加载)
Class<?> c3 = Class.forName("com.example.User");
// 三种方式获取的是同一个Class对象
System.out.println(c1 == c2 && c2 == c3); // true
2
3
4
5
6
7
8
9
10
11
12
三种方式的区别与适用场景:
| 方式 | 触发类加载 | 触发初始化 | 适用场景 |
|---|---|---|---|
类名.class | 否(已加载) | 否 | 编译时已知类型,获取类元信息 |
对象.getClass() | 否(已实例化) | 否 | 已有对象实例,运行时获取实际类型 |
Class.forName() | 是 | 是(默认) | 运行时动态加载,如 JDBC 驱动 |
// Class.forName 可以控制是否触发初始化
// 第二个参数 false 表示不初始化(不执行 static 块)
Class<?> c = Class.forName("com.example.User", false,
Thread.currentThread().getContextClassLoader());
2
3
4
# 7.2.3 反射的核心API
| API | 功能 | 示例 |
|---|---|---|
getConstructor | 获取公共构造方法 | 创建实例 |
getDeclaredConstructor | 获取所有构造方法(含私有) | 创建实例 |
getMethod | 获取公共方法(含继承) | 调用方法 |
getDeclaredMethod | 获取本类声明的方法(含私有,不含继承) | 调用方法 |
getField | 获取公共字段(含继承) | 读写字段 |
getDeclaredField | 获取本类声明的字段(含私有) | 读写字段 |
setAccessible(true) | 暴力访问私有成员 | 突破封装 |
// 反射访问私有字段
Class<?> clazz = User.class;
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 突破 private 限制
User user = new User();
nameField.set(user, "张三");
String name = (String) nameField.get(user);
2
3
4
5
6
7
get 系列与 getDeclared 系列的区别:
public class Parent {
public int publicField;
private int privateField;
public void publicMethod() {}
private void privateMethod() {}
}
public class Child extends Parent {
public int childField;
private int childPrivateField;
}
// getFields():返回所有公共字段(含继承的)
// → [publicField, childField]
Child.class.getFields();
// getDeclaredFields():返回本类声明的所有字段(含私有,不含继承)
// → [childField, childPrivateField]
Child.class.getDeclaredFields();
// getMethods():返回所有公共方法(含继承的,含 Object 的)
// → [publicMethod, childMethod, equals, hashCode, toString, ...]
Child.class.getMethods();
// getDeclaredMethods():返回本类声明的所有方法(含私有,不含继承)
Child.class.getDeclaredMethods();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 7.2.4 Class对象的本质与存储
每个类被加载后,JVM 会在堆内存中创建一个 java.lang.Class 对象,作为该类在方法区中类数据的访问入口。
类加载过程:
.class 文件 → 类加载器读取字节码
→ 方法区存储:类的元数据(字段描述、方法描述、常量池等)
→ 堆中创建:java.lang.Class 对象
→ Class 对象持有指向方法区类数据的指针
2
3
4
5
Class 对象的关键特性:
- 唯一性:同一个类加载器加载的同一个类,只有一个 Class 对象
- 不可实例化:Class 的构造器是 private 的,只有 JVM 能创建
- 数据源:Class 对象是反射操作的数据来源,所有反射 API 都从它出发
// 验证 Class 对象的唯一性
Class<?> c1 = String.class;
Class<?> c2 = "hello".getClass();
Class<?> c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2); // true
System.out.println(c2 == c3); // true
// Class 对象存储在堆中
// JDK 8+ 类的元数据存储在元空间(Metaspace,本地内存)
// Class 对象本身仍在堆中,是元数据的"入口"
2
3
4
5
6
7
8
9
10
Class 对象的内部结构(简化):
public final class Class<T> {
// 缓存反射数据,避免每次都解析
private volatile transient SoftReference<ReflectionData<T>> reflectionData;
// 反射数据缓存
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// ... 更多缓存
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
设计思考:反射数据用 SoftReference 缓存——内存充足时缓存加速,内存不足时可被 GC 回收。这是性能与内存的平衡。
# 7.3 反射的底层实现
# 7.3.1 Method.invoke的调用链
method.invoke(obj, args)
→ Method.invoke()
→ 安全检查(checkAccess)
→ MethodAccessor.invoke()
→ NativeMethodAccessorImpl.invoke() (前15次)
→ native 方法,通过 JNI 调用
→ GeneratedMethodAccessorXxx.invoke() (第16次起)
→ 动态生成的字节码,直接调用目标方法
2
3
4
5
6
7
8
// Method.invoke 源码核心逻辑
public Object invoke(Object obj, Object... args) throws ... {
// 1. 安全检查(如果未 setAccessible(true))
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
// 2. 获取 MethodAccessor(延迟创建)
MethodAccessor ma = methodAccessor;
if (ma == null) {
ma = acquireMethodAccessor();
}
// 3. 委托给 MethodAccessor 执行
return ma.invoke(obj, args);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 7.3.2 从Native到字节码生成
JVM 对反射调用做了"膨胀"优化(Inflation):
- 前 15 次调用:通过 Native 方法实现,启动快但每次调用有 JNI 开销
- 第 16 次起:JVM 动态生成字节码类
GeneratedMethodAccessorXxx,直接调用目标方法,性能接近直接调用
// 通过 -Dsun.reflect.inflationThreshold=15 控制阈值
// 通过 -Dsun.reflect.noInflation=true 可以直接生成字节码(跳过Native阶段)
2
膨胀机制的源码实现:
// NativeMethodAccessorImpl 核心逻辑
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private int numInvocations;
public Object invoke(Object obj, Object[] args) {
// 调用次数达到阈值,生成字节码替换
if (++numInvocations > ReflectionFactory.inflationThreshold()) {
// 动态生成字节码类
MethodAccessorImpl acc =
(MethodAccessorImpl) new MethodAccessorGenerator()
.generateMethod(/* 目标方法信息 */);
// 替换委托对象
parent.setDelegate(acc);
}
// 通过 JNI 调用
return invoke0(method, obj, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
生成的字节码类示例(反编译):
// JVM 动态生成的 GeneratedMethodAccessor1
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public Object invoke(Object obj, Object[] args) {
// 直接调用目标方法,无需 JNI
((UserService) obj).save((String) args[0]);
return null;
}
}
2
3
4
5
6
7
8
设计思考:为什么不一开始就生成字节码?因为生成字节码有开销(需要动态生成类、加载类),如果反射只调用一两次,Native 方式更快。只有高频调用才值得"投资"生成字节码。这是延迟优化的经典设计。
# 7.3.3 反射为什么慢
- 无法内联优化:JIT 编译器无法对反射调用做方法内联,因为调用目标在运行时才确定
- 装箱拆箱:
invoke(Object obj, Object... args)参数是 Object,基本类型需要装箱 - 安全检查:每次调用都要检查访问权限(
setAccessible(true)可以跳过部分检查) - 缓存失效:反射调用的目标不确定,CPU 分支预测和缓存命中率低
- 数组创建:
Object... args变长参数每次都会创建新数组
性能对比(调用 100 万次):
| 方式 | 耗时 | 倍数 |
|---|---|---|
| 直接调用 | ~3ms | 1x |
| 反射(带 setAccessible) | ~50ms | ~17x |
| 反射(不带 setAccessible) | ~100ms | ~33x |
| MethodHandle | ~5ms | ~1.7x |
详细基准测试代码:
public class ReflectBenchmark {
private static final int ITERATIONS = 1_000_000;
public static void main(String[] args) throws Exception {
UserService service = new UserService();
Method method = UserService.class.getMethod("doWork");
method.setAccessible(true);
// 预热,触发 Inflation
for (int i = 0; i < 20; i++) {
method.invoke(service);
}
// 1. 直接调用
long start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
service.doWork();
}
System.out.println("直接调用: " + (System.nanoTime() - start) / 1_000_000 + "ms");
// 2. 反射调用
start = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
method.invoke(service);
}
System.out.println("反射调用: " + (System.nanoTime() - start) / 1_000_000 + "ms");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 7.3.4 反射优化实战
优化1:缓存 Class/Method/Field 对象
// 错误:每次都查找 Method
public void badReflect(Object obj) throws Exception {
// getDeclaredMethod 每次都要遍历方法列表,开销大
Method m = obj.getClass().getDeclaredMethod("doWork");
m.setAccessible(true);
m.invoke(obj);
}
// 正确:缓存 Method 对象
private static final Map<Class<?>, Method> METHOD_CACHE = new ConcurrentHashMap<>();
public void goodReflect(Object obj) throws Exception {
Method m = METHOD_CACHE.computeIfAbsent(obj.getClass(), clazz -> {
try {
Method method = clazz.getDeclaredMethod("doWork");
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
m.invoke(obj);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
优化2:setAccessible(true) 跳过安全检查
Method method = clazz.getDeclaredMethod("doWork");
// 设置为 true 后,每次 invoke 都跳过访问权限检查
// 能提升约 50% 的反射调用性能
method.setAccessible(true);
2
3
4
优化3:使用 MethodHandle 替代反射
// MethodHandle(JDK 7+)性能接近直接调用
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(
UserService.class, "doWork", MethodType.methodType(void.class));
// 调用方式
mh.invoke(service); // 通用调用,有类型检查
mh.invokeExact(service); // 精确调用,无额外检查,最快
2
3
4
5
6
7
8
优化4:使用 LambdaMetafactory 动态生成函数式接口实现
// 终极优化:编译时不知道方法名,但运行时性能接近直接调用
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(
UserService.class, "doWork", MethodType.methodType(void.class));
// 动态生成函数式接口实现
CallSite site = LambdaMetafactory.metafactory(
lookup,
"run",
MethodType.methodType(Runnable.class, UserService.class),
MethodType.methodType(void.class),
mh,
MethodType.methodType(void.class)
);
// 获取函数式接口实例(只需创建一次)
Runnable invoker = (Runnable) site.getTarget().invoke(service);
// 后续调用完全无反射开销
invoker.run();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 7.3.5 MethodHandle与反射对比
JDK 7 引入的 java.lang.invoke.MethodHandle 是反射的"轻量级替代品":
| 特性 | 反射(Reflection) | MethodHandle |
|---|---|---|
| 引入版本 | JDK 1.1 | JDK 7 |
| 性能 | 较慢(15次后优化) | 接近直接调用 |
| JIT 优化 | 难以内联 | 可以内联优化 |
| 安全检查 | 每次调用都检查(除非setAccessible) | 创建时检查一次 |
| 类型安全 | 运行时检查 | MethodType 类型安全 |
| 权限模型 | 基于 Class 的访问权限 | 基于 Lookup 的访问权限 |
| 主要用途 | 框架、工具 | invokedynamic、lambda |
// MethodHandle 的核心优势:JIT 可以将其内联
// 因为 MethodHandle 在创建时就确定了目标方法
// JIT 编译器可以"看穿" MethodHandle,直接内联目标方法
MethodHandle mh = lookup.findVirtual(
String.class, "length", MethodType.methodType(int.class));
// JIT 可以将下面的调用优化为:直接调用 str.length()
int len = (int) mh.invokeExact("hello");
2
3
4
5
6
7
8
9
# 7.4 动态代理原理剖析
# 7.4.1 代理模式回顾
代理模式:不直接访问目标对象,而是通过一个代理对象间接访问,代理可以在调用前后增加额外逻辑。
Client → Proxy → RealSubject
↓
前置逻辑(日志/权限)
调用目标方法
后置逻辑(监控/事务)
2
3
4
5
静态代理的局限:
// 静态代理:为每个接口手写代理类
public class UserServiceProxy implements UserService {
private UserService target;
@Override
public void save(String name) {
System.out.println("[LOG] before save");
target.save(name); // 委托给真实对象
System.out.println("[LOG] after save");
}
@Override
public void delete(int id) {
System.out.println("[LOG] before delete");
target.delete(id); // 每个方法都要手写
System.out.println("[LOG] after delete");
}
// 100个方法就要写100遍...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
问题:接口方法一多,代理类的代码量爆炸;接口变动时,代理类也要跟着改。动态代理就是为了解决这个问题。
# 7.4.2 JDK动态代理实现
JDK 动态代理基于接口,运行时动态生成代理类:
public interface UserService {
void save(String name);
void delete(int id);
}
public class UserServiceImpl implements UserService {
public void save(String name) {
System.out.println("保存用户: " + name);
}
public void delete(int id) {
System.out.println("删除用户: " + id);
}
}
// InvocationHandler 定义代理逻辑
public class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("[LOG] 方法开始: " + method.getName());
Object result = method.invoke(target, args); // 调用真实对象
long cost = System.currentTimeMillis() - start;
System.out.println("[LOG] 方法结束: " + method.getName() + ", 耗时: " + cost + "ms");
return result;
}
}
// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LogHandler(new UserServiceImpl())
);
proxy.save("张三");
// [LOG] 方法开始: save
// 保存用户: 张三
// [LOG] 方法结束: save, 耗时: 0ms
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 7.4.3 Proxy类的生成过程
Proxy.newProxyInstance() 内部做了什么?
// Proxy.newProxyInstance 源码核心逻辑(简化)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 1. 安全检查
Objects.requireNonNull(h);
// 2. 查找或生成代理类(核心!)
Class<?> cl = getProxyClass0(loader, interfaces);
// 3. 获取代理类的构造器(参数为 InvocationHandler)
Constructor<?> cons = cl.getConstructor(InvocationHandler.class);
// 4. 创建代理实例
return cons.newInstance(h);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
代理类的生成过程(getProxyClass0 内部):
1. 检查缓存:WeakCache<ClassLoader, Class<?>[], Class<?>>
→ 命中缓存:直接返回
→ 未命中:进入生成流程
2. 验证接口:
→ 接口是否为真正的接口(不是类)
→ 接口是否可通过给定类加载器访问
→ 接口是否有重复
3. 生成代理类名:com.sun.proxy.$Proxy0, $Proxy1, $Proxy2...
→ 如果接口包含非 public 接口,代理类与该接口同包
4. 生成代理类字节码:ProxyGenerator.generateProxyClass()
→ 遍历所有接口方法
→ 为每个方法生成代理逻辑
→ 同时代理 Object 的 hashCode/equals/toString
5. 加载代理类:使用传入的 ClassLoader 加载字节码
6. 放入缓存
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 7.4.4 反编译$Proxy0完整源码
通过 ProxyGenerator.generateProxyClass() 可以将生成的字节码保存为文件,反编译后如下:
// JVM 动态生成的 $Proxy0(完整版)
public final class $Proxy0 extends Proxy implements UserService {
// 每个被代理方法对应一个 static Method 字段
private static Method m0; // hashCode
private static Method m1; // equals
private static Method m2; // toString
private static Method m3; // save
private static Method m4; // delete
// 静态初始化:获取 Method 对象
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.example.UserService").getMethod("save", Class.forName("java.lang.String"));
m4 = Class.forName("com.example.UserService").getMethod("delete", Integer.TYPE);
} catch (Exception e) {
throw new NoSuchMethodError(e.getMessage());
}
}
// 构造器:接收 InvocationHandler
public $Proxy0(InvocationHandler h) {
super(h);
}
// 代理 save 方法
@Override
public final void save(String name) {
try {
// 所有方法调用都转发给 InvocationHandler
this.h.invoke(this, m3, new Object[]{name});
} catch (Throwable t) {
if (t instanceof RuntimeException) throw (RuntimeException) t;
if (t instanceof Error) throw (Error) t;
throw new UndeclaredThrowableException(t);
}
}
// 代理 delete 方法
@Override
public final void delete(int id) {
try {
this.h.invoke(this, m4, new Object[]{Integer.valueOf(id)});
} catch (Throwable t) {
if (t instanceof RuntimeException) throw (RuntimeException) t;
if (t instanceof Error) throw (Error) t;
throw new UndeclaredThrowableException(t);
}
}
// 代理 hashCode(Object 方法)
@Override
public final int hashCode() {
try {
return (Integer) this.h.invoke(this, m0, null);
} catch (Throwable t) {
if (t instanceof RuntimeException) throw (RuntimeException) t;
if (t instanceof Error) throw (Error) t;
throw new UndeclaredThrowableException(t);
}
}
// equals 和 toString 类似...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
从反编译中可以看出:
- 代理类继承了
Proxy(所以 JDK 代理只能代理接口,因为 Java 单继承) - 每个方法内部都调用
InvocationHandler.invoke() - Method 对象在
static {}中初始化一次,后续调用不再反射查找 - 基本类型参数会自动装箱(
int→Integer.valueOf(id))
如何保存代理类字节码:
// JDK 8
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 11+
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
// 或手动生成并保存
byte[] classFile = ProxyGenerator.generateProxyClass(
"$Proxy0", new Class[]{UserService.class});
Files.write(Paths.get("$Proxy0.class"), classFile);
2
3
4
5
6
7
8
9
10
# 7.4.5 CGLIB动态代理
CGLIB 基于继承,通过生成目标类的子类来实现代理,不需要接口:
public class UserServiceImpl { // 注意:没有实现接口
public void save(String name) {
System.out.println("保存用户: " + name);
}
public final void finalMethod() {
System.out.println("final方法不能被代理");
}
}
// CGLIB 代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[LOG] 前置: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("[LOG] 后置: " + method.getName());
return result;
}
});
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.save("张三");
// [LOG] 前置: save
// 保存用户: 张三
// [LOG] 后置: save
proxy.finalMethod();
// final方法不能被代理——直接调用原方法,不经过拦截器
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CGLIB 生成的代理类结构:
// CGLIB 生成的代理类(简化)
public class UserServiceImpl$$EnhancerByCGLIB$$xxx extends UserServiceImpl {
private MethodInterceptor callback;
// CGLIB 为每个方法生成两个方法
// 1. 代理方法(重写父类方法)
@Override
public void save(String name) {
if (callback != null) {
callback.intercept(this, saveMethod, new Object[]{name}, saveMethodProxy);
} else {
super.save(name);
}
}
// 2. FastClass 调用方法(直接调用父类,避免反射)
final void CGLIB$save$0(String name) {
super.save(name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 7.4.6 CGLIB的FastClass机制
CGLIB 的性能优势来源于 FastClass 机制——避免反射调用:
// JDK 代理调用真实方法:通过反射
method.invoke(target, args); // 反射调用,有安全检查和 JNI 开销
// CGLIB 调用真实方法:通过 FastClass + 方法索引
methodProxy.invokeSuper(obj, args); // 非反射,直接调用
2
3
4
5
FastClass 的原理:
// CGLIB 为目标类和代理类各生成一个 FastClass
// FastClass 通过方法索引(int)直接调用方法,不走反射
public class UserServiceImpl$$FastClassByCGLIB$$xxx extends FastClass {
// 根据方法签名返回索引
public int getIndex(String methodName, Class[] paramTypes) {
if ("save".equals(methodName) && paramTypes.length == 1) return 0;
if ("delete".equals(methodName) && paramTypes.length == 1) return 1;
return -1;
}
// 根据索引直接调用方法(switch-case,无反射)
public Object invoke(int index, Object obj, Object[] args) {
UserServiceImpl target = (UserServiceImpl) obj;
switch (index) {
case 0: target.save((String) args[0]); return null;
case 1: target.delete((Integer) args[0]); return null;
}
throw new IllegalArgumentException("Invalid index");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
空间换时间:FastClass 为每个方法生成了一个索引,通过 switch-case 直接分发,避免了反射调用。代价是生成更多的类文件,占用更多的元空间内存。
# 7.4.7 JDK代理与CGLIB深度对比
| 特性 | JDK 动态代理 | CGLIB |
|---|---|---|
| 前提 | 目标类必须实现接口 | 不需要接口 |
| 原理 | 实现接口 + InvocationHandler | 继承目标类 + MethodInterceptor |
| 限制 | 只能代理接口方法 | 不能代理 final 类/方法 |
| 方法调用 | 通过反射调用真实方法 | 通过 FastClass 直接调用 |
| 性能(创建代理) | 较快(直接生成字节码) | 较慢(需要分析继承关系) |
| 性能(方法调用) | JDK 8+ 接近 CGLIB | 略快(FastClass 无反射) |
| 依赖 | JDK 内置 | 需要引入第三方库 |
| 代理类个数 | 1个代理类 | 3个类(代理类 + 2个FastClass) |
| Java 版本兼容 | 全版本 | JDK 17+ 需要额外参数 |
性能实测(JDK 17,JMH 基准测试):
| 场景 | JDK 代理 | CGLIB |
|---|---|---|
| 创建代理对象 | ~0.5μs | ~5μs |
| 方法调用(无参) | ~50ns | ~40ns |
| 方法调用(有参) | ~80ns | ~60ns |
结论:创建代理 JDK 更快(适合频繁创建),方法调用 CGLIB 略快(适合频繁调用)。在 Spring Boot 场景中,Bean 创建只有一次,方法调用却有无数次,所以 Spring Boot 2.x 默认改为 CGLIB。
# 7.5 反射与代理的应用
# 7.5.1 Spring AOP的实现选择
Spring AOP 的代理选择策略:
- 目标对象实现了接口 → 默认使用 JDK 动态代理
- 目标对象没有实现接口 → 使用 CGLIB
- 可通过
@EnableAspectJAutoProxy(proxyTargetClass=true)强制使用 CGLIB
Spring Boot 2.x 的变化:默认 spring.aop.proxy-target-class=true,即默认使用 CGLIB。
# 7.5.2 Spring AOP代理选择源码分析
// Spring AOP 的代理选择逻辑(简化)
// DefaultAopProxyFactory.java
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isOptimize() || // 优化标志
config.isProxyTargetClass() || // 强制使用 CGLIB
hasNoUserSuppliedProxyInterfaces(config)) // 目标类没有接口
{
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("...");
}
// 如果目标类本身就是接口,或者是 Proxy 子类,仍用 JDK 代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则使用 CGLIB
return new ObjenesisCglibAopProxy(config);
} else {
// 有接口且未强制 CGLIB → 使用 JDK 代理
return new JdkDynamicAopProxy(config);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Spring AOP 的调用链:
@Transactional
public void transfer(int from, int to, BigDecimal amount) { ... }
// 实际调用链:
proxy.transfer(from, to, amount)
→ CglibAopProxy.DynamicAdvisedInterceptor.intercept()
→ 获取拦截器链 (List<MethodInterceptor>)
→ TransactionInterceptor.invoke()
→ 开启事务
→ target.transfer(from, to, amount) // 真实方法
→ 提交事务 / 回滚事务
2
3
4
5
6
7
8
9
10
11
# 7.5.3 MyBatis Mapper代理原理
MyBatis 的 Mapper 接口没有实现类,但可以直接调用,就是因为动态代理:
// Mapper 接口——没有实现类
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(int id);
}
// MyBatis 的代理实现
public class MapperProxy<T> implements InvocationHandler {
private SqlSession sqlSession;
private Class<T> mapperInterface;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object 方法直接调用(equals, hashCode, toString)
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 根据接口方法名和参数,构建 SQL 并执行
MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
// 创建 Mapper 代理
MapperProxyFactory<UserMapper> factory = new MapperProxyFactory<>(UserMapper.class);
UserMapper mapper = factory.newInstance(sqlSession);
// mapper 就是一个 JDK 动态代理对象
User user = mapper.findById(1); // 实际执行 SQL
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
MapperProxy 的调用链:
mapper.findById(1)
→ MapperProxy.invoke()
→ MapperMethod.execute()
→ sqlSession.selectOne("findById", 1)
→ Executor.query()
→ JDBC PreparedStatement 执行 SQL
2
3
4
5
6
# 7.5.4 框架中的反射应用全景
| 框架 | 反射/代理用途 | 具体机制 |
|---|---|---|
| Spring IoC | 根据配置/注解反射创建 Bean | Class.forName + Constructor.newInstance |
| Spring AOP | 方法拦截,实现事务、日志等切面 | JDK 代理 / CGLIB |
| MyBatis | Mapper 接口→动态代理生成实现类 | JDK 动态代理 |
| Jackson/Gson | 反射读写对象字段,实现 JSON 序列化 | Field.get/set |
| JUnit | 反射调用 @Test 标注的方法 | Method.invoke |
| Hibernate | 延迟加载,代理实体对象 | CGLIB / ByteBuddy |
| Dubbo | RPC 接口代理,远程调用透明化 | JDK 动态代理 |
| Retrofit | HTTP API 接口代理 | JDK 动态代理 |
| Lombok | 编译期注解处理(非运行时反射) | APT,不是反射 |
# 7.6 反射安全与模块化限制
# 7.6.1 反射突破封装的安全风险
反射可以访问私有成员,这在框架中很有用,但也带来安全风险:
// 通过反射修改 String 的 value 数组(JDK 8)
String s = "hello";
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
value[0] = 'H';
System.out.println(s); // "Hello"
// 字符串常量池中的 "hello" 被改了!
// 所有引用 "hello" 的地方都会受影响
// 通过反射调用私有构造器创建枚举实例
// 通过反射修改 final 字段
// ... 这些都是反射的"黑魔法"
2
3
4
5
6
7
8
9
10
11
12
13
# 7.6.2 JDK9模块化对反射的限制
JDK 9 引入模块化系统(JPMS/Jigsaw),对反射做了严格限制:
// JDK 8:可以随意反射访问 JDK 内部类
// JDK 9+:默认不允许跨模块反射访问非导出包
// 例如:反射访问 java.lang.String 的 value 字段
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
// JDK 9+: 抛出 InaccessibleObjectException
// Unable to make field private final byte[] java.lang.String.value accessible
2
3
4
5
6
7
8
解决方案:
# 方案1:JVM 启动参数打开模块
--add-opens java.base/java.lang=ALL-UNNAMED
# 方案2:模块声明中导出包
module my.module {
opens com.example.internal to spring.core; // 只对 Spring 开放
}
# 方案3:使用 Unsafe(不推荐)
# Unsafe 仍然可以绕过模块化限制
2
3
4
5
6
7
8
9
10
模块化对框架的影响:
| 框架 | 受影响的功能 | 解决方案 |
|---|---|---|
| Spring | Bean 的反射创建 | --add-opens 参数 |
| Hibernate | 实体字段反射访问 | --add-opens 参数 |
| Jackson | JSON 序列化反射读写 | 使用 getter/setter 或 --add-opens |
| JUnit | 反射调用测试方法 | 框架已适配 |
# 7.6.3 SecurityManager与反射
// SecurityManager 可以限制反射操作
// 但 JDK 17 已标记 SecurityManager 为 deprecated
// JDK 18+ 默认不允许安装 SecurityManager
// 在 JDK 17 之前可以这样限制反射:
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission
&& "suppressAccessChecks".equals(perm.getName())) {
throw new SecurityException("不允许 setAccessible");
}
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
# 7.7 面试高频问题深度解析
Q1:反射为什么能突破 private 限制?
setAccessible(true) 的作用是设置 AccessibleObject.override 字段为 true。当 override 为 true 时,Method.invoke() / Field.get() 等方法内部跳过 Java 语言的访问权限检查。注意:这只是跳过了 Java 层面的检查,不是修改了字段的访问修饰符。
Q2:JDK 动态代理为什么必须基于接口?
因为生成的代理类已经继承了 java.lang.reflect.Proxy,Java 不支持多继承,所以代理类无法再继承目标类。只能通过实现接口来与目标类建立关系。
Q3:反射调用方法时,方法内部的 this 是谁?
Method method = UserServiceImpl.class.getMethod("save", String.class);
method.invoke(target, "张三");
// save 方法内部的 this 是 target 对象
// 不是代理对象,也不是 null
2
3
4
Q4:动态代理能代理 private 方法吗?
- JDK 动态代理:不能。只能代理接口中声明的方法,接口方法默认是 public。
- CGLIB:不能。CGLIB 通过继承实现,private 方法不能被子类重写。
- 要"代理"私有方法,只能通过字节码增强工具(如 ByteBuddy、ASM)直接修改字节码。
Q5:Spring 事务失效的场景与代理的关系?
@Service
public class OrderService {
@Transactional
public void createOrder() {
// ... 创建订单
this.updateStock(); // 直接调用,不走代理!事务不生效!
}
@Transactional
public void updateStock() {
// ... 更新库存
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
原因:this.updateStock() 是直接调用,不经过代理对象。Spring AOP 的事务切面只在代理对象的方法调用时生效。
解决方案:
// 方案1:注入自身
@Autowired
private OrderService self;
public void createOrder() {
self.updateStock(); // 通过代理对象调用
}
// 方案2:从 ApplicationContext 获取代理
((OrderService) AopContext.currentProxy()).updateStock();
// 方案3:将方法拆分到不同的类中
2
3
4
5
6
7
8
9
10
11
12
Q6:CGLIB 在 JDK 17+ 的问题?
JDK 17 对 java.lang.invoke 做了更严格的限制,CGLIB 依赖的 Unsafe.defineClass 被移除。Spring 6 / Spring Boot 3 已迁移到 ByteBuddy 来替代传统 CGLIB。
# JDK 17+ 使用 CGLIB 需要添加
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
2
3
# 7.8 总结与核心要点
设计哲学:
- 反射是 Java 动态性的基石:让"编译时未知的类"在运行时可操作
- 动态代理是 AOP 的实现机制:运行时生成代理类,实现方法拦截
- 性能与灵活性的取舍:反射牺牲性能换灵活性,JVM 通过 Inflation 优化高频调用
- 安全与灵活的博弈:模块化限制了反射的"黑魔法",但保障了系统安全
技术演进总结:
| 阶段 | 技术 | 特点 |
|---|---|---|
| JDK 1.1 | Reflection API | 基础反射能力 |
| JDK 1.3 | JDK 动态代理 | 基于接口的动态代理 |
| JDK 1.5 | 泛型+注解+反射 | 反射能力增强 |
| JDK 7 | MethodHandle | 轻量级反射替代品 |
| JDK 8 | LambdaMetafactory | 函数式接口动态生成 |
| JDK 9 | 模块化 | 反射受到限制 |
| JDK 16+ | Record/Sealed | 反射需适配新特性 |
核心要点:
| 问题 | 答案 |
|---|---|
| 反射为什么慢 | 无法内联、装箱、安全检查、缓存失效 |
| 15次后为什么变快 | Inflation:从 Native 切换为字节码直接调用 |
| JDK代理的限制 | 只能代理接口(因为单继承,已继承Proxy) |
| CGLIB代理的限制 | 不能代理 final 类/方法(基于继承) |
| Spring AOP 用哪种 | Spring Boot 2.x+ 默认 CGLIB |
| MethodHandle 优势 | 可被 JIT 内联,性能接近直接调用 |
| 反射优化方案 | 缓存Method/Field + setAccessible + MethodHandle |
| 事务失效原因 | this 调用不经过代理对象 |