在Java后端开发体系中,动态代理(Dynamic Proxy)是一项绕不开的核心技术,无论是Spring AOP的声明式事务管理,还是MyBatis的Mapper接口代理,其底层都离不开动态代理的支持-49。然而很多开发者在实际项目中只会“拿来就用”,一旦被问及底层原理、JDK与CGLIB的区别,或在面试中被要求对比两种代理方式的优劣时,往往支支吾吾说不清楚。本文由豆豆包AI助手整理,将通过痛点对比→核心概念→代码示例→底层原理→面试考点这一完整链路,帮你系统掌握动态代理,直击面试高频扣分点。
一、痛点切入:为什么需要动态代理?

痛点展示:静态代理的“笨重”
假设我们有一个用户服务接口和一个简单的实现类:

// 业务接口 public interface UserService { void addUser(String name); void deleteUser(Long id); } // 目标类 public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("添加用户:" + name); } @Override public void deleteUser(Long 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("【日志】开始执行addUser方法"); long start = System.currentTimeMillis(); target.addUser(name); long end = System.currentTimeMillis(); System.out.println("【耗时】" + (end - start) + "ms"); } // deleteUser方法同样要写一遍相同的增强逻辑... // 如果接口有20个方法,就要写20份重复代码 }
静态代理的三大致命缺陷
类爆炸问题:每个目标类都需要对应一个代理类。系统中若有100个Service,就要写100个代理类。
接口变更维护困难:接口每增加一个方法,代理类也必须同步新增对应方法,违背开闭原则。
增强逻辑难以复用:日志、事务、权限等通用逻辑在每个代理方法中重复编写,代码冗余度极高-11。
正是因为静态代理存在这些根本性局限,Java才在JDK 1.3版本中引入了动态代理机制——在程序运行时动态生成代理类和代理对象,无需手动编写任何代理代码,真正实现“一次编写,处处生效”-49。
二、核心概念讲解:JDK动态代理
2.1 InvocationHandler接口
英文全称:java.lang.reflect.InvocationHandler
中文释义:调用处理器接口,是动态代理逻辑的统一处理者。
InvocationHandler接口中仅定义了一个方法invoke(),代理对象上所有方法的调用最终都会转发到这个方法中处理-3。开发者只需实现该接口,在invoke方法中编写增强逻辑即可。
类比理解:InvocationHandler就像客服总台,无论你打客服电话问什么问题(对应不同的方法),都会先被总台接听,由总台统一记录、分发和转发,而不是每个问题都要找不同的人处理。
2.2 Proxy类
英文全称:java.lang.reflect.Proxy
中文释义:动态代理工具类,负责在运行时创建代理类和代理实例。
Proxy类提供了静态方法newProxyInstance(),通过指定类加载器、接口数组和调用处理器三个参数,在内存中动态生成一个实现了指定接口的代理对象-1。所有通过Proxy创建的动态代理类都继承自Proxy类本身-8。
2.3 核心组件小结
| 组件 | 作用 |
|---|---|
| InvocationHandler | 定义代理逻辑的处理者,实现invoke方法编写增强代码 |
| Method | 反射中的方法对象,用于在运行时调用目标方法 |
| Proxy | JDK提供的工具类,用于动态生成代理类并创建代理实例 |
三、关联概念讲解:CGLIB动态代理
3.1 定义
英文全称:CGLIB(Code Generation Library)
中文释义:代码生成库,通过生成目标类的子类来实现代理,可代理没有实现接口的普通类-31。
3.2 与JDK动态代理的关系
JDK动态代理和CGLIB是Java动态代理的两种具体实现方式。JDK动态代理是JDK内置的,只能代理接口;CGLIB是第三方库,可以代理没有接口的普通类。前者基于反射,后者基于字节码操作。
3.3 核心原理对比
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 实现指定接口 | 继承目标类生成子类 |
| 对目标类要求 | 必须实现至少一个接口 | 不需要接口,但类/方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码增强 |
| 创建代理速度 | 较快 | 较慢(需生成字节码) |
| 方法调用性能 | JDK8之前较慢,JDK9+优化后差距缩小 | 通常更快 |
| 常用场景 | Spring AOP中代理有接口的Bean | Spring AOP中代理无接口的Bean |
一句话概括:JDK动态代理是“Java官方标配”,轻量但要求多;CGLIB是“灵活替补选手”,万能但有代价(不能代理final方法)-21。
3.4 JDK动态代理为什么只能代理接口?
这是由JDK动态代理的底层实现机制决定的。Proxy类在运行时通过字节码生成技术创建的代理类,会继承Proxy类。由于Java不允许多重继承,代理类无法再继承其他类,因此只能通过实现接口的方式来代理目标对象-6。如果传入非接口类型(如普通Class对象),会直接抛出IllegalArgumentException-。
四、代码示例:JDK动态代理完整实战
4.1 目标接口与实现
// 1. 业务接口(JDK动态代理必须依赖接口) public interface UserService { void addUser(String name); void deleteUser(Long id); } // 2. 目标实现类 public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("【业务】添加用户:" + name); } @Override public void deleteUser(Long id) { System.out.println("【业务】删除用户:" + id); } }
4.2 实现InvocationHandler
// 3. 调用处理器:统一编写增强逻辑 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("【增强】前置处理 - 开始执行方法:" + method.getName()); long start = System.currentTimeMillis(); // 核心:反射调用目标对象的方法 Object result = method.invoke(target, args); // 后置增强:性能统计 long end = System.currentTimeMillis(); System.out.println("【增强】后置处理 - 方法执行耗时:" + (end - start) + "ms"); return result; } }
4.3 创建代理对象并执行
// 4. 创建代理对象并调用 public class ProxyDemo { public static void main(String[] args) { // 目标对象 UserService target = new UserServiceImpl(); // 动态创建代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 代理类要实现的接口数组 new LogInvocationHandler(target) // 调用处理器 ); // 调用代理对象的方法——会自动触发LogInvocationHandler中的增强逻辑 proxy.addUser("张三"); proxy.deleteUser(10086L); } }
4.4 执行流程解读
当你调用proxy.addUser("张三")时,背后发生的事情是:
代理对象的方法被调用 → 自动转发给
LogInvocationHandler.invoke()方法invoke()中先执行前置增强逻辑(日志记录)通过
method.invoke(target, args)反射调用真正的目标对象方法执行后置增强逻辑(性能统计)
返回结果
整个过程对业务代码完全透明——目标类UserServiceImpl没有任何改动,增强逻辑被统一封装在InvocationHandler中,这就是动态代理“无侵入式增强”的核心魅力-51。
五、底层原理与技术支撑
5.1 核心依赖:Java反射机制
动态代理的底层基础是Java反射(Reflection) ——即程序在运行时获取类信息并动态操作对象的能力。在invoke()方法中,method.invoke(target, args)正是通过反射来调用目标对象的方法-41。
5.2 字节码生成技术
JDK动态代理在调用Proxy.newProxyInstance()时,JVM会在内存中动态生成一个代理类的字节码(类名通常为$Proxy0),这个类:
继承自
java.lang.reflect.Proxy类实现了指定的所有接口
将所有接口方法调用统一转发给
InvocationHandler.invoke()-6
5.3 CGLIB的技术选型
CGLIB则选择了更底层的ASM字节码操作框架,通过直接操作字节码生成目标类的子类来实现代理。ASM可以在类被加载到JVM之前动态改变类行为,CGLIB正是利用了这一能力-31-。
一句话总结:JDK动态代理 = 反射机制 + Proxy动态生成接口实现类;CGLIB = ASM字节码操作 + 继承生成子类。
六、高频面试题与参考答案
面试题1:Java的动态代理是基于什么原理?
参考答案要点:Java的动态代理基于反射机制实现,通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口配合完成。运行时,通过Proxy.newProxyInstance()动态生成一个实现了指定接口的代理类,该代理类将所有方法调用转发给InvocationHandler的invoke()方法,开发者可在invoke()中编写前置/后置增强逻辑-41。
💡 踩分点:提到“反射机制”、“Proxy + InvocationHandler”、“运行时生成代理类”三个关键词即可拿分。
面试题2:JDK动态代理和CGLIB有什么区别?各自的使用场景是什么?
参考答案要点:
| 维度 | JDK动态代理 | CGLIB |
|---|---|---|
| 实现原理 | 反射 + 动态生成接口实现类 | ASM字节码增强 + 生成子类 |
| 代理方式 | 实现接口 | 继承类 |
| 目标类要求 | 必须实现接口 | 不需要接口,但不能是final |
| 性能 | JDK9+优化后与CGLIB差距缩小 | 运行期性能通常更高 |
| 应用场景 | Spring AOP代理有接口的Bean | Spring AOP代理无接口的Bean |
💡 踩分点:必须同时说出两种方式的核心区别和各自适用场景-42-21。
面试题3:Spring AOP中默认使用哪种动态代理?如何强制使用CGLIB?
参考答案要点:Spring AOP默认使用JDK动态代理(当目标类实现接口时),若目标类没有实现任何接口,则自动切换为CGLIB。可通过在配置类添加@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB代理-48。
💡 踩分点:说出“默认JDK代理”、“无接口时自动切换CGLIB”、“proxyTargetClass配置”三个关键点。
面试题4:JDK动态代理为什么不能代理普通类(无接口的类)?
参考答案要点:因为Proxy.newProxyInstance()动态生成的代理类会继承Proxy类,而Java是单继承语言,代理类无法再继承其他类,因此只能通过实现接口的方式来代理目标对象。若要代理无接口的类,必须使用CGLIB-6。
💡 踩分点:核心是“代理类已继承Proxy,无法多重继承”。
七、结尾总结
本文由豆豆包AI助手带你系统梳理了Java动态代理的完整知识链路:
✅ 静态代理的痛点:类爆炸、维护难、逻辑重复,催生了动态代理的诞生
✅ JDK动态代理核心:InvocationHandler(定义增强逻辑)+ Proxy(创建代理对象)
✅ CGLIB补充方案:ASM字节码生成子类,弥补JDK不能代理普通类的短板
✅ 底层原理:JDK依赖反射 + 动态字节码生成,CGLIB依赖ASM字节码框架
✅ 面试高频题:原理、两种代理区别、Spring AOP中的实际应用
易错点提醒:不要混淆“代理对象执行的是InvocationHandler.invoke()”与“代理对象执行的是目标方法”——前者才是真实情况,后者只是错觉。动态代理并非直接调用目标方法,而是通过method.invoke()反射调用。
🔜 下篇预告:我们将深入Spring AOP源码,剖析ProxyFactory如何根据目标类的接口情况自动选择JDK还是CGLIB代理,以及AOP拦截器链的完整执行流程。