跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

JDK 动态代理与 CGLIB 动态代理区别及 Spring AOP 适配原理

JDK 动态代理基于接口实现,通过反射调用目标方法,要求目标类必须实现接口。CGLIB 动态代理基于继承实现,生成目标类的子类,利用 FastClass 索引化调用提升性能。在 Spring AOP 中,若目标类有接口优先使用 JDK 代理,若无接口则自动切换为 CGLIB 代理。CGLIB 因无需接口且高频调用性能更优,常被视为更适配复杂场景的代理方案,但需注意 final 类无法被代理的限制。

活在当下发布于 2026/3/25更新于 2026/10/2598 浏览

一、JDK 动态代理原理

package com.example.proxy;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;

/**
 * JDK 动态代理示例
 * 必须基于接口一种代理方式。
 * 目标类和代理类都是接口的实现类。
 */
public class JdkDynamicProxyDemo {
    public static void main(String[] args) {
        // 目标对象
        UserService userService = new UserServiceImpl();
        // 创建代理对象。利用反射机制创建代理类对象。
        UserService proxyObject = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                (proxyObj, method, argsArray) -> {
                    System.out.println("方法开始执行了...");
                    // 非业务代码,每个方法开始都要执行这行代码,重复代码开发,不利于维护。
                    // 反射调用目标方法
                    Boolean flag = (Boolean) method.invoke(userService, argsArray);
                    System.out.println("方法结束执行了...");
                    return flag;
                }
        );
        boolean flag = proxyObject.login("admin", "123456");
        System.out.println("flag = " + flag);
    }
}

在 JDK 动态代理中,我们先创建一个包含真实业务逻辑的目标对象(如 UserServiceImpl),再通过 Proxy.newProxyInstance() 动态生成一个实现了相同接口的代理对象。这个代理对象本身不包含业务逻辑,它的核心是通过 InvocationHandler 拦截方法调用,在调用前后插入增强逻辑(如日志、事务),并通过 method.invoke() 反射调用目标对象的真实方法。代理对象是多例的,每次调用 newProxyInstance 都会生成一个新实例,其生命周期由代码作用域决定,而非随单次方法调用结束而销毁。

二、CGLIB 动态代理原理

  • JDK 代理的 Method.invoke() 是纯反射调用,每次都要动态解析方法签名、检查权限,有固定开销;
  • CGLIB 的 MethodProxy.invokeSuper() 基于 FastClass 生成索引化调用逻辑,绕过反射的动态解析,高频调用场景下性能比 JDK 代理高 20%-30%(Spring 5+ 对 JDK 代理做了优化,差距缩小,但 CGLIB 仍略优)。
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    proxy.sig1 = new Signature(name1, desc);
    proxy.sig2 = new Signature(name2, desc);
    proxy.createInfo = new CreateInfo(c1, c2);
    return proxy;
}

private void init() {
    if (this.fastClassInfo == null) {
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                CreateInfo ci = this.createInfo;
                FastClassInfo fci = new FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}

private static FastClass helper(CreateInfo ci, Class type) {
    FastClass.Generator g = new FastClass.Generator();
    g.setType(type);
    g.setClassLoader(ci.c2.getClassLoader());
    g.setNamingPolicy(ci.namingPolicy);
    g.setStrategy(ci.strategy);
    g.setAttemptLoad(ci.attemptLoad);
    return g.create();
}

MethodProxy 是 CGLIB 为代理方法生成的'高性能调用器',核心解决普通反射 Method.invoke() 性能低的问题。这段代码的核心流程是:创建 MethodProxy → 懒加载初始化 FastClass → 为方法分配索引 → 通过索引快速调用。

2.1 逐方法拆解源码
1. create(...) 方法:初始化 MethodProxy 的基础元信息
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    proxy.sig1 = new Signature(name1, desc); // 目标类方法的签名
    proxy.sig2 = new Signature(name2, desc); // 代理类方法的签名
    proxy.createInfo = new CreateInfo(c1, c2); // 存储目标类/代理类信息
    return proxy;
}

参数 / 变量解读:

  • c1:目标类(如 UserService,真正包含业务逻辑的类);
  • c2:CGLIB 动态生成的代理类(如 UserService$$EnhancerByCGLIB$$xxxx,目标类的子类);
  • desc:方法的字节码描述符(如 login 方法的描述符是 (Ljava/lang/String;Ljava/lang/String;)Z,代表'两个 String 参数,返回 boolean');
  • name1:目标类中方法的原始名称(如 login);
  • name2:代理类中对应方法的别名(CGLIB 生成的,如 CGLIB$login$0);
  • sig1/sig2:Signature 是 CGLIB 封装的'方法签名'(方法名 + 描述符),用于唯一标识一个方法;
  • createInfo:临时存储目标类/代理类的核心信息(类加载器、命名规则等),供后续生成 FastClass 使用。

