编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

    • 综合案例

    • 专栏博客

      • README
      • JVM内存模型与对象
      • 类加载与双亲委派
      • 垃圾回收与GC调优
      • 异常体系与JVM机制
      • 字节码指令集javap实战
      • JIT编译与去优化机制
      • JVM性能诊断工具链
      • OOM八大现场全景剖析
      • JVM参数调优全景图
      • GraalVM与AOT编译原理
      • HashMap底层哈希设计
      • String不可变与常量池
      • ArrayList与LinkedList源码
      • ConcurrentHashMap并发
      • TreeMap与红黑树原理
      • LinkedHashMap与LRU实现
      • Java数字类型原理
      • Object通用方法的契约
      • 泛型擦除与类型系统
      • 枚举原理与最佳实践
      • 注解原理与编译期处理
      • Lambda与引用底层原理
      • Stream原理与流水线设计
      • Optional设计原理
      • Record密封类与模式
      • 反射机制与动态代理
        • 7.1 开篇疑问
        • 7.2 反射的本质
          • 7.2.1 什么是反射
          • 7.2.2 获取Class对象的三种方式
          • 7.2.3 反射的核心API
          • 7.2.4 Class对象的本质与存储
        • 7.3 反射的底层实现
          • 7.3.1 Method.invoke的调用链
          • 7.3.2 从Native到字节码生成
          • 7.3.3 反射为什么慢
          • 7.3.4 反射优化实战
          • 7.3.5 MethodHandle与反射对比
        • 7.4 动态代理原理剖析
          • 7.4.1 代理模式回顾
          • 7.4.2 JDK动态代理实现
          • 7.4.3 Proxy类的生成过程
          • 7.4.4 反编译$Proxy0完整源码
          • 7.4.5 CGLIB动态代理
          • 7.4.6 CGLIB的FastClass机制
          • 7.4.7 JDK代理与CGLIB深度对比
        • 7.5 反射与代理的应用
          • 7.5.1 Spring AOP的实现选择
          • 7.5.2 Spring AOP代理选择源码分析
          • 7.5.3 MyBatis Mapper代理原理
          • 7.5.4 框架中的反射应用全景
        • 7.6 反射安全与模块化限制
          • 7.6.1 反射突破封装的安全风险
          • 7.6.2 JDK9模块化对反射的限制
          • 7.6.3 SecurityManager与反射
        • 7.7 面试高频问题深度解析
        • 7.8 总结与核心要点
      • MethodHandle与VarHandle
      • 三大字节码框架对比
      • JavaAgent与Instrumentation机制
      • AOP三种实现路线对比
      • synchronized与锁升级
      • volatile与JMM内存模型
      • 线程池核心源码设计
      • Thread线程生命周期
      • AQS同步框架源码
      • 并发锁三剑客
      • CAS和Atomic深入分析
      • 五大同步器对比
      • CompletableFuture异步
      • IO模型演进BIO到AIO
      • ByteBuffer与堆外内存
      • 序列化原理与替代方案
      • 文件IO与NIO.2
      • 面向对象的真意
      • JDK设计模式上
      • JDK设计模式下
      • SPI与模块化设计
  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 专栏博客
杨充
2026-06-02
目录

反射机制与动态代理

# 26.反射机制与动态代理

# 目录介绍

  • 7.1 开篇疑问
  • 7.2 反射的本质
    • 7.2.1 什么是反射
    • 7.2.2 获取Class对象的三种方式
    • 7.2.3 反射的核心API
    • 7.2.4 Class对象的本质与存储
  • 7.3 反射的底层实现
    • 7.3.1 Method.invoke的调用链
    • 7.3.2 从Native到字节码生成
    • 7.3.3 反射为什么慢
    • 7.3.4 反射优化实战
    • 7.3.5 MethodHandle与反射对比
  • 7.4 动态代理原理剖析
    • 7.4.1 代理模式回顾
    • 7.4.2 JDK动态代理实现
    • 7.4.3 Proxy类的生成过程
    • 7.4.4 反编译$Proxy0完整源码
    • 7.4.5 CGLIB动态代理
    • 7.4.6 CGLIB的FastClass机制
    • 7.4.7 JDK代理与CGLIB深度对比
  • 7.5 反射与代理的应用
    • 7.5.1 Spring AOP的实现选择
    • 7.5.2 Spring AOP代理选择源码分析
    • 7.5.3 MyBatis Mapper代理原理
    • 7.5.4 框架中的反射应用全景
  • 7.6 反射安全与模块化限制
    • 7.6.1 反射突破封装的安全风险
    • 7.6.2 JDK9模块化对反射的限制
    • 7.6.3 SecurityManager与反射
  • 7.7 面试高频问题深度解析
  • 7.8 总结与核心要点

