代理模式是Java框架底层应用最广泛的设计模式之一,但很多开发者长期陷入“只会用、不懂原理”的困境——面试被问“JDK动态代理为什么只能代理接口”时答不上来,写AOP时也分不清何时用JDK何时用CGLIB。本文将从静态代理→JDK动态代理→CGLIB动态代理逐层递进,讲清核心概念、写出可运行代码、剖析底层原理、汇总高频面试题,帮你建立完整知识链路。
一、痛点切入:为什么需要代理模式?

先看一个最常见的场景:为UserService添加日志记录功能。很多初学者会直接在业务方法内部写日志代码:
public class UserServiceImpl {public void saveUser() { System.out.println("[日志]保存用户方法开始执行"); // 核心业务逻辑 System.out.println("保存用户数据"); System.out.println("[日志]保存用户方法执行结束"); } }
这段代码有什么问题?
耦合高:日志代码与业务代码纠缠在一起,修改日志格式需要改动业务类
代码冗余:每个需要日志的方法都要重复编写类似代码
扩展性差:若需新增事务、权限校验等功能,每个方法都得改一遍
违反单一职责原则:业务类既要管核心逻辑,又要管辅助功能
为了解决这些问题,代理模式应运而生——在客户端和目标对象之间引入一个代理对象,由代理对象统一处理辅助逻辑,业务类只需专注核心功能。
二、概念 A:静态代理
标准定义
静态代理(Static Proxy) :代理类在编译期就已确定,需要为每个目标类显式编写对应的代理类,代理类与目标类实现相同的接口,并在内部持有目标对象的引用-4。
生活化类比
代理模式在生活中随处可见:火车站售票与代售点。火车站负责核心卖票业务,代售点作为代理——它先收取服务费(前置增强),再把请求转发给火车站(调用目标方法)-1。
代码示例
// 1. 抽象主题:统一业务接口 public interface UserService { void saveUser(); } // 2. 真实主题:只负责核心业务 public class UserServiceImpl implements UserService { @Override public void saveUser() { System.out.println("保存用户数据"); } } // 3. 静态代理类:实现同一接口,持有目标对象引用 public class UserServiceProxy implements UserService { private UserService target; // 持有目标对象 public UserServiceProxy(UserService target) { this.target = target; } @Override public void saveUser() { System.out.println("[静态代理]前置日志"); // 前置增强 target.saveUser(); // 调用目标方法 System.out.println("[静态代理]后置事务"); // 后置增强 } } // 4. 客户端调用 public class Client { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.saveUser(); } }
优缺点分析
| 优点 | 缺点 |
|---|---|
| 符合开闭原则,无需修改目标对象 | 类爆炸:每个业务类都要写一个代理类-4 |
| 职责清晰,辅助逻辑集中在代理类 | 接口耦合:代理类必须实现与目标类相同的接口 |
| 编译期检查,类型安全有保障 | 维护成本高:接口新增方法,代理类和目标类都得改- |
随着项目规模扩大,上述缺点会变得非常突出——这就是动态代理诞生的直接原因。
三、概念 B:动态代理
标准定义
动态代理(Dynamic Proxy) :代理类在运行时由JVM动态生成,无需手动编写代理类代码。一个动态代理类可以为任意多个真实类提供代理服务-56。
动态代理的两大实现方式
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 代理方式 | 基于接口实现 | 基于继承生成子类 |
| 目标对象要求 | 必须实现至少一个接口 | 无接口要求,但不能是final类 |
| 底层技术 | 反射 + Proxy | ASM字节码生成框架 |
| 性能特点 | JDK 8后性能持续优化 | 生成类稍慢,调用接近直接调用-38 |
| 依赖 | Java标准库(无需额外依赖) | 需引入CGLIB库 |
| 限制 | 只能代理接口中定义的方法 | 无法代理final类和final方法-27 |
JDK动态代理代码示例
// 1. 目标接口 public interface UserService { void saveUser(); } // 2. 目标类 public class UserServiceImpl implements UserService { @Override public void saveUser() { System.out.println("保存用户数据"); } } // 3. InvocationHandler:代理逻辑处理器 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()); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("[动态代理]方法执行后"); return result; } } // 4. 客户端调用 public class Client { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 代理类要实现的接口列表 new LogInvocationHandler(target) // 调用处理器 ); proxy.saveUser(); } }
CGLIB动态代理代码示例
// 引入Maven依赖 // <dependency> // <groupId>cglib</groupId> // <artifactId>cglib</artifactId> // <version>3.2.6</version> // </dependency> // 1. 目标类(无需实现接口) public class UserService { public void saveUser() { System.out.println("保存用户数据"); } } // 2. MethodInterceptor:方法拦截器 public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("[CGLIB]前置处理:" + method.getName()); Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法 System.out.println("[CGLIB]后置处理"); return result; } } // 3. 客户端调用 public class Client { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); // 设置父类(目标类) enhancer.setCallback(new LogMethodInterceptor()); // 设置回调 UserService proxy = (UserService) enhancer.create(); proxy.saveUser(); } }
四、概念关系与区别总结
一句话总结:静态代理是“写死”的代理,动态代理是“运行时生成”的代理;JDK动态代理基于接口,CGLIB动态代理基于继承。
代理模式
静态代理
动态代理
JDK动态代理
基于接口+反射
CGLIB动态代理
基于继承+字节码
| 对比项 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 代理类生成时机 | 编译期 | 运行期 | 运行期 |
| 是否需要手动编写代理类 | 是 | 否 | 否 |
| 目标对象要求 | 需实现接口 | 必须实现接口 | 无需接口(非final即可) |
| 性能 | 直接调用,最快 | 反射调用,JDK8后显著优化 | 生成类较慢,调用接近直接调用 |
| 灵活性 | 差 | 好 | 好 |
| 典型应用 | 固定场景 | Spring AOP代理接口 | Spring AOP代理普通类、Hibernate懒加载 |
五、底层原理 / 技术支撑
JDK动态代理底层原理
JDK动态代理的本质是动态生成字节码 + 反射机制的结合-49。
调用Proxy.newProxyInstance()时,JVM执行以下步骤:
查找或生成代理类:调用
getProxyClass0(),先从WeakCache缓存中查找,若无则生成生成代理类字节码:通过
ProxyGenerator.generateProxyClass()动态生成代理类的二进制字节码,代理类命名格式为$Proxy0、$Proxy1等-67加载代理类:通过类加载器将字节码加载到JVM中
创建代理实例:通过反射获取构造方法并实例化
生成的代理类继承java.lang.reflect.Proxy,实现了传入的所有接口。代理类中的每个方法都会将调用转发给InvocationHandler.invoke()方法-。
关键源码流程:
// Proxy.newProxyInstance() → getProxyClass0() → proxyClassCache.get() // → ProxyClassFactory.apply() → 生成字节码 → defineClass0()加载
CGLIB动态代理底层原理
CGLIB(Code Generation Library)通过ASM字节码框架在运行时动态生成目标类的子类:
使用
Enhancer设置目标类作为父类通过ASM技术生成目标类的子类字节码,子类重写所有非final方法
方法调用被拦截到
MethodInterceptor.intercept()方法在
intercept()中完成增强逻辑后,通过MethodProxy.invokeSuper()调用父类方法-27
静态代理的底层支撑
静态代理直接调用目标方法,不依赖反射或字节码生成,因此性能最高,但灵活性最差。
一句话说透:静态代理是编译期“写死”的调用链;JDK动态代理是运行期“拼出来”的字节码加反射调用;CGLIB是运行期“生出来”的子类加方法拦截。
六、高频面试题与参考答案
面试题1:代理模式是什么?有什么优缺点?
标准答案要点:
代理模式属于结构型设计模式,通过引入代理对象控制对目标对象的访问-
优点:符合开闭原则,降低耦合度,可在不修改目标对象的前提下增强功能-56
缺点:静态代理会产生大量代理类;增加代理对象会使请求处理速度略慢-58
面试题2:JDK动态代理为什么只能代理接口?
标准答案要点:
生成的代理类已经继承了
Proxy类,Java不支持多继承,所以无法再继承目标类代理类通过实现目标接口来“冒充”目标对象
若目标类没有实现任何接口,JDK动态代理无法创建代理-58
面试题3:JDK动态代理和CGLIB动态代理的区别是什么?
标准答案要点:
| 区别维度 | 说明 |
|---|---|
| 代理方式 | JDK基于接口,CGLIB基于继承生成子类 |
| 目标要求 | JDK要求目标类实现接口;CGLIB无此要求,但不能代理final类/方法 |
| 底层技术 | JDK使用反射+Proxy;CGLIB使用ASM字节码生成 |
| 性能 | JDK8后两者差距缩小;CGLIB生成代理类较慢,但调用效率高 |
| 依赖 | JDK是标准库;CGLIB需额外引入 |
| 选择建议 | 有接口优先用JDK,无接口或用Spring AOP时可自动切换-35 |
面试题4:Spring AOP中默认使用哪种代理?
标准答案要点:
Spring AOP默认对接口实现类使用JDK动态代理
若目标对象未实现任何接口,或强制使用CGLIB(
proxy-target-class="true"),则使用CGLIB动态代理Spring 4.0后,CGLIB代理的性能已大幅提升-
面试题5:静态代理和动态代理的根本区别是什么?
标准答案要点:
根本区别在于代理类的生成时机:静态代理在编译期生成,动态代理在运行期生成
静态代理需要手动编写代理类代码,一个目标类对应一个代理类
动态代理由JVM在运行时动态生成字节码,一个代理类可为多个目标类服务-56
七、结尾总结
核心知识点回顾
| 序号 | 核心知识点 |
|---|---|
| 1 | 代理模式通过引入代理对象控制对目标对象的访问,核心价值在于解耦与功能增强 |
| 2 | 静态代理在编译期确定代理关系,实现简单但扩展性差,会导致类爆炸 |
| 3 | JDK动态代理基于接口+反射,只能代理实现了接口的类 |
| 4 | CGLIB动态代理基于继承+ASM字节码,可代理无接口的普通类,但不能代理final类/方法 |
| 5 | 底层原理上,JDK动态代理=字节码动态生成+反射调用;CGLIB=生成子类+方法拦截 |
| 6 | 实际开发中,Spring AOP会根据目标对象是否实现接口自动选择合适的代理方式 |
重点与易错点
易错点1:误以为JDK动态代理可以代理普通类——不行,目标类必须实现接口
易错点2:误以为CGLIB可以代理所有类——不行,final类、final方法无法代理
重点理解:静态代理 vs 动态代理的本质区别——代理类生成时机不同
面试必考点:JDK vs CGLIB的代理方式、限制、底层原理
进阶预告
下一篇将深入Spring AOP底层源码,剖析Spring如何利用动态代理实现声明式事务管理,以及@Transactional注解失效的根本原因。欢迎持续关注!

扫一扫微信交流