技术汇
HOME
技术汇
正文内容
2026年04月10日 · 一文搞懂Java动态代理与CGLIB核心原理
发布时间 : 2026-04-14
作者 : 小编
访问数量 : 27
扫码分享至微信

在 Java 技术栈中,动态代理是 AOP(Aspect-Oriented Programming,面向切面编程)框架、RPC 框架和 MyBatis 等中间件的核心底层支撑,堪称“框架工程师的必修课”-20。今天,助手抖音AI小助手就带大家从“只会用”到“懂原理”,彻底打通动态代理的知识链路。

一、痛点切入:为什么需要代理模式?

很多开发者在工作中都会遇到这样的需求:给 Service 层的业务方法统一加日志,或者给支付接口加权限校验,却又不想在每个方法里重复编写相同的代码-2

传统做法无非两种:

  • 方式一:在类中直接嵌入逻辑。若多个类都需要日志功能,每个类都要重复编写相同的日志代码,一旦格式要改,所有类都得同步修改,维护成本极高-47

  • 方式二:通过组合引入功能类。目标类需要持有事务管理器或日志服务的引用,方法调用前后主动调用,这会让目标类被迫知晓大量实现细节,破坏了单一职责原则,耦合度剧增-47

静态代理正是解决上述问题的第一步。代理类与目标类实现同一接口,在代理类的方法调用前后嵌入附加逻辑,客户端直接与代理对象交互-38。来看一个极简示例:

java
复制
下载
// 1. 抽象主题:业务接口
public interface UserService {
    void addUser(String username);
}

// 2. 真实主题:核心业务(只干活,不写日志)
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("数据库新增用户:" + username);
    }
}

// 3. 静态代理类:为目标类附加日志功能
public class UserServiceProxy implements UserService {
    private final UserService target;   // 持有目标对象引用
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void addUser(String username) {
        System.out.println("〖日志〗开始执行addUser,参数:" + username);  // 前置增强
        target.addUser(username);                                        // 调用核心业务
        System.out.println("〖日志〗addUser执行完毕");                    // 后置增强
    }
}

// 客户端调用
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.addUser("张三");

静态代理虽有“不入侵原代码”的优点,但问题也显而易见:

  • 代理类爆炸:每个目标类都需要手动编写一个对应的代理类,数量会随着业务规模急剧膨胀-

  • 扩展性差:一旦接口新增方法,所有代理类都得同步修改,违背开闭原则-3

  • 代码冗余:日志、权限这类增强逻辑在每个代理类中重复出现,毫无复用性可言。

由此,动态代理应运而生——核心目标就是“运行时生成代理类,一套增强逻辑为无数个目标对象服务”-38

二、核心概念讲解:JDK动态代理

定义

JDK动态代理是 Java 原生提供的动态代理技术。它的核心思想是:在运行时动态生成一个实现指定接口的代理类,并将对该代理类的方法调用统一转发给 InvocationHandler(调用处理器)的 invoke() 方法,由开发者在该方法中自由插入增强逻辑-11

核心关键词拆解

  • 动态:代理类不是在编译期写死的,而是在运行时由 JVM 通过字节码生成技术创建-38

  • 接口驱动:JDK 动态代理要求目标类必须实现至少一个接口,代理类会实现这些接口-11

  • InvocationHandler:开发者只需实现该接口的 invoke() 方法,所有代理对象的方法调用最终都会“汇集”到这里-3

  • Proxy.newProxyInstance():这是创建代理对象的工厂方法,接受三个参数(类加载器、接口数组、InvocationHandler实例)并返回代理对象-3

生活化类比

JDK 动态代理就像一个 “万能接线员” :你只需要告诉接线员“我要联系哪些部门(接口)”,接线员就会自动帮你接通并记录通话(增强逻辑),而你完全不用关心每个部门是不是都配了一个专属秘书。一个接线员可以为无数个部门提供服务——这正是动态代理“一套逻辑服务多个目标”的核心优势。

作用与价值

JDK 动态代理实现了横切关注点与业务逻辑的解耦,让开发者可以在不修改业务代码的前提下统一添加日志、事务、权限校验等增强逻辑,同时解决了静态代理代理类爆炸的扩展性问题-3