# 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, "张三");
1
2
3
4
5
6
7
8
9

反射的核心能力:

  1. 运行时获取类信息:字段、方法、构造器、注解、泛型参数
  2. 运行时创建对象:不需要 new,通过 Class 对象构造
  3. 运行时调用方法:不需要编译时知道方法名
  4. 运行时访问/修改字段:包括私有字段

# 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
1
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());
1
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);
1
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();
1
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 对象持有指向方法区类数据的指针
1
2
3
4
5

Class 对象的关键特性:

  1. 唯一性:同一个类加载器加载的同一个类,只有一个 Class 对象
  2. 不可实例化:Class 的构造器是 private 的,只有 JVM 能创建
  3. 数据源: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 对象本身仍在堆中,是元数据的"入口"
1
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;
        // ... 更多缓存
    }
}
1
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次起)
        → 动态生成的字节码,直接调用目标方法
1
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);
}
1
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阶段)
1
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);
    }
}
1
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;
    }
}
1
2
3
4
5
6
7
8

设计思考:为什么不一开始就生成字节码?因为生成字节码有开销(需要动态生成类、加载类),如果反射只调用一两次,Native 方式更快。只有高频调用才值得"投资"生成字节码。这是延迟优化的经典设计。

# 7.3.3 反射为什么慢

  1. 无法内联优化:JIT 编译器无法对反射调用做方法内联,因为调用目标在运行时才确定
  2. 装箱拆箱:invoke(Object obj, Object... args) 参数是 Object,基本类型需要装箱
  3. 安全检查:每次调用都要检查访问权限(setAccessible(true) 可以跳过部分检查)
  4. 缓存失效:反射调用的目标不确定,CPU 分支预测和缓存命中率低
  5. 数组创建: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");
    }
}
1
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);
}
1
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);
1
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);   // 精确调用,无额外检查,最快
1
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();
1
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");
1
2
3
4
5
6
7
8
9

# 7.4 动态代理原理剖析

# 7.4.1 代理模式回顾

代理模式:不直接访问目标对象,而是通过一个代理对象间接访问,代理可以在调用前后增加额外逻辑。

Client → Proxy → RealSubject
           ↓
     前置逻辑(日志/权限)
     调用目标方法
     后置逻辑(监控/事务)
1
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遍...
}
1
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
1
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);
}
1
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. 放入缓存
1
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 类似...
}
1
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

从反编译中可以看出:

  1. 代理类继承了 Proxy(所以 JDK 代理只能代理接口,因为 Java 单继承)
  2. 每个方法内部都调用 InvocationHandler.invoke()
  3. Method 对象在 static {} 中初始化一次,后续调用不再反射查找
  4. 基本类型参数会自动装箱(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);
1
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方法不能被代理——直接调用原方法,不经过拦截器
1
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);
    }
}
1
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);  // 非反射,直接调用
1
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");
    }
}
1
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);
        }
    }
}
1
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)  // 真实方法
      → 提交事务 / 回滚事务
1
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
1
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
1
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 字段
// ... 这些都是反射的"黑魔法"
1
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
1
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 仍然可以绕过模块化限制
1
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");
        }
    }
});
1
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
1
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() {
        // ... 更新库存
    }
}
1
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:将方法拆分到不同的类中
1
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
1
2
3

# 7.8 总结与核心要点

设计哲学:

  1. 反射是 Java 动态性的基石:让"编译时未知的类"在运行时可操作
  2. 动态代理是 AOP 的实现机制:运行时生成代理类,实现方法拦截
  3. 性能与灵活性的取舍:反射牺牲性能换灵活性,JVM 通过 Inflation 优化高频调用
  4. 安全与灵活的博弈:模块化限制了反射的"黑魔法",但保障了系统安全

技术演进总结:

阶段 技术 特点
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 调用不经过代理对象
上次更新: 2026/06/10, 11:13:41
Record密封类与模式
MethodHandle与VarHandle

← Record密封类与模式 MethodHandle与VarHandle→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式