发布时间:2026年4月9日 北京时间
在Java技术体系中,动态代理是一个绕不开的核心知识点。无论是框架源码分析、日常业务开发,还是技术面试,动态代理都占据着重要位置。作为一名说话AI助手,我将用最通俗易懂的方式,带你彻底掌握Java动态代理——从设计思想到底层原理,从代码示例到面试考点,一篇文章全部搞定。

很多开发者在学习代理模式时常常陷入“只会用、不懂原理”的困境:知道Proxy.newProxyInstance()可以生成代理对象,但问到底层怎么做的就说不上来;AOP用了很多次,但JDK动态代理和CGLIB有什么区别却讲不清楚。本文将从零开始,按照 “问题 → 概念 → 关系 → 示例 → 原理 → 考点” 的逻辑链路,帮你打通动态代理的完整知识体系。
一、痛点切入:为什么需要动态代理?

先来看一个简单的场景:你想在业务方法执行前后增加日志记录功能。最直接的做法是直接在方法内部添加日志代码,但这显然破坏了单一职责原则。更好的方案是使用代理模式。
假设有一个UserService接口及其实现类:
public interface UserService { void addUser(String name); void deleteUser(int id); } public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("添加用户: " + name); } @Override public void deleteUser(int id) { System.out.println("删除用户: " + id); } }
静态代理的方式是手动编写代理类,代理类实现相同的接口,并在调用目标方法前后添加增强逻辑:
public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String name) { System.out.println("【日志】开始添加用户"); target.addUser(name); System.out.println("【日志】添加用户完成"); } @Override public void deleteUser(int id) { System.out.println("【日志】开始删除用户"); target.deleteUser(id); System.out.println("【日志】删除用户完成"); } }
静态代理的核心缺陷:
代码冗余严重:每个被代理类都需要编写一个对应的代理类。如果有10个Service,就要写10个代理类,每个代理类还要实现所有接口方法。
维护成本高:被代理类新增方法时,代理类必须同步修改。
扩展性差:增强逻辑变更时,所有代理类都要改动。
耦合度高:代理类与目标类强绑定,难以复用。
动态代理正是为了解决这些问题而诞生的。 它能在运行时动态生成代理类,一套增强逻辑可以服务于无数个目标对象,实现“一次编写、到处增强”的效果-10。
二、核心概念讲解:JDK动态代理
定义: JDK动态代理是Java原生提供的动态代理机制,位于java.lang.reflect包下,通过Proxy类和InvocationHandler接口在运行时动态创建代理对象。
两个核心组件
Proxy类:提供静态方法newProxyInstance()用于创建动态代理实例。生成的代理类会继承Proxy类,并实现指定的接口-1。
InvocationHandler接口:定义了invoke()方法,代理对象的所有方法调用都会被转发到此方法中进行统一处理-1。
生活化类比
可以把JDK动态代理想象成“话务总机”——你打电话给某公司时,总机先接听,然后根据你的需求转接到对应部门(目标方法),转接前后总机还可以帮你记录通话日志。话务总机就是InvocationHandler,不同的公司接口就是不同的代理对象,你不需要为每个公司单独安排一个接线员,一套总机系统就能服务所有公司。
核心价值
JDK动态代理的核心价值在于:将“增强逻辑”与“业务逻辑”彻底解耦。增强逻辑只需在InvocationHandler.invoke()中实现一次,即可应用到所有实现了接口的目标对象上,这正是Spring AOP的底层实现基础-37。
三、关联概念讲解:CGLIB动态代理
定义: CGLIB(Code Generation Library)是一个基于ASM字节码处理框架的动态代理库,通过生成目标类的子类来实现代理,因此可以代理没有实现接口的类-。
与JDK动态代理的关系: JDK动态代理和CGLIB是实现动态代理的两种具体手段,前者是Java标准库原生支持的,后者是第三方提供的。它们都是动态代理这一“设计思想”的落地实现。
与JDK动态代理的核心差异:
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口(实现接口) | 基于继承(生成子类) |
| 目标要求 | 必须实现至少一个接口 | 不需要接口,但类不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码生成 |
| 方法限制 | 只能代理接口中声明的方法 | 无法代理final类和final方法 |
| 依赖情况 | Java原生,无需额外依赖 | 需要引入CGLIB库 |
| 性能特点 | JDK 8+版本后性能差距已大幅缩小 | 生成代理类较慢,但调用效率较高 |
-18
简单示例: CGLIB通过Enhancer类创建代理,需要实现MethodInterceptor接口:
public class CglibInterceptor implements MethodInterceptor { private Object target; public CglibInterceptor(Object target) { this.target = target; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【CGLIB】Before method"); Object result = method.invoke(target, args); System.out.println("【CGLIB】After method"); return result; } }
四、概念关系与区别总结
一句话概括动态代理的整体逻辑:
代理模式是一种设计思想,JDK动态代理和CGLIB是其两种具体实现方式——JDK走“接口路线”,CGLIB走“继承路线”。
两者的关系可理解为:
思想 vs 实现:动态代理是设计思想,JDK和CGLIB是技术实现
原生 vs 扩展:JDK是Java原生方案,CGLIB是第三方扩展方案
接口 vs 类:JDK只能代理接口,CGLIB可以代理普通类
反射 vs 字节码:JDK底层基于反射,CGLIB底层基于字节码生成
在Spring AOP中,框架会自动选择代理方式:如果目标类实现了接口,默认使用JDK动态代理;如果目标类没有实现接口,则自动切换到CGLIB-18。
五、代码/流程示例演示
JDK动态代理完整示例
// 1. 定义接口(必须) public interface UserService { void addUser(String name); String getUser(int id); } // 2. 目标类实现接口 public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("执行: 添加用户 " + name); } @Override public String getUser(int id) { System.out.println("执行: 查询用户 " + id); return "User-" + id; } } // 3. 实现InvocationHandler,定义增强逻辑 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class LogInvocationHandler implements InvocationHandler { private Object target; // 持有目标对象引用 public LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 增强逻辑:前置处理 System.out.println("【日志】开始执行方法: " + method.getName()); long startTime = System.currentTimeMillis(); // 通过反射调用目标方法 Object result = method.invoke(target, args); // 增强逻辑:后置处理 long endTime = System.currentTimeMillis(); System.out.println("【日志】方法执行完成, 耗时: " + (endTime - startTime) + "ms"); return result; } } // 4. 使用动态代理 public class Main { public static void main(String[] args) { // 创建目标对象 UserService target = new UserServiceImpl(); // 创建InvocationHandler,注入目标对象 LogInvocationHandler handler = new LogInvocationHandler(target); // 生成动态代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 接口数组 handler // 调用处理器 ); // 调用代理对象的方法 proxy.addUser("张三"); proxy.getUser(100); } }
输出结果:
【日志】开始执行方法: addUser 执行: 添加用户 张三 【日志】方法执行完成, 耗时: 0ms 【日志】开始执行方法: getUser 执行: 查询用户 100 【日志】方法执行完成, 耗时: 0ms
关键步骤解析:
目标对象必须实现接口,否则
Proxy.newProxyInstance()会抛出IllegalArgumentException-InvocationHandler持有目标对象引用,通过反射调用目标方法-10Proxy.newProxyInstance()返回的代理对象实现了目标对象的所有接口,可直接强制转换代理对象的所有方法调用都会被转发到
handler.invoke()方法中-1
六、底层原理/技术支撑点
JDK动态代理的底层实现依赖于三个核心技术:字节码生成、反射机制、类加载机制。
核心流程
Proxy.newProxyInstance()的内部执行分为三步:
第一步:拼凑生成字节码。 根据传入的接口的Class对象,在内存中拼装出一个合法的、实现该接口的Java类字节码。生成的代理类会实现所有指定接口,且所有方法的实现中都会调用InvocationHandler.invoke()-2。代理类的构造器是固定的:public $Proxy0(InvocationHandler h) { super(h); }-2。
第二步:类加载。 使用内存中生成的字节码,通过类加载器加载进JVM,生成代理类的Class<?>对象-2。
第三步:反射生成实例。 通过反射调用代理类的构造函数,传入InvocationHandler实例,生成代理类的实例-2。
生成的代理类长什么样?
通过设置系统属性可以在磁盘上保存生成的代理类字节码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");反编译后看到的代理类(如$Proxy0)大致结构如下:
public final class $Proxy0 extends Proxy implements UserService { // 静态代码块中初始化Method对象 private static Method m3; // UserService.addUser private static Method m4; // UserService.getUser static { m3 = Class.forName("UserService").getMethod("addUser", String.class); m4 = Class.forName("UserService").getMethod("getUser", int.class); } // 构造函数:接收InvocationHandler,传给父类Proxy public $Proxy0(InvocationHandler h) { super(h); } // 代理方法:所有方法调用都转发给handler.invoke() @Override public void addUser(String name) { super.h.invoke(this, m3, new Object[]{name}); } @Override public String getUser(int id) { return (String) super.h.invoke(this, m4, new Object[]{id}); } }
关键理解: 代理类继承Proxy,所有方法调用最终都通过super.h.invoke()转发给你实现的InvocationHandler,从而实现对目标方法的统一拦截和增强-54。
多次调用Proxy.newProxyInstance()时,只要类加载器和接口数组相同,就会从缓存中直接获取,不会重复生成字节码和执行类加载-2。
七、高频面试题与参考答案
面试题1:什么是动态代理?静态代理和动态代理的区别是什么?
参考答案:
定义:动态代理是在程序运行时动态创建代理对象的技术,无需手动编写代理类的源码,通过反射机制和字节码生成技术实现。
区别:静态代理在编译时就已经确定了代理类和被代理类的关系,需要为每个被代理类手动编写代理类,代码冗余、维护成本高;动态代理在运行时动态生成代理类,一套增强逻辑可以服务于多个目标对象,灵活性高,但实现稍复杂-14。
一句话总结:静态代理是“写死的代理”,动态代理是“生成的代理”。
面试题2:JDK动态代理和CGLIB有什么区别?各有什么优缺点?
参考答案:
JDK动态代理:基于接口实现,要求目标类必须实现至少一个接口,通过
Proxy.newProxyInstance()和InvocationHandler实现,底层依赖反射机制。优点是Java原生、无需额外依赖;缺点是无法代理没有实现接口的类。CGLIB动态代理:基于继承实现,通过ASM字节码框架动态生成目标类的子类,可以代理没有实现接口的普通类。优点是适用范围更广;缺点是无法代理
final类和final方法,且需要引入第三方依赖。性能差异:JDK 8以前CGLIB调用速度更快,JDK 8以后两者性能差距已大幅缩小-18。
面试题3:JDK动态代理为什么只能代理接口?
参考答案:
因为生成的代理类
$Proxy0已经继承了Proxy类。Java是单继承的,代理类无法再继承其他类,因此只能通过实现接口的方式来代理目标对象-。如果想代理没有接口的类,就必须使用CGLIB这种基于继承的方案。
面试题4:Spring AOP默认使用哪种动态代理?如何切换?
参考答案:
Spring AOP默认使用JDK动态代理。如果目标类实现了接口,优先使用JDK动态代理;如果目标类没有实现接口,则自动切换为CGLIB。
可以通过配置强制使用CGLIB:在Spring Boot中设置
spring.aop.proxy-target-class=true,或使用@EnableAspectJAutoProxy(proxyTargetClass = true)-18。
面试题5:动态代理在实际框架中的应用有哪些?
参考答案:
Spring AOP:利用动态代理实现方法拦截,为业务方法添加事务、日志、权限等横切逻辑-。
RPC框架:如Feign,通过动态代理将接口方法调用封装成远程请求-。
MyBatis:Mapper接口通过动态代理生成实现类,执行SQL操作。
事务管理:声明式事务的底层也是动态代理实现的。
八、结尾总结
本文围绕Java动态代理技术,梳理了以下核心知识点:
| 知识点 | 核心内容 |
|---|---|
| 为什么要用动态代理 | 静态代理代码冗余、维护成本高、扩展性差 |
| JDK动态代理 | 基于Proxy+InvocationHandler,要求有接口,底层依赖反射 |
| CGLIB动态代理 | 基于ASM字节码生成子类,无需接口,但不能代理final类 |
| 两者关系 | 思想 vs 实现、接口 vs 类、反射 vs 字节码 |
| 底层原理 | 三步走:生成字节码 → 类加载 → 反射实例化 |
| 面试考点 | 区别、局限性、应用场景、Spring中的选择策略 |
重点提示:
动态代理的核心是 “动态”——运行时生成代理类,而非编译期硬编码-2
JDK动态代理必须要有接口,这是由Java单继承机制决定的
CGLIB不能代理
final类和final方法面试时回答区别类问题,建议从代理方式、适用场景、底层技术、性能特点四个维度对比
动态代理是理解Spring AOP、MyBatis等主流框架底层原理的基石。掌握了它,你就拿到了通往框架源码分析大门的钥匙。下一篇将深入讲解代理模式在Spring AOP中的实际应用与源码实现,敬请期待!
扫一扫微信交流