本文通过
前言

这套定义听起来有点抽象,但回到日常场景就很好理解了:租房时你找中介而不是直接联系房东,签约时明星的经纪人在前面替你谈条件,海淘时代购帮你搞定一切手续。这些“中间人”本质上就是代理——你只和代理打交道,代理负责与真实对象交互,并在过程中承担额外的职责。

很多学习者在接触代理模式时会遇到类似的困惑:静态代理和动态代理傻傻分不清,知道AOP用到了代理却说不出底层原理,面试被问到JDK与CGLIB的区别只能支支吾吾。 这篇文章将带你从概念到原理、从代码到面试,打通代理模式的完整知识链路。
一、痛点切入:为什么需要代理模式
在理解代理模式之前,先看看不使用时的问题。
假设有一个发送短信的业务接口和它的实现类:
public interface SmsService { void send(String message); } public class SmsServiceImpl implements SmsService { @Override public void send(String message) { System.out.println("发送消息:" + message); } }
现在需要在发送短信前后分别记录日志。最直接的做法是修改 send() 方法,在首尾添加日志输出。但问题随之而来:假如有10个不同业务都需要加日志,就要修改10处代码,并且一旦日志逻辑需要调整,10处都要改动。
如果业务和增强逻辑混在一起,会出现以下问题:
代码冗余:相同的增强逻辑在多个方法中重复编写
耦合度高:业务代码混杂非业务逻辑,违背单一职责原则
扩展性差:新增增强功能需修改原有代码,违背开闭原则
维护困难:修改一处增强逻辑需同步修改所有调用处
这正是代理模式要解决的问题——在不修改原始类的前提下,为目标对象提供功能增强和访问控制。
二、核心概念讲解:代理模式
标准定义
代理模式(Proxy Pattern) 是一种结构型设计模式,它引入一个代理对象来代表真实对象,客户端只与代理交互,代理决定何时、如何、是否将请求转发给真实对象-1。
生活化类比
代理模式的运作机制,用“中介租房”类比最直观:
| 角色 | 代理模式角色 | 中介场景对应 |
|---|---|---|
| 定义公共行为 | 抽象主题(Subject) | 租房接口(带看、签约) |
| 真正执行业务 | 真实主题(RealSubject) | 房东(拥有房子) |
| 控制访问 | 代理主题(Proxy) | 中介(持有房东信息) |
| 发起请求 | 客户端(Client) | 租客 |
房东只负责收租和交房,不处理带看、签约、押金这些琐事;租客只和中介沟通,不直接联系房东。中介在带看前后可以添加“筛选房源”“记录看房日志”等操作,这就是“增强”。代理对象不创造核心能力,最终的核心业务还是由真实对象完成 -1。
核心价值
引入代理层带来三点核心价值:
透明性:客户端无需感知代理的存在,调用方式与直接调用真实对象一致
控制性:代理可在请求前后插入逻辑(权限校验、日志记录、缓存等)
解耦性:将横切关注点(安全、日志)从业务逻辑中剥离,符合单一职责原则-4
三、关联概念讲解:静态代理 vs 动态代理
代理模式根据代理类的创建时机和方式,分为静态代理和动态代理两大类。
3.1 静态代理
静态代理是指代理类的代码在编译时就已经确定,需要开发人员手动编写代理类。代理类和目标类都实现同一个接口,代理类持有目标对象的引用,在调用目标方法前后添加增强逻辑-2。
示例:为SmsService添加日志增强
// 代理类:实现同一接口,持有真实对象引用 public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public void send(String message) { // 前置增强:日志记录 System.out.println("【前置】发送消息开始:" + message); // 调用真实对象的核心方法 smsService.send(message); // 后置增强:日志记录 System.out.println("【后置】消息发送成功"); } } // 使用方式 SmsService realService = new SmsServiceImpl(); SmsService proxy = new SmsProxy(realService); proxy.send("Hello"); // 调用代理对象,代理内部调用真实对象
缺点:如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则;且每个目标类都需要单独写一个代理类,代码量大、维护成本高-12。
3.2 动态代理
动态代理在运行时动态生成代理类的字节码,无需手动编写代理类。代理类的创建逻辑被集中封装,大幅减少代码冗余-。
Java平台提供两种主流动态代理实现:
JDK动态代理
原理:基于接口实现,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口,在运行时生成实现指定接口的代理类-15。
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()); // 反射调用真实对象的方法 Object result = method.invoke(target, args); // 后置增强 System.out.println("【后置】方法执行完成"); return result; } } // 使用方式 SmsService realService = new SmsServiceImpl(); InvocationHandler handler = new LogInvocationHandler(realService); SmsService proxy = (SmsService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), handler ); proxy.send("Hello");
限制:目标类必须实现至少一个接口,代理对象的类型由接口列表决定-15。
CGLIB动态代理
原理:通过字节码生成技术,在运行时动态生成目标类的子类来创建代理对象。CGLIB底层采用ASM字节码框架,对目标类的非final方法进行覆写增强-20。
public class LogMethodInterceptor 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; } } // 使用方式(需引入cglib依赖) Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(SmsServiceImpl.class); enhancer.setCallback(new LogMethodInterceptor()); SmsServiceImpl proxy = (SmsServiceImpl) enhancer.create(); proxy.send("Hello");
限制:无法代理 final 类,也无法代理 final 或 private 方法-20。
3.3 JDK动态代理 vs CGLIB动态代理:全维度对比
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现方式 | 接口代理 | 子类代理 |
| 是否依赖接口 | 必须有接口 | 不需要接口 |
| 代理类生成 | ProxyGenerator.generateProxyClass() | ASM字节码框架动态生成子类 |
| 类名格式 | $Proxy0、$Proxy1 | 类名$$EnhancerByCGLIB$$xxxx |
| 性能特点 | 调用成本低,生成成本低 | 生成类成本高,调用快 |
| final类/方法 | 不可代理 | 不可代理 |
| Spring默认策略 | 有接口时优先使用 | 无接口时fallback使用 |
一句话总结:JDK动态代理是“对接口编程”的代理,CGLIB动态代理是“对类继承”的代理-34。
四、概念关系与区别总结
理清以下三层关系,代理模式就彻底通透了:
第一层:代理模式 vs 静态/动态代理
代理模式是设计思想(概念层)
静态代理和动态代理是实现方式(技术层)
静态代理在编译期确定代理关系,动态代理在运行期动态生成-2
第二层:静态代理的特点
手动编写代理类,实现简单,代码清晰
每个目标类需对应一个代理类 → 类数量膨胀
接口变更时代理类需同步修改 → 违背开闭原则
适用场景:目标类少、需求固定、性能要求高-72
第三层:动态代理的特点
运行时动态生成代理类 → 代码复用率高、扩展性强
JDK代理要求接口,CGLIB代理无此限制
动态代理广泛应用于AOP、过滤器、拦截器等框架设计-69
一句话高度概括:静态代理是为每个目标类手写一个“助理”,动态代理是通过反射/字节码技术自动生成“助理”;JDK是“按接口合同”生成助理,CGLIB是“认目标类当爹”生成助理。
五、代码示例完整演示
以“订单下单”业务场景为例,完整演示从静态代理到JDK动态代理的演进过程。
5.1 业务接口与实现
// 抽象主题:订单服务接口 public interface OrderService { void placeOrder(String userId, String productId); } // 真实主题:核心业务实现 public class OrderServiceImpl implements OrderService { @Override public void placeOrder(String userId, String productId) { System.out.println("核心业务:用户 " + userId + " 下单商品 " + productId); } }
5.2 静态代理实现
public class OrderServiceStaticProxy implements OrderService { private final OrderService target; public OrderServiceStaticProxy(OrderService target) { this.target = target; } @Override public void placeOrder(String userId, String productId) { // 前置增强:权限校验 + 日志记录 System.out.println("【静态代理前置】校验用户权限:" + userId); System.out.println("【静态代理前置】记录请求日志"); target.placeOrder(userId, productId); // 调用真实业务 // 后置增强:发送通知 System.out.println("【静态代理后置】发送下单成功通知"); } }
5.3 JDK动态代理实现
// 统一的增强处理器 public class LogAndAuthInvocationHandler implements InvocationHandler { private final Object target; public LogAndAuthInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("【动态代理前置】方法:" + method.getName()); System.out.println("【动态代理前置】参数:" + Arrays.toString(args)); Object result = method.invoke(target, args); System.out.println("【动态代理后置】执行完成"); return result; } } // 使用方式 OrderService realService = new OrderServiceImpl(); OrderService proxy = (OrderService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new LogAndAuthInvocationHandler(realService) ); proxy.placeOrder("user001", "productA");
输出结果:
【动态代理前置】方法:placeOrder 【动态代理前置】参数:[user001, productA] 核心业务:用户 user001 下单商品 productA 【动态代理后置】执行完成
5.4 对比效果
| 对比项 | 静态代理 | 动态代理 |
|---|---|---|
| 代理类编写 | 每个业务类单独写一个代理类 | 一个InvocationHandler覆盖所有业务 |
| 接口变更 | 代理类必须同步修改 | 无需改动,InvocationHandler自动适配 |
| 代码量 | 随业务增长线性增加 | 固定不变 |
| 灵活度 | 低 | 高 |
| 性能 | 直接调用 | 反射调用略有开销 |
六、底层原理与技术支撑
6.1 JDK动态代理底层
JDK动态代理的核心在 Proxy.newProxyInstance() 方法。底层调用 ProxyGenerator.generateProxyClass(),在运行时动态生成标准Java字节码(符合JVM规范),输出实现指定接口的代理类,类名格式为 $Proxy0、$Proxy1-20。
代理类内部会为接口的每个方法生成对应实现,方法体中调用 InvocationHandler.invoke(),实现统一的拦截入口。所有被代理的方法调用最终都被“拦截”到 invoke 方法中。
6.2 CGLIB动态代理底层
CGLIB底层采用ASM字节码框架,直接在内存中逐指令构建类文件。它复制目标类的非final方法字节码,在开头和结尾插入拦截逻辑,并重写构造器,第一行强制调用父类构造器后将 Callback 实例塞进字段-20。
生成的代理类是目标类的子类,类名包含 $$EnhancerByCGLIB$$ 标识。这也是为什么CGLIB无法代理 final 类的原因——Java的 final 类不允许被继承。
6.3 与主流框架的关联
代理模式是Spring AOP(Aspect Oriented Programming,面向切面编程)的底层核心支柱。Spring在容器初始化Bean时,判断是否需要AOP增强,若需要则通过动态代理生成代理对象替换原始Bean-34:
目标类实现了接口 → Spring默认使用JDK动态代理
目标类没有实现接口 → Spring fallback到CGLIB代理
这也是面试中高频出现的考点——理解代理模式,就等于理解了Spring AOP的一半。
七、高频面试题与参考答案
Q1:静态代理和动态代理的区别是什么?
参考答案:
生成时机不同:静态代理在编译期确定,代理类字节码在运行前就已存在;动态代理在运行期动态生成,编译后没有实际class文件-
代码量不同:静态代理需为每个目标类手动编写代理类;动态代理只需编写一个
InvocationHandler即可代理任意目标对象扩展性不同:静态代理接口变更时代理类需同步修改,违背开闭原则;动态代理自动适配,扩展性强
性能差异:静态代理性能更高(直接调用);动态代理涉及反射调用,有轻微性能损耗
Q2:JDK动态代理和CGLIB动态代理有什么区别?
参考答案:
| 对比点 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 基于接口,生成实现接口的代理类 | 基于继承,生成目标类的子类 |
| 接口要求 | 目标类必须实现接口 | 不要求接口 |
| final类/方法 | 不可代理 | 不可代理 |
| 底层技术 | ProxyGenerator生成字节码 | ASM字节码框架动态生成子类 |
| Spring默认 | 有接口时优先 | 无接口时fallback |
Q3:为什么JDK动态代理只能代理接口?
参考答案:
JDK动态代理基于 Proxy.newProxyInstance() 方法创建代理对象,该方法需要传入接口列表来定义代理对象的类型。代理对象内部的方法调用通过反射转发到 InvocationHandler.invoke(),这要求目标类必须有接口约束。如果要代理一个普通类,只能使用CGLIB这种基于继承的方案-15。
Q4:Spring AOP默认使用哪种代理方式?
参考答案:
Spring AOP根据目标类是否实现接口来决定:
目标类实现了接口 → 默认使用JDK动态代理
目标类没有实现接口 → 使用CGLIB代理
通过配置@EnableAspectJAutoProxy(proxyTargetClass = true)可强制使用CGLIB代理-34。
Q5:CGLIB能代理final类吗?为什么?
参考答案:
不能。CGLIB通过生成目标类的子类来实现代理,而Java中的 final 类不允许被继承,因此CGLIB无法代理final类。同样,final 和 private 方法也无法被代理,因为子类无法覆写它们-20。
八、结尾总结
回顾全文核心知识点:
代理模式定义:为其他对象提供一种代理,以控制对这个对象的访问
四大角色:Subject、RealSubject、Proxy、Client
静态代理:编译期确定,手动编写,简单但类膨胀
JDK动态代理:运行期生成,基于接口,统一拦截入口
CGLIB动态代理:运行期生成,基于继承,无接口要求
底层原理:JDK依赖
ProxyGenerator生成字节码,CGLIB依赖ASM框架生成子类框架应用:Spring AOP底层核心支撑,理解代理即理解AOP的一半
重点记忆:静态代理是“手动挡”,JDK动态代理是“接口适配自动挡”,CGLIB动态代理是“继承式自动挡”。面试中能清晰区分三者的原理和适用场景,并能说明Spring AOP的选择策略,就足以应对大部分代理模式相关提问。
下篇预告:本文将深入剖析代理模式与装饰器模式、适配器模式的区别——这三个结构型设计模式常被混淆,下一期将用对比表格和代码实例帮你彻底区分它们。