代理模式是 Spring AOP、MyBatis、RPC 框架的底层基石,但"静态代理、JDK 动态代理、CGLIB"这几个词平时总是一起出现又分不清。这篇把它从头捋一遍:代理解决什么、静态 vs 动态的区别、以及它和装饰器这对"长得一样"的兄弟怎么区分。
一、解决什么问题
代理模式解决的是**"在不改原类的情况下,控制对对象的访问"**。
典型诉求:
- 延迟加载:对象创建很贵(大对象),先给个代理占位,真用到了才创建
- 权限控制:访问前先校验权限
- 远程调用:本地代理代表远程对象(RPC 就是这么做的)
- 日志/监控:方法调用前后记录日志
核心思想:代理和目标实现同一个接口,客户端无感知地通过代理访问目标,代理在"转调"的前后插入控制逻辑。
二、静态代理
java
// ① 接口
public interface Subject {
void request();
}
// ② 真实对象
public class RealSubject implements Subject {
public void request() { System.out.println("真实业务逻辑"); }
}
// ③ 代理:实现同一接口,持有真实对象,前后加控制
public class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
System.out.println("前置:权限校验"); // 访问控制
realSubject.request(); // 转调真实对象
System.out.println("后置:日志记录"); // 附加逻辑
}
}
// 客户端:无感知,直接调代理
Subject subject = new Proxy();
subject.request();缺点:每个真实对象都要手写一个代理类,接口方法一多,代理类又长又重复——所以有了动态代理。
三、动态代理
静态代理"每个类手写一个代理"太累,动态代理在运行时自动生成代理类,分两种实现:
- JDK 动态代理:靠"实现接口"冒充,目标必须有接口
- CGLIB:靠"继承生成子类"冒充,无接口也行,但 final 不行
两种实现的机制差异、完整代码、对比表,详见《Java 动态代理:JDK 代理 vs CGLIB》——那篇把"为什么有接口用 JDK、无接口用 CGLIB"讲透了。
四、实际应用
- Spring AOP:事务、日志、权限,底层就是动态代理——有接口用 JDK 代理,没接口用 CGLIB
- MyBatis Mapper:
UserMapper没有实现类,就是 JDK 动态代理生成的(见《MyBatis 快速入门》) - RPC 框架:本地代理对象代表远程服务,调用时发起网络请求
五、代理 vs 装饰器(易混)
代码结构几乎一样,但意图不同:
| 维度 | 代理模式 | 装饰器模式 |
|---|---|---|
| 目的 | 控制访问(能不能用) | 增强功能(能做更多) |
| 侧重点 | 前置校验、延迟、远程 | 层层叠加新能力 |
| 谁创建 | 框架/容器自动生成 | 使用方手动层层包装 |
一句话:代理是"门卫"(拦在前面管你能不能进),装饰器是"裁缝"(往对象身上加衣服)。装饰器的详解见《装饰器模式》。
小结
- 代理解决"不改原类、控制访问",核心是代理与目标同接口 + 转调前后加控制
- 静态代理要手写代理类,动态代理运行时生成
- JDK 代理基于接口,CGLIB 基于继承(无接口时用)
- 落地:Spring AOP、MyBatis Mapper、RPC
- 与装饰器区别:代理"控制访问",装饰器"增强功能"
下一步可以看同为结构型的《外观模式》——它和代理相反,代理是"拦在单个对象前",外观是"把一堆复杂的对象封装成一个简单入口"(文章整理中)。