三、关联概念讲解:CGLIB动态代理

定义

CGLIB(Code Generation Library,代码生成库) 是基于 ASM 字节码操作框架实现的另一种动态代理技术。它的实现方式是通过动态生成目标类的子类作为代理类,并重写父类的非 final 方法来植入增强逻辑,因此不要求目标类实现任何接口-19-22

与 JDK 动态代理的关系

JDK 动态代理和 CGLIB 是两种并行且互补的动态代理实现方式:

  • JDK 动态代理:基于接口,轻量原生,无需引入第三方库;

  • CGLIB:基于继承,可代理无接口的普通类,但需引入 cglib 库(Spring Core 已内置),且无法代理 final 类或 final 方法-19-24

一句话记忆:JDK 动态代理是“接口派”,CGLIB 是“子类派”-20

四、概念关系与区别总结

对比维度JDK 动态代理CGLIB 动态代理
实现方式接口代理(实现目标接口)子类代理(继承目标类)-19
是否依赖接口必须实现接口不需要接口-19
能否代理 final 类/方法❌ 不涉及❌ 无法代理(继承受限)-19
底层技术反射 + ProxyASM 字节码生成-19
第三方依赖无需(Java 原生)需引入 cglib 库-19
创建代理开销较小较大(需生成字节码)-19
方法调用性能JDK 8 前略慢,8+ 已大幅优化执行效率更高-24

逻辑关系总结:JDK 动态代理是 Java 官方原生提供的“接口驱动型”代理方案,CGLIB 则是社区提供的“继承驱动型”补充方案。两者共同解决了静态代理的扩展性问题,但各有所长、互为补充-20

五、代码示例演示

JDK 动态代理完整示例

java
复制
下载
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 定义业务接口(JDK动态代理必须有接口)
public interface UserService {
    void addUser(String username);
}

// 2. 目标类:实现接口
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("【核心业务】数据库新增用户:" + username);
    }
}

// 3. 实现 InvocationHandler,编写增强逻辑
public class LogInvocationHandler implements InvocationHandler {
    private final Object target;   // 持有目标对象引用
    
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强:日志记录
        System.out.println("〖JDK动态代理〗方法 " + method.getName() + " 开始执行,参数:" + args[0]);
        
        // 核心:反射调用目标方法
        Object result = method.invoke(target, args);
        
        // 后置增强
        System.out.println("〖JDK动态代理〗方法 " + method.getName() + " 执行完毕");
        return result;
    }
}

// 4. 创建代理对象并调用
public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),           // 类加载器
            target.getClass().getInterfaces(),            // 目标类实现的接口数组
            new LogInvocationHandler(target)              // InvocationHandler实例
        );
        proxy.addUser("李四");
    }
}

执行流程

  1. 调用 proxy.addUser("李四") 时,JVM 将调用转发给 LogInvocationHandler.invoke()

  2. invoke() 中先执行前置增强(打印日志),再通过反射 method.invoke(target, args) 调用目标类的真正业务方法

  3. 目标方法执行完毕后,invoke() 继续执行后置增强,最后返回结果-24

关键点invoke() 中的 target 必须是非 null 的真实对象实例,不能是代理对象自身,否则会导致无限递归-13

六、底层原理与技术支撑

JDK 动态代理的实现高度依赖两个底层技术:

1. Java 反射机制(Reflection)

  • 代理类的创建依赖 Proxy.newProxyInstance(),该方法的底层通过反射动态生成代理类的字节码;

  • 目标方法的实际调用依赖 Method.invoke(),这也是反射 API 的核心能力-47

2. 字节码生成技术

当调用 Proxy.newProxyInstance() 时,JVM 会在内存中动态生成一个代理类字节码(类名通常为 $Proxy0)并加载到 JVM 中。这个代理类会:

  • 继承 java.lang.reflect.Proxy 类(因此无法再继承其他类,这也是 JDK 动态代理无法代理普通类的根本原因)-11-13

  • 实现目标对象的所有接口;

  • 将所有接口方法的调用统一转发给 InvocationHandler.invoke()-16

CGLIB 的底层原理则有所不同:它基于 ASM 字节码操作框架,在运行时动态生成目标类的子类作为代理类,通过重写父类方法来植入增强逻辑-19

