什么是代理模式?
代理模式为另一个对象提供了一个 替身(surrogate) 或 占位符(placeholder) ,以控制对这个对象的访问;简单来说,当客户端想要调用一个对象(真实主题)的方法时,它不是直接去调用,而是先调用代理对象的方法。代理对象在将请求转发给真实对象之前或之后,可以添加额外的逻辑;
核心意图: 引入一个间接层,通过这个代理对象来控制、保护、优化或增强对真实对象的访问。
代理模式时序图

核心作用:在不改变原对象代码的前提下控制访问 🤝
- 代理模式的本质是:控制对真实对象的访问,并为其添加额外功能
- 代理模式让你在不修改原始类的前提下,控制访问,并为方法自动添加任何你想加的逻辑
- 它是 Spring AOP 与各种框架增强的核心机制
代理模式主要包括两个能力:
(一) 控制对真实对象的访问(最核心)
不让外部直接访问真实对象,强制只能访问代理对象
| 限制访问的场景 | 典型例子 | 代理具体限制了什么 |
|---|---|---|
| 权限控制(最常见) | 后台管理系统、文件读写 | 根据用户角色(admin/user/guest)决定能不能调用 |
| 敏感数据脱敏/字段屏蔽 | 返回用户个人信息接口 | 普通用户看不到身份证号、手机号完整号码 |
| 频率限制(防刷/限流) | 短信验证码、点赞按钮 | 60秒内只能点一次,超了直接 429 返回 |
| IP 黑白名单 | API 网关、内部服务调用 | 不在白名单的 IP 直接拒绝 |
| 付费墙(内容付费) | 知乎、付费视频、VIP文章 | 未付费用户只能看到前 30%,其余返回"请开通会员" |
| 青少年模式/内容分级 | 抖音、YouTube Kids | 未成年用户禁止访问血腥、恐怖、成人内容 |
| 企业内部服务只允许内网访问 | 公司内部 OA、Jenkins、K8s Dashboard | 非内网 IP 一律 403 |
| 操作审计 + 禁止危险操作 | 数据库管理工具、Linux 服务器管理后台 | 普通用户禁止执行 delete from users 之类的语句 |
(二) 为原对象增加行为(AOP增强)
无需修改真实类即可增强其功能
| 功能 | 属于代理的哪种类型 | 代理在什么时候插入代码 | 项目中最常见实现方式 |
|---|---|---|---|
| 方法执行前打印日志 | 通用/日志代理 | 前置处理(preHandle) | Spring AOP、Java Dynamic Proxy、AspectJ |
| 方法执行后写数据库 | 审计/日志代理 | 后置处理(afterReturning / after) | 各种框架的 @After 切面 |
| 自动加事务 | 事务代理(最经典!) | 前置开启事务 → 正常结束提交 → 异常回滚 | Spring @Transactional 就是事务代理! |
| 性能统计耗时 | 监控/性能代理 | 记录开始时间 → 结束后计算差值并打印/上报 | Micrometer、SkyWalking、自定义AOP |
| 缓存控制 | 缓存代理(非常常见) | 先查缓存 → 没命中才调真实方法 → 结果写回缓存 | Spring Cache 中@Cacheable/@CachePut/@CacheEvict |
| 调用次数统计 | 统计/限流代理 | 每次调用原子 +1,可结合 Redis/MeterRegistry | Prometheus Counter、Guava RateLimiter |
实际用途:为什么明明能直接调用对象,却要用代理?
用代理模式的原因有以下四类:
(一) 原始类不能被修改
例如:
- 第三方 jar 包中的类
- 核心类禁止业务人员随意改动
- 多人协作不希望都改同一个类,代理可以做到 不改源码就增强功能
(二) 希望在方法调用前后加逻辑(AOP 做的事)
代理可以自动加:前置增强 → 调用原方法 → 后置增强
例如不想写以下几种重复代码逻辑:
- log(); ------日志记录
- check(); ------ 权限/参数校验(前置增强)
- target.xxx(); ------ 调用真实业务方法(核心业务)
- audit(); ------ 审计 / 记录调用结果(后置增强)
(三) 需要控制访问权限
例如对某些对象和方法做权限校验:
- 禁止调用某些方法
- 只能在特定用户身份下访问真实对象
- 强制通过网关或统一入口才能访问
(四) 希望统一管理横切逻辑
几乎所有 AOP、拦截器、框架增强都通过代理实现:
- 事务管理(@Transactional)
- 日志增强
- 异常捕捉
- 监控统计
- 防重复提交
延伸场景:解决实际业务问题
(一) 延迟加载(Lazy Loading)
真实对象过于"重"(如数据库连接、文件、网络资源)代理可以先占位真正使用时才创建真实对象
(二) 远程调用封装(RPC 实现)
所有 RPC 框架都在用代理,代理让调用远程服务看起来像调用本地方法,包括以下几种:
- Dubbo 的 ReferenceBean
- Feign 的 @FeignClient
- gRPC Stub
- Spring 的 RestTemplate 动态代理
(三) 安全隔离
当不允许别人直接访问真实对象时,通过代理统一管理
(四) Spring 中的代理设计模式
| 场景 | 使用方式 | 实现代理方式 |
|---|---|---|
| AOP 切面增强 | @Aspect | JDK 或 CGLIB |
| 事务管理 | @Transactional | JDK 或 CGLIB |
| 缓存注解 | @Cacheable | Spring AOP 代理 |
代理设计模式的实现方式常见 3 大类 + 1 扩展(常用于框架)
一、静态代理(Static Proxy)
支持代理 final 类
- 定义公共接口(抽象主题)
- 实现真实业务类(RealSubject)
- 编写代理类(Proxy),内部持有 RealSubject 对象,控制访问
csharp
// 抽象主题
public interface UserService {
void doWork();
}
// 真实对象
public class UserServiceImpl implements UserService {
public void doWork() {
System.out.println("执行真实业务逻辑");
}
}
// 代理对象
public class UserServiceProxy implements UserService {
private final UserServiceImpl userServiceImpl = new UserServiceImpl();
public void doWork() {
System.out.println("前置日志记录");
realService.doWork();
System.out.println("后置监控统计");
}
}
// 运行代码
public static void main(String[] args) {
UserServiceProxy userServiceProxy = new UserServiceProxy();
userServiceProxy.doWork();
}
二、JDK 动态代理(JDK Dynamic Proxy)
使用 JDK 动态代理的前提:被代理的类必须实现接口
实现方式:
- 创建接口和实现类
- 使用
InvocationHandler实现增强逻辑 - 通过
Proxy.newProxyInstance()生成代理对象,并隐藏真实对象防止调用
typescript
public interface IPaymentService {
void pay(String amount);
}
@Service
public class PaymentServiceImpl implements IPaymentService {
@Override
public void pay(String amount) {
System.out.println(">>> 正在执行支付操作,金额:" + amount);
}
}
typescript
public class PaymentInvocationHandler implements InvocationHandler {
// 原对象
private final Object target;
// 定义需要排除的 Object 类方法列表
private static final List<String> EXCLUDE_METHODS = Arrays.asList("toString", "hashCode", "equals", "clone");
public PaymentInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// ⭐️ 关键过滤逻辑:排除 Object 类的方法,这些方法不进行增强(JDK 动态代理的 invoke 方法的机制是:它会拦截 代理对象上所有公开方法的调用,包括那些从 java.lang.Object 继承来的方法)
if (EXCLUDE_METHODS.contains(methodName)) {
// 直接调用原方法,不执行增强逻辑
return method.invoke(target, args);
}
// 业务方法的增强逻辑 (仅在此处执行)
System.out.println("[代理增强] 方法执行前: 正在记录访问日志 -> " + methodName);
Object result = method.invoke(target, args);
System.out.println("[代理增强] 方法执行后: 日志记录完成");
return result;
}
}
typescript
@Component
public class PaymentProxyHidingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 识别到 PaymentServiceImpl 目标 Bean ,如果注入原对象则替换成代理对象
if (bean instanceof PaymentServiceImpl) {
System.out.println("[BPP 机制] 发现目标 Bean [" + beanName + "],准备使用动态代理替换它!");
// 1. 创建代理对象,将原对象作为参数传入 InvocationHandler
Object proxyInstance = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
new PaymentInvocationHandler(bean)
);
// 2. 关键步骤:返回代理对象
// Spring 容器会将 beanName="userServiceImpl" 映射到这个代理对象。
// 原对象实例被隐藏在 proxyInstance 内部,外部无法获取。
return proxyInstance;
}
return bean;
}
}
less
@Slf4j
@RestController
@RequestMapping("/portal/test")
public class TestController {
@Autowired
private IPaymentService paymentService;
@GetMapping("/test/proxy")
public void testProxy() {
// 1. 验证注入的对象到底是谁
// 如果是 JDK 代理,类名通常是 $Proxy...
System.out.println("注入对象的实际类名: " + paymentService.getClass().getName());
// 2. 调用方法
paymentService.pay("100元");
}
}
三、CGLIB 动态代理(CGLIB Enhancer)
CGLIB 动态代理基于继承,代理对象继承于原对象,目标类不能是
final类,方法也不能是final修饰
实现方式: 使用第三方库 CGLIB(如 Spring AOP 默认使用) 生成目标类的子类实现代理
typescript
public interface IPaymentService {
void pay(String amount);
}
@Service
public class PaymentServiceImpl implements IPaymentService {
@Override
public void pay(String amount) {
System.out.println(">>> 正在执行支付操作,金额:" + amount);
}
}
typescript
public class PaymentMethodInterceptor implements MethodInterceptor {
// 维护目标对象(原 Spring Bean)
private final Object target;
// 定义需要排除的方法列表
private static final List<String> EXCLUDE_METHODS = Arrays.asList("toString", "hashCode", "equals", "clone");
public PaymentMethodInterceptor(Object target) {
this.target = target;
}
/**
* @param proxy 代理对象本身
* @param method 被拦截的方法对象
* @param args 方法参数
* @param methodProxy CGLIB 提供的机制,用于调用父类方法 (invokeSuper)
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
// 1. 过滤不需要增强的方法
if (EXCLUDE_METHODS.contains(methodName)) {
// 直接反射调用目标对象的方法
return method.invoke(target, args);
}
// 2. 增强逻辑:前置
System.out.println("[CGLIB 增强] 方法执行前: 正在记录访问日志 -> " + methodName);
// 3. 执行目标方法
// 注意:这里我们调用 target (原对象) 的方法,而不是使用 methodProxy.invokeSuper。
// 因为我们在 BeanPostProcessor 中拿到的是已经由 Spring 初始化好的 Bean,
// 我们希望操作那个具体的 Bean 实例。
Object result = method.invoke(target, args);
// 4. 增强逻辑:后置
System.out.println("[CGLIB 增强] 方法执行后: 日志记录完成");
return result;
}
}
java
@Component
public class PaymentProxyHidingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 识别目标 Bean
if (bean instanceof PaymentServiceImpl) {
System.out.println("[BPP 机制] 发现目标 Bean [" + beanName + "],准备使用 CGLIB 代理替换它!");
// 1. 创建 Enhancer 对象 (类似于 JDK 的 Proxy 类)
Enhancer enhancer = new Enhancer();
/*
2. 设置父类 (Superclass)
关键区别:JDK 代理是 setInterfaces,CGLIB 是 setSuperclass
代理类将继承 PaymentServiceImpl
*/
enhancer.setSuperclass(bean.getClass());
// 3. 设置回调 (Callback),将我们自定义的拦截器传入,并把原 bean 传进去
enhancer.setCallback(new PaymentMethodInterceptor(bean));
// 4. 创建代理对象
return enhancer.create();
}
return bean;
}
}
四、三种代理模式总结
| 实现方式 | 是否要求接口 | 是否可以增强所有方法 | 是否支持 final 类 |
|---|---|---|---|
| 静态代理 | ✅ | ✅ | ✅ |
| JDK 动态代理 | ✅ | ✅ | ✅ |
| CGLIB 动态代理 | ❌ | ✅ | ❌ |