北京时间:2026年4月10日
一、开篇引入

在Java后端开发中,代理模式是面试必考、框架必用的核心知识点。无论是Spring AOP的声明式事务管理,还是日志拦截、权限校验,背后都离不开代理模式的支撑。不少开发者在日常工作中只会用@Transactional注解,却说不清Spring到底是用JDK动态代理还是CGLIB来织入事务逻辑;能写出Service层代码,却讲不明白静态代理和动态代理的本质区别。本文将从“为什么需要代理”这个问题出发,由浅入深地拆解代理模式的定义、静态代理与动态代理的核心差异、JDK动态代理与CGLIB的底层原理,并附上可直接运行的代码示例和面试高频考题。读完本文,你将对代理模式建立起完整的知识链路——既能看懂框架源码,也能在面试中从容应答。
二、痛点切入:为什么需要代理模式?

先看一个日常开发中再常见不过的场景:我们需要为UserService的增删改查方法统一添加日志记录。
// 目标类:专注核心业务 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { // 核心业务:新增用户 System.out.println("数据库新增用户:" + username); } @Override public void deleteUser(String username) { System.out.println("数据库删除用户:" + username); } }
传统实现方式——直接在业务代码中混入日志:
public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("【日志】开始执行addUser,参数:" + username); // 日志逻辑入侵业务代码 System.out.println("数据库新增用户:" + username); System.out.println("【日志】addUser执行完成"); } // deleteUser同理... }
上述实现存在三大痛点:
耦合高:日志逻辑与业务逻辑混在一起,违反了“单一职责原则”。
代码冗余:每个需要日志的方法都要重复编写相同的日志代码。
扩展性差:若要将日志换成性能监控或权限校验,必须逐一修改所有业务方法。
代理模式正是为解决这些问题而生。 它通过引入一个“代理对象”作为客户端与目标对象之间的中间层,让代理对象来“包裹”目标对象的方法调用,从而在不修改目标对象源码的前提下,额外增加功能。
三、核心概念讲解:代理模式
什么是代理模式?
代理模式(Proxy Pattern) 是一种结构型设计模式,为其他对象提供一种代理以控制对这个对象的访问-1。通俗地说,就是在调用目标方法时不再直接调用,而是通过代理类间接调用。代理对象可以在目标方法执行前后插入额外的操作(如日志、事务、权限校验等),从而实现对目标对象功能的扩展-。
生活化类比: 租房时,你不直接找房东,而是通过房产中介。中介在带你看房前后可以帮你做信息筛选、合同审核等额外工作,但最终签合同和交房租的还是房东本人。在这里,中介就是“代理”,房东就是“目标对象”。
代理模式的三大角色
| 角色 | 英文名称 | 作用描述 | 租房场景类比 |
|---|---|---|---|
| 抽象主题 | Subject | 定义目标对象和代理对象的共同行为接口 | “租房”这个通用行为 |
| 真实主题 | RealSubject | 实际执行业务逻辑的对象 | 房东 |
| 代理类 | Proxy | 控制对真实主题的访问,可添加额外功能 | 房产中介 |
四、静态代理:最基础的实现方式
什么是静态代理?
静态代理是指代理类在程序编译期就已经确定并生成,在程序运行前代理类的.class文件就已经存在了-1。开发人员需要为每个目标类手动编写一个对应的代理类。
静态代理代码示例
以房屋租赁为例:
// 1. 抽象主题接口(定义房东和中介都要做的事情) public interface HouseSubject { void rentHouse(); } // 2. 真实主题——房东(目标对象) public class RealHouseSubject implements HouseSubject { @Override public void rentHouse() { System.out.println("我是房东,我出租房子!"); } } // 3. 代理类——中介 public class HouseProxy implements HouseSubject { private HouseSubject houseSubject; // 持有目标对象的引用 public HouseProxy(HouseSubject houseSubject) { this.houseSubject = houseSubject; } @Override public void rentHouse() { System.out.println("【前置增强】中介开始筛选租客"); houseSubject.rentHouse(); // 调用房东的真实方法 System.out.println("【后置增强】中介完成合同签订"); } } // 4. 客户端调用 public class Client { public static void main(String[] args) { HouseSubject landlord = new RealHouseSubject(); HouseProxy proxy = new HouseProxy(landlord); proxy.rentHouse(); // 通过代理访问目标方法 } }
运行结果:
【前置增强】中介开始筛选租客 我是房东,我出租房子! 【后置增强】中介完成合同签订
静态代理的致命缺陷
静态代理虽然解决了“不修改目标对象即可增强功能”的问题,但它要求为每个目标类手动编写一个代理类。当系统中存在几十个Service时,就需要编写几十个代理类——代码量暴增且难以维护-1。如果目标接口新增了方法,代理类和所有具体实现类都必须同步修改,扩展性极差。这正是动态代理登场的原因。
五、关联概念讲解:动态代理
什么是动态代理?
动态代理是指在程序运行期间,运用反射机制动态创建代理类及其实例,代理类与被代理类的关系在运行前并不确定-1。开发者无需手动编写代理类代码,框架会在运行时自动生成。
在Java体系中,动态代理主要有两种实现方式:JDK动态代理和CGLIB动态代理。
(一)JDK动态代理
JDK动态代理是Java原生支持的代理机制,位于java.lang.reflect包中,通过Proxy类和InvocationHandler接口实现-13。
核心原理
JDK动态代理基于接口工作。它在运行时动态生成一个实现了指定接口的代理类,代理类会拦截所有接口方法的调用,并将调用转发给InvocationHandler的invoke方法。开发者只需在invoke方法中编写增强逻辑即可-12。
工作流程
客户端调用代理对象方法 → 代理类转发到InvocationHandler → InvocationHandler执行invoke方法 → 反射调用目标对象方法核心组件
| 组件 | 作用 |
|---|---|
InvocationHandler接口 | 定义代理逻辑的处理器,需实现invoke方法 |
Proxy.newProxyInstance() | 静态工厂方法,在运行时动态创建代理对象 |
代码示例
// 1. 定义接口(必须有接口) public interface StudentService { void study(String studentName); void eat(); } // 2. 目标实现类 public class StudentServiceImpl implements StudentService { @Override public void study(String studentName) { System.out.println(studentName + " 正在学习..."); } @Override public void eat() { 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("【日志】方法 " + method.getName() + " 执行完毕"); return result; } } // 4. 生成代理对象并调用 public class JdkProxyDemo { public static void main(String[] args) { // 创建目标对象 StudentService target = new StudentServiceImpl(); // 创建InvocationHandler InvocationHandler handler = new LogInvocationHandler(target); // 生成代理对象(关键:三个参数) StudentService proxy = (StudentService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 目标对象实现的接口数组 handler // InvocationHandler处理器 ); // 通过代理对象调用方法 proxy.study("张三"); } }
运行结果:
【日志】方法 study 开始执行 张三 正在学习... 【日志】方法 study 执行完毕
Proxy.newProxyInstance() 的三个参数详解
| 参数 | 含义 | 常用取值 |
|---|---|---|
loader | 定义代理类的类加载器 | target.getClass().getClassLoader() |
interfaces | 代理类需要实现的接口数组 | target.getClass().getInterfaces() |
h | 方法调用处理器(InvocationHandler实现类) | 自定义的Handler对象 |
(二)CGLIB动态代理
CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它通过继承的方式实现动态代理,不需要目标类实现任何接口-21。
为什么需要CGLIB?
JDK动态代理有一个硬性限制:目标类必须实现至少一个接口。但在实际开发中,很多类并没有实现接口(如普通的POJO类、工具类等)。CGLIB正是为弥补这一缺陷而生。
核心原理
CGLIB通过ASM字节码操作框架,在运行时动态生成目标类的子类作为代理类。这个子类会覆盖目标类中所有非final的方法,并将方法调用委托给MethodInterceptor拦截器,开发者在拦截器中插入增强逻辑-21。
本质区别总结:
| 代理方式 | 实现原理 | 通俗理解 |
|---|---|---|
| JDK动态代理 | 基于组合:生成实现接口的$Proxy类,持有目标对象,通过反射转发调用 | 代理对象“用”目标对象 |
| CGLIB | 基于继承:生成目标类的子类,通过重写方法实现拦截 | 代理对象就是目标对象的“子类” |
核心组件
| 组件 | 作用 |
|---|---|
Enhancer | 生成器,配置父类、拦截器,生成字节码 |
MethodInterceptor | 拦截器接口,核心方法intercept()用于定义增强逻辑 |
MethodProxy | 快速调用器,通过字节码索引直接调用,性能优于反射 |
代码示例
// 1. 目标类(无需实现任何接口) public class OrderService { public void placeOrder() { System.out.println("正在创建订单..."); } } // 2. 实现MethodInterceptor拦截器 public class LogInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【CGLIB前置日志】方法 " + method.getName() + " 开始执行"); // 调用父类(目标类)的原始方法(注意:使用invokeSuper而非反射) Object result = proxy.invokeSuper(obj, args); System.out.println("【CGLIB后置日志】方法 " + method.getName() + " 执行完毕"); return result; } } // 3. 生成CGLIB代理对象 public class CglibProxyDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OrderService.class); // 设置父类(目标类) enhancer.setCallback(new LogInterceptor()); // 设置拦截器 OrderService proxy = (OrderService) enhancer.create(); // 生成代理对象 proxy.placeOrder(); } }
运行结果:
【CGLIB前置日志】方法 placeOrder 开始执行 正在创建订单... 【CGLIB后置日志】方法 placeOrder 执行完毕
CGLIB的限制
无法代理
final类:因为final类不能被继承无法代理
final方法:因为final方法不能被重写构造方法无法被拦截
六、概念关系与区别总结
整体逻辑关系图
代理模式(思想) ├── 静态代理(编译期确定,需手动编写代理类) └── 动态代理(运行期生成,自动创建代理类) ├── JDK动态代理(基于接口 + 反射) └── CGLIB动态代理(基于继承 + 字节码增强)
一句话助记
代理模式是思想,静态代理是手写,动态代理是自动生成。JDK靠接口+反射,CGLIB靠继承+字节码。
三者的详细对比
| 对比维度 | 静态代理 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|---|
| 创建时机 | 编译期 | 运行期 | 运行期 |
| 是否需要接口 | 需要(或抽象类) | 必须需要 | 不需要 |
| 实现原理 | 手动编写代理类 | 反射生成代理类(基于组合) | 字节码生成子类(基于继承) |
| 代理类数量 | N个目标类 → N个代理类 | 通用,一个Handler通配所有 | 通用,一个Interceptor通配所有 |
| 代码维护 | 接口变更需改代理类 | 无需维护代理类代码 | 无需维护代理类代码 |
| 性能 | 无反射开销,最快 | JDK 1.8+优化后性能优异 | JDK 1.7及以下版本性能更优 |
| 适用场景 | 小项目、接口稳定 | 目标类有接口 | 目标类无接口,或需强制使用CGLIB |
七、代码示例:JDK vs CGLIB 直观对比
// ============ JDK动态代理示例 ============ public class JdkComparison { public static void main(String[] args) { // 前提:目标类必须实现接口 UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxyObj, method, args1) -> { System.out.println("JDK代理前置"); Object result = method.invoke(target, args1); System.out.println("JDK代理后置"); return result; } ); proxy.addUser("张三"); } } // ============ CGLIB动态代理示例 ============ public class CglibComparison { public static void main(String[] args) { // 无需接口,可直接代理普通类 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OrderService.class); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("CGLIB代理前置"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLIB代理后置"); return result; }); OrderService proxy = (OrderService) enhancer.create(); proxy.placeOrder(); } }
八、底层原理与技术支撑
1. JDK动态代理的底层支撑——反射机制
JDK动态代理的核心是Java反射机制。Proxy.newProxyInstance()在运行时动态生成代理类的字节码(如$Proxy0),通过反射调用InvocationHandler.invoke()方法-。生成的代理类大致结构如下-13:
// JDK动态代理生成的代理类简化版 public class $Proxy0 extends Proxy implements UserService { private InvocationHandler h; public $Proxy0(InvocationHandler h) { super(h); } public void addUser(String name) { h.invoke(this, addUserMethod, new Object[]{name}); } }
要深入理解动态代理,必须先掌握反射机制——Java的反射基于java.lang.Class,而Class对应JVM内部的Klass元数据结构-。
2. CGLIB的底层支撑——ASM字节码技术
CGLIB包的底层通过ASM字节码处理框架来转换字节码并生成新的类-。相比于JDK动态代理的反射调用,CGLIB生成了FastClass机制:为代理类和被代理类各生成一个类,为每个方法分配索引,调用时直接通过索引定位方法,避免了反射的性能开销-42。
3. Spring AOP中的实际应用
Spring AOP的底层实现就是动态代理。Spring会根据目标类的特性智能选择代理机制-52:
默认策略:目标类实现了接口 → 使用JDK动态代理;目标类没有实现接口 → 使用CGLIB
强制CGLIB:可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)或配置文件设置spring.aop.proxy-target-class=true强制使用CGLIB-55Spring Boot默认:由于大量Bean没有接口(如Controller、Service),Spring Boot默认开启CGLIB代理-55
九、高频面试题与参考答案
面试题1:静态代理和动态代理有什么区别?
参考答案:
创建时机不同:静态代理在编译期手动编写/生成,编译后存在
.class文件;动态代理在运行期通过反射/字节码技术动态生成,无物理.class文件-41。灵活性不同:静态代理一对一绑定目标类和接口,接口变更需同步修改代理类;动态代理可通用适配多个目标类,灵活性高。
代码量不同:静态代理需要为每个目标类编写代理类;动态代理只需一个
InvocationHandler或MethodInterceptor即可处理所有方法。
面试题2:JDK动态代理和CGLIB动态代理有什么区别?
参考答案:
核心前提:JDK动态代理要求目标类必须实现接口;CGLIB无需接口,通过继承目标类生成子类-41。
实现原理:JDK基于反射和接口组合;CGLIB基于ASM字节码和继承。
性能:JDK 1.8及以上版本对反射优化后,JDK动态代理性能优于CGLIB;JDK 1.8以下版本CGLIB性能略优-41。
限制条件:CGLIB无法代理
final类和final方法;JDK动态代理无法代理未实现接口的普通类。
面试题3:Spring AOP默认使用哪种代理?如何强制切换?
参考答案:
Spring AOP的默认策略是通过DefaultAopProxyFactory自动判断:
目标类实现了接口 → 使用JDK动态代理
目标类未实现接口 → 使用CGLIB动态代理-55
强制使用CGLIB的方式:
在配置类上添加
@EnableAspectJAutoProxy(proxyTargetClass = true)在
application.properties中设置spring.aop.proxy-target-class=true
面试题4:CGLIB能代理final类吗?为什么?
参考答案:
不能。CGLIB动态代理基于继承实现,它会在运行时动态生成目标类的子类作为代理类。由于final类不能被继承,CGLIB无法为其创建子类,因此无法代理final类。同理,final方法也无法被CGLIB代理,因为final方法不能被重写-41。
面试题5:动态代理在框架中有哪些实际应用场景?
参考答案:
声明式事务管理(Spring
@Transactional):通过动态代理在业务方法执行前自动开启事务,执行成功后提交事务,异常时回滚统一日志记录:在方法执行前后自动记录入参、耗时、返回结果
权限校验拦截:在核心方法执行前校验用户权限
性能监控:统计方法执行时间-41
十、结尾总结
回顾全文核心知识点:
| 核心要点 | 关键结论 |
|---|---|
| 代理模式本质 | 引入中间层,在不修改目标对象的前提下增强功能 |
| 静态代理 vs 动态代理 | 静态代理编译期手写(一对一的专属经纪人),动态代理运行期自动生成(一劳永逸的通用中介) |
| JDK动态代理核心 | 必须要有接口,靠反射+InvocationHandler实现 |
| CGLIB动态代理核心 | 无需接口,靠继承+字节码技术实现,但无法代理final类/方法 |
| Spring AOP选择策略 | 有接口用JDK,无接口用CGLIB;可通过proxyTargetClass=true强制CGLIB |
重点记忆: 面试时回答“静态代理和动态代理的区别”,一定要从 “创建时机、灵活性、代码量” 三个角度展开;回答“JDK和CGLIB的区别”,务必点出 “接口依赖 vs 继承依赖、反射 vs 字节码、final限制” 这三个核心差异。
下一篇文章将深入Spring AOP源码,剖析动态代理在事务管理中的完整织入流程,敬请期待!
