在Java面试中,“动态代理”几乎是个必考的知识点。很多人会用Spring AOP,却说不清它底层到底是怎么“动态”起来的。JDK动态代理和CGLIB有什么区别?为什么Spring默认优先用JDK?本文AI助手小猫将从零开始,带你彻底搞懂动态代理的核心原理、手写代码示例和高频面试题。
一、痛点切入:为什么需要动态代理?

先来看一个常见场景:你有一个UserService,需要在每个方法执行前后记录日志、统计耗时。
public class UserServiceImpl implements UserService {@Override public void createUser(String name) { System.out.println("创建用户:" + name); } @Override public void deleteUser(Long id) { System.out.println("删除用户:" + id); } }
如果你在每个方法里都手动加上日志代码,很快就会发现——代码重复、耦合度高、维护困难。更糟糕的是,如果项目中有几十上百个Service,每个都要这样改一遍。
静态代理:一个笨办法
静态代理的思路是:为每个目标类单独写一个代理类,实现相同接口,在代理类中嵌入增强逻辑。
public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void createUser(String name) { System.out.println("开始执行 createUser"); target.createUser(name); System.out.println("执行结束"); } }
静态代理的三大硬伤:①每个接口都要单独写一个代理类,代码量翻倍;②接口新增方法时,代理类必须同步修改;③当需要批量代理几十个Service时,几乎不可行-8。
动态代理:解决方案
动态代理的魔力在于:在程序运行时,由JVM动态生成代理类和代理对象,无需手动编写代理代码。Java中主要有两种实现方式:JDK动态代理和CGLIB动态代理-8。
二、核心概念:JDK动态代理
什么是JDK动态代理?
JDK动态代理(JDK Dynamic Proxy) 是Java标准库提供的一种基于接口的动态代理机制。它允许在运行期动态创建某个接口的实例,并将方法调用“代理”给InvocationHandler来处理-15。
生活化类比:你找商务谈需求,商务就是你的“代理”。你只需要说“我要什么功能”,商务负责去协调研发团队。商务和研发之间有一套标准流程,就像JDK动态代理中InvocationHandler负责统一处理所有方法调用。
三大核心组件
JDK动态代理由以下三个核心组件构成-8:
InvocationHandler(调用处理器) :你需要实现这个接口,在
invoke()方法中编写方法调用前后的增强逻辑。Method(方法对象) :代表被调用的方法,通过它可以利用反射机制调用目标方法。
Proxy(代理类) :JDK提供的工具类,核心方法是
Proxy.newProxyInstance(),用于动态生成代理类并创建代理实例。
极简代码示例
// 1. 定义接口 public interface UserService { void createUser(String name); } // 2. 实现目标类 public class UserServiceImpl implements UserService { @Override public void createUser(String name) { System.out.println("创建用户:" + name); } } // 3. 自定义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 { System.out.println("【前置】开始执行:" + method.getName()); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("【后置】执行结束"); return result; } } // 4. 使用Proxy创建代理对象 UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogHandler(target) ); proxy.createUser("张三"); // 输出: // 【前置】开始执行:createUser // 创建用户:张三 // 【后置】执行结束
执行流程
当调用proxy.createUser("张三")时,底层发生的是:方法调用被拦截并转发到LogHandler.invoke()方法,由它在内部通过反射调用目标对象的原始方法-1。
三、关联概念:CGLIB动态代理
什么是CGLIB?
CGLIB(Code Generation Library) 是一个基于ASM字节码操作框架的代码生成库。它通过动态生成目标类的子类来实现代理,不要求目标类实现任何接口-20-30。
一句话概括:JDK动态代理 = 给“有接口的类”做代理;CGLIB动态代理 = 给“没接口的普通类”也能做代理。
两大核心组件
MethodInterceptor:自定义的拦截器接口,实现
intercept()方法定义增强逻辑-20。Enhancer:增强器类,用于配置目标类、设置回调、生成代理子类-20。
极简代码示例
// 1. 目标类(无需实现接口) public class UserService { public void createUser(String name) { System.out.println("创建用户:" + name); } } // 2. 自定义MethodInterceptor import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class LogInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【前置】开始执行:" + method.getName()); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("【后置】执行结束"); return result; } } // 3. 使用Enhancer生成代理 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new LogInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.createUser("李四"); // 输出: // 【前置】开始执行:createUser // 创建用户:李四 // 【后置】执行结束
特别注意:CGLIB无法代理final类或final方法,因为Java语言规范禁止继承final类或重写final方法-20。
四、概念关系与区别总结
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 基于接口,通过反射动态生成代理类,代理类实现目标接口 | 基于继承,通过ASM生成目标类的子类作为代理类 |
| 目标类要求 | 必须实现至少一个接口 | 无需接口,但类和方法不能是final |
| 底层技术 | 反射 + Proxy | ASM字节码增强 |
| 性能特点 | JDK 8+反射优化后,调用速度提升明显,差距缩小 | 生成代理类较慢,但调用执行效率较高 |
| 依赖 | Java标准库(无需额外依赖) | 需引入CGLIB库(Spring已内置) |
| 典型应用 | Spring AOP对接口代理 | Spring AOP对无接口类代理、Hibernate懒加载 |
-29-46
一句话记忆口诀:JDK看接口,反射造代理;CGLIB看继承,字节码生子类。
五、底层原理:它是怎么“动态”起来的?
JDK动态代理的底层原理
JDK动态代理本质上是 “动态生成字节码 + 反射机制” 的结合-。Proxy.newProxyInstance()方法内部经历了三个步骤-11:
拼装生成字节码:根据传入的接口列表,在内存中拼装出一个实现了这些接口的Java类字节码。这个类会继承
Proxy类,并在每个接口方法的实现中调用InvocationHandler.invoke()。类加载:将内存中生成的字节码加载进JVM,生成代理类的
Class对象。通过反射创建实例:调用代理类的构造函数(接收
InvocationHandler作为参数),生成代理实例。
如果你打印代理类的全类名,会看到类似jdk.proxy1.$Proxy0这样的名字,这就是JDK运行时生成的代理类-11。
CGLIB动态代理的底层原理
CGLIB底层依赖ASM字节码操作框架,在运行时直接操纵字节码-25:
创建
Enhancer对象,设置目标类作为父类。设置
Callback(通常是MethodInterceptor)。调用
create()方法,CGLIB使用ASM框架动态生成一个继承自目标类的子类,覆盖所有非final方法。在生成的子类中,被覆盖的方法内部会调用
MethodInterceptor.intercept(),从而实现方法拦截和增强。
两者底层逻辑的本质差异:JDK动态代理生成的代理类实现的是接口,而CGLIB生成的代理类继承的是目标类。前者通过反射调用,后者通过字节码直接调用。
六、高频面试题与参考答案
1. JDK动态代理和CGLIB动态代理有什么区别?(⭐星标)
【答题层次】原理 → 要求 → 性能 → 场景 → 一句话总结
原理差异:JDK基于接口,利用反射动态生成代理类;CGLIB基于继承,利用ASM字节码框架生成目标类的子类。
目标类要求:JDK要求目标类必须实现接口;CGLIB无需接口,但目标类和方法不能是
final。性能差异:JDK 1.8及以上版本反射已优化,两者性能差距很小;CGLIB生成代理类较慢但调用执行快。
使用场景:JDK是Java原生,无额外依赖;CGLIB需引入库,Spring AOP会根据目标类是否有接口自动选择。
2. 为什么JDK动态代理只能代理接口?
因为JDK动态代理生成的代理类会继承java.lang.reflect.Proxy类,而Java不支持多继承,所以代理类只能通过实现接口来扩展功能。代理类的类型由传入的接口列表决定,因此只有实现了接口的类才能被代理-49。
3. 静态代理和动态代理有什么区别?
| 维度 | 静态代理 | 动态代理 |
|---|---|---|
| 创建时机 | 编译期手动编写代理类 | 运行期动态生成代理类 |
| 灵活性 | 一对一绑定,接口变更需同步修改 | 可通用适配多个目标类 |
| 代码量 | 每个接口都需要单独写代理类 | 一套横切逻辑即可批量代理 |
-56
4. Spring AOP底层用的是哪种代理?
Spring AOP默认使用策略:如果目标类实现了接口,则使用JDK动态代理;如果目标类没有实现任何接口,则自动切换为CGLIB动态代理。开发者也可以通过配置proxyTargetClass=true强制使用CGLIB-46。
5. 动态代理在实际框架中有哪些应用场景?
Spring AOP:声明式事务管理、统一日志记录、权限校验拦截-56。
MyBatis:Mapper接口的动态代理实现,将接口方法调用转换为SQL执行-。
RPC框架:将远程服务调用伪装成本地方法调用,屏蔽网络通信细节-。
七、结尾总结
回顾全文核心要点:
静态代理 vs 动态代理:静态代理需要为每个接口手动编写代理类,维护成本高;动态代理在运行期动态生成,实现“一次编写,处处生效”。
JDK动态代理:基于接口,依赖
InvocationHandler和Proxy,利用反射机制,是Java原生实现。CGLIB动态代理:基于继承,依赖
MethodInterceptor和Enhancer,利用ASM字节码框架,可代理无接口类,但无法代理final类和final方法。底层原理核心:JDK是“反射 + 动态字节码生成”,CGLIB是“ASM字节码增强 + 子类继承”。
高频考点:两种代理的区别、Spring AOP的代理策略、应用场景,是面试中的必考内容。
重点关注:面试中不仅要会回答区别,更要理解“动态”二字的本质——运行期生成,编译期不写死。这是AOP等框架得以实现的核心思想。
本文是AI助手小猫【Java进阶系列】的第一篇。下一篇我们将深入讲解反射机制的底层实现与性能优化,敬请关注!