核心作用:为目标方法和代理方法做'身份标记'——相当于给方法办了'身份证',后续生成 FastClass 时,能通过这个'身份证'找到对应的方法。

2. init() 方法:懒加载创建 FastClass(性能优化的核心)
private void init() {
    if (this.fastClassInfo == null) { // 双重检查锁:保证线程安全 + 懒加载
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                CreateInfo ci = this.createInfo;
                FastClassInfo fci = new FastClassInfo();
                // 1. 为目标类 (c1) 生成 FastClass
                fci.f1 = helper(ci, ci.c1);
                // 2. 为代理类 (c2) 生成 FastClass
                fci.f2 = helper(ci, ci.c2);
                // 3. 为目标方法/代理方法分配'索引'(核心!)
                fci.i1 = fci.f1.getIndex(this.sig1); // 目标方法在 FastClass 中的索引
                fci.i2 = fci.f2.getIndex(this.sig2); // 代理方法在 FastClass 中的索引
                this.fastClassInfo = fci; // 缓存 FastClass 信息,避免重复创建
                this.createInfo = null; // 释放临时信息,节省内存
            }
        }
    }
}

关键逻辑解读:

  • 懒加载:init() 不会在 create() 时执行,而是第一次调用 methodProxy.invokeSuper() 时才触发——如果代理方法从未被调用,就不用生成 FastClass,节省内存和初始化时间;
  • 双重检查锁(DCL):保证多线程下只会生成一次 FastClass,避免重复创建导致的性能损耗;
  • FastClass 索引化:fci.f1.getIndex(this.sig1) 是核心 ——FastClass 会为目标类的所有方法生成一个'索引表'(方法名 → 整数索引),比如 login 方法的索引是 1,logout 是 2。后续调用方法时,只需传索引 1,不用再通过方法名反射找方法,这是性能提升的关键;
  • fastClassInfo:缓存 FastClass 和方法索引,后续调用直接复用,无需重复生成。
3. helper(...) 方法:生成 FastClass 实例
private static FastClass helper(CreateInfo ci, Class type) {
    FastClass.Generator g = new FastClass.Generator();
    g.setType(type); // 指定为哪个类生成 FastClass(目标类/代理类)
    g.setClassLoader(ci.c2.getClassLoader()); // 复用代理类的类加载器
    g.setNamingPolicy(ci.namingPolicy); // 遵循 CGLIB 的命名规则(如 FastClass 类名格式:XXX$$FastClassByCGLIB$$xxxx)
    g.setStrategy(ci.strategy); // 字节码生成策略(控制 FastClass 的生成规则)
    g.setAttemptLoad(ci.attemptLoad); // 尝试从已加载的类中获取 FastClass,避免重复生成
    return g.create(); // 核心:动态生成 FastClass 的字节码并加载到 JVM
}

FastClass 的本质:FastClass 是 CGLIB 为目标类/代理类生成的'快速调用类',它的核心逻辑是:

// 简化版 FastClass 生成的代码
public class UserService$$FastClassByCGLIB$$xxxx {
    // 方法索引表:索引 → 方法调用
    public Object invoke(int index, Object obj, Object[] args) {
        UserService target = (UserService) obj;
        switch(index) {
            case 1: return target.login((String)args[0], (String)args[1]); // 直接调用,无反射
            case 2: return target.logout(); // ... 其他方法
        }
    }
}

可以看到:FastClass 把'通过方法名反射调用'变成了'通过整数索引直接调用',完全是普通的 Java 方法调用,没有任何反射开销。

结合调用流程,看懂性能优化的本质

当你调用 methodProxy.invokeSuper(proxyObj, args) 时,底层执行流程是:

methodProxy.invokeSuper(...) → 触发 init()(首次调用) → helper() 生成目标类/代理类的 FastClass → 为目标方法分配索引(如 login 方法索引=1) → 调用 FastClass.invoke(1, proxyObj, args) → 通过 switch(index) 直接调用目标方法(无反射)

2.2 代码示例

package com.example.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

// ========== 1. 目标类(无需实现接口,仅做核心业务) ==========
class UserService {
    public boolean login(String username, String password) {
        System.out.println("UserService - login 目标方法执行了");
        // 核心业务:登录校验
        return "admin".equals(username) && "123456".equals(password);
    }
}