七、高频面试题与参考答案

面试题1:JDK 动态代理和 CGLIB 有什么区别?

参考答案(踩分点)

  1. 实现原理不同:JDK 基于接口代理,通过 Proxy.newProxyInstance() 生成实现指定接口的代理类;CGLIB 基于继承代理,通过 ASM 字节码技术生成目标类的子类作为代理类。

  2. 依赖条件不同:JDK 要求目标类必须实现接口;CGLIB 不依赖接口,但要求目标类和目标方法不能是 final

  3. 性能差异:JDK 动态代理创建代理对象较快(反射生成类),但方法调用通过反射执行,有额外开销;CGLIB 创建代理对象较慢(需生成字节码),但方法调用直接操作,执行效率更高。JDK 8+ 后性能差距已显著缩小。

  4. 第三方依赖:JDK 是 Java 原生支持,无需额外依赖;CGLIB 需要引入 cglib 库(Spring Core 已内置)。

  5. 适用场景:JDK 适合接口驱动的轻量场景;CGLIB 适合无接口类的代理需求-19-22

面试题2:Spring AOP 默认使用哪种动态代理?

参考答案

  • Spring Framework 5.x:默认优先使用 JDK 动态代理。若目标类没有实现任何接口,则自动切换为 CGLIB--32

  • Spring Boot 2.x 及更高版本:默认已改为 CGLIB 代理-32

  • 可通过配置强制指定:spring.aop.proxy-target-class=true 强制使用 CGLIB;false 则强制使用 JDK 动态代理(前提是目标类有接口)。

面试题3:JDK 动态代理为什么只能代理接口?

参考答案

因为 JDK 动态代理生成的代理类(如 $Proxy0)会继承 java.lang.reflect.Proxy。Java 是单继承的,一个类只能有一个父类,因此生成的代理类无法再继承目标类。只能通过实现接口的方式,让代理类与目标类具有相同的方法签名,从而实现对目标方法的拦截和转发。如果目标类没有实现任何接口,Proxy.newProxyInstance() 会直接抛出 IllegalArgumentException-13-11

面试题4:动态代理中的“动态”体现在哪里?

参考答案

“动态”体现在两个层面:

  1. 代理类生成时机:代理类不是在编译期手动编写,而是在运行时由 JVM 通过字节码生成技术动态创建-38

  2. 代理关系的灵活性:同一套增强逻辑(一个 InvocationHandler 实现)可以为任意多个实现了接口的目标对象提供代理服务,无需为每个目标类单独编写代理类,从根本上解决了静态代理的扩展性问题-37

八、结尾总结

核心知识点回顾

知识点要点
代理模式本质通过代理对象控制对目标对象的访问,在方法调用前后植入附加逻辑-38
静态代理痛点代理类爆炸、扩展性差、代码冗余-
JDK 动态代理基于接口 + 反射,依赖 ProxyInvocationHandler-11
CGLIB 动态代理基于继承 + ASM 字节码,可代理无接口类-19
底层原理反射 + 字节码生成 + 类加载机制-16
Spring AOP 选择Spring Boot 2.x+ 默认 CGLIB;传统 Spring 优先 JDK,无接口时降级 CGLIB-32

重点与易错点

  • ⚠️ 易错点1:JDK 动态代理的目标类必须有接口,否则 Proxy.newProxyInstance() 直接报错;

  • ⚠️ 易错点2:CGLIB 无法代理 final 类和 final 方法,因为 Java 的继承机制不支持;

  • ⚠️ 易错点3InvocationHandler.invoke()不要递归调用代理对象自身的方法,否则会无限循环-13

  • ⚠️ 易错点4:混淆 Spring Framework 和 Spring Boot 的默认代理策略——前者优先 JDK,后者默认 CGLIB。

下篇预告

下一篇我们将深入 Spring AOP 的源码实现,剖析 AnnotationAwareAspectJAutoProxyCreator 的代理创建全流程,以及 MethodInterceptor 调用链的完整执行模型,带大家从“会用 AOP”进阶到“看懂 AOP 源码”-29。敬请期待!

王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2026  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部