✅ 一句话定义
代理 = 找一个替身(代理类),帮你控制对真实目标对象的访问;在调用真实方法前后,加额外逻辑,不改原有目标代码。
真实对象:做核心业务 代理对象:增强(日志、耗时、权限、事务),不改动原有业务代码
✅ 为什么会出现代理模式(解决什么痛点)
核心痛点:开闭原则
原有业务代码只负责核心逻辑,不想修改它;但又想在方法执行前后加通用能力(日志、监控、权限校验、事务)。
如果不使用代理: 直接改业务方法,到处写日志代码 → 业务代码和通用杂糅在一起;新增 / 删除日志,要修改所有业务方法,违反开闭原则。
代理思路:通用增强逻辑放到代理里面,业务代码不动。
✅ 3 种代理分类(只抓重点)
1. 静态代理(写死代码,编译期就写好代理类)
特点:代理类代码手动写,一个目标类对应一个代理类 缺点:目标类越多,代理类爆炸,维护麻烦。
示例场景:用户服务,新增打印耗时
java
// 接口
public interface UserService {
void addUser();
}
// 真实类(核心业务)
class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("新增用户【核心业务】");
}
}
// 静态代理类
public class UserServiceProxy implements UserService{
// 持有真实对象
private UserService target;
public UserServiceProxy(UserService target){
this.target = target;
}
@Override
public void addUser() {
System.out.println("【代理前置】方法开始,记录时间");
target.addUser(); // 调用真实业务
System.out.println("【代理后置】方法结束,统计耗时");
}
}
使用:
java
UserService target = new UserServiceImpl();
UserService proxy = new UserServiceProxy(target);
proxy.addUser();
2. JDK 动态代理(⭐Spring AOP 底层之一)
要求:目标对象必须实现接口 运行期动态生成代理类,不用手写代理代码。 原理:
java.lang.reflect.Proxy+InvocationHandler调用代理方法时,进入invoke(),在 invoke 里面执行增强 + 真实方法。
3. CGLIB 代理(⭐Spring AOP 底层之二)
目标类不用实现接口,通过继承目标类生成子类作为代理 不能代理 final 类 /final 方法(无法继承重写)
Spring AOP 规则: 目标有接口 → 默认 JDK 动态代理;无接口 → CGLIB
✅ 日常开发什么时候巧妙用代理(真实场景)
- Spring AOP(最常见!本质就是动态代理) 给接口方法统一加:日志、耗时统计、异常捕获、权限校验、事务 @Transactional
事务:方法执行前开启事务,异常回滚,正常提交,全部在代理增强逻辑,业务代码不用管。
-
RPC 框架:Dubbo 消费端拿到的服务接口,其实是代理对象。调用接口方法时,代理帮你做序列化、网络传输、调用远程服务,本地只是接口,真实逻辑在远端。
-
Mybatis Mapper 接口 Mapper 只是接口,没有实现类!Mybatis 启动时生成代理对象。调用 mapper.select (),代理帮你拼装 SQL、执行 JDBC、封装结果。👉 这个是面试高频!
-
本地缓存、限流、熔断增强 调用目标方法前,代理先判断限流;命中缓存直接返回,不执行真实方法。
✅ 代理 vs 装饰器(容易混淆,一句话区分)
- 代理:侧重【控制访问】 ,能不能执行、权限、远程调用;代理和目标不一定实现一模一样的目的(Dubbo 代理根本本地没有实现)
- 装饰器:侧重【增强功能】,包装同一个对象,不断叠加能力;IO 流 BufferedReader 就是装饰器
✅ 核心总结
- 目的:不修改原有业务代码,对方法做增强,遵守开闭原则
- 静态代理:手写代理类,类多了臃肿;
- JDK 代理:需要接口;CGLIB:继承,不需要接口
- 项目最常见:SpringAOP、Dubbo 消费端、MyBatis Mapper,底层都是动态代理
📝 速记口诀
代理替身控访问,原有业务不用改; 静态手写类爆炸,动态运行生成快; JDK 代理要接口,CGLIB 继承来覆盖; AOP 事务与 Mybatis,RPC 远程都靠它。