// ========== 2. CGLIB 动态代理核心:通用增强逻辑 + 生成代理 ==========
public class CglibDynamicProxyDemo {
    public static void main(String[] args) {
        // 步骤 1:创建目标对象
        UserService target = new UserService();
        // 步骤 2:生成动态代理对象(运行时自动创建代理类,继承目标类)
        UserService proxy = (UserService) Enhancer.create(
                UserService.class, // 目标类的 Class 对象(核心:继承目标类)
                (MethodInterceptor) (proxyObj, method, argsArr, methodProxy) -> {
                    // 增强逻辑 1:方法执行前(如打印日志)
                    System.out.println("Before " + method.getName() + ": " + argsArr[0]);
                    // 调用目标类的核心方法
                    Object result = methodProxy.invoke(target, argsArr);
                    // 通过方法代理对象调用目标方法
                    // 增强逻辑 2:方法执行后(如打印结果)
                    System.out.println("After " + method.getName() + ": " + result);
                    return result;
                }
        );
        // 步骤 3:调用代理方法(自动触发增强 + 核心业务)
        boolean flag = proxy.login("admin", "123456");
        System.out.println("flag = " + flag);
    }
}

内部 Enhancer e = new Enhancer(); e.setSuperclass(type); e.setCallback(callback); e.create();

CGLIB 动态代理通过 Enhancer 核心工具类实现,无需目标类实现接口,而是以目标类(如 UserService)作为父类动态生成子类作为代理类;Enhancer.create 方法指定目标类 Class 对象和 MethodInterceptor 回调接口,代理类的所有方法调用都会被该回调拦截,回调方法接收代理实例、被拦截方法的反射对象、方法实际参数值和 CGLIB 优化的 methodProxy 对象;调用代理对象方法时,会先执行前置增强逻辑,再通过 methodProxy.invoke(性能优于普通反射)调用目标对象的核心业务方法,执行完成后执行后置增强逻辑并返回结果,这一机制弥补了 JDK 动态代理需实现接口的局限性,同时通过方法代理优化提升了调用性能。

三、对比

对比维度JDK 动态代理(JDK Proxy)CGLIB 动态代理(Code Generation Library)
底层原理基于接口实现:动态生成一个实现目标接口的代理类(如 $Proxy0),代理类的方法转发给 InvocationHandler基于继承实现:动态生成目标类的子类作为代理类,重写目标方法并转发给 MethodInterceptor
核心限制必须要求目标类实现至少一个接口(否则无法生成代理)目标类不能是 final(无法继承),目标方法不能是 final(无法重写)
核心类 / 接口Proxy.newProxyInstance()、InvocationHandlerEnhancer.create()、MethodInterceptor、MethodProxy
方法调用方式Method.invoke()(纯反射,性能中等)MethodProxy.invokeSuper()(FastClass 索引调用,性能更高)
侵入性目标类必须实现接口(侵入式设计)目标类无需实现任何接口(无侵入式设计)
实例化方式代理类实现接口,可直接强转为接口类型代理类继承目标类,可直接强转为目标类类型
Spring AOP 默认选择目标类有接口 → 优先用 JDK 代理目标类无接口 → 自动切换为 CGLIB 代理

目录

  1. 一、JDK 动态代理原理
  2. 二、CGLIB 动态代理原理
  3. 2.1 逐方法拆解源码
  4. 1. create(...) 方法:初始化 MethodProxy 的基础元信息
  5. 2. init() 方法:懒加载创建 FastClass(性能优化的核心)
  6. 3. helper(...) 方法:生成 FastClass 实例
  7. 结合调用流程,看懂性能优化的本质
  8. 2.2 代码示例
  9. 三、对比

更多推荐文章

查看全部
  • AI 提示词基础:从零构建高效对话思维
  • 智能仿真无人机平台多线程实现:任务分配与碰撞规避
  • Java 集合框架核心组件与使用场景
  • 代价驱动的 SQL 连接条件下推实践
  • GLM-4-9B 开源发布:支持 26 种语言与 128K 上下文,性能优于 Llama-3-8B
  • Eclipse IDE Java 注解处理器 AnnotationMirror 示例
  • 基于 Docker 部署开源人机协作平台 DeskClaw
  • Linux 命令行参数与环境变量深度解析及代码实践
  • SSM 架构下教师业绩管理系统的设计与实现
  • Rokid 灵珠平台搭建旅游 AR 智能体实战
  • 正版 Adobe PS 与 LRC AI 填充功能无法使用解决方案
  • GitHub Copilot Pro 使用指南:模型配额与性价比分析
  • 从空乘转行网络安全:零基础入门经验与面试技巧分享
  • 网络安全转行学习建议与成长路径指南
  • Verilog 零基础入门:语法、仿真与 FPGA 实战
  • Java 并发核心:单例、生产者消费者、定时器与线程池实战
  • 从零构建智能语音交互:Python 语音客户端实战指南
  • VSCode 集成 Copilot 与 Figma MCP 生成产品代码
  • GitLab 个人访问令牌(Token)获取方法
  • Pi0 机器人大模型在昇腾 A2 平台上的部署与性能测评

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online