代理模式
代理模式用于给目标对象提供一个代理对象,通过代理控制对目标对象的访问。
一句话理解:调用目标对象之前,先经过代理这一层。
解决什么问题
有些逻辑不属于目标对象本身,但又希望在访问目标对象时统一处理:
- 权限校验。
- 日志记录。
- 性能监控。
- 事务控制。
- 缓存。
- 远程调用。
- 延迟加载。
如果这些逻辑直接写进业务类,会污染业务代码。代理模式把访问控制和增强逻辑放到代理对象中。
基本结构
text
Client -> Subject
^
|
Proxy -> RealSubject角色:
Subject:目标接口。RealSubject:真实对象。Proxy:代理对象,持有真实对象。
静态代理
接口:
java
interface UserService {
void createUser(String username);
}真实对象:
java
class UserServiceImpl implements UserService {
public void createUser(String username) {
System.out.println("create user: " + username);
}
}代理对象:
java
class UserServiceProxy implements UserService {
private final UserService target;
UserServiceProxy(UserService target) {
this.target = target;
}
public void createUser(String username) {
System.out.println("check permission");
long start = System.currentTimeMillis();
target.createUser(username);
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
}使用:
java
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.createUser("justin");静态代理清楚直观,但每个接口都要写代理类。
JDK 动态代理
JDK 动态代理要求目标对象实现接口。
java
import java.lang.reflect.Proxy;
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{UserService.class},
(object, method, args) -> {
System.out.println("before method");
Object result = method.invoke(target, args);
System.out.println("after method");
return result;
}
);
proxy.createUser("justin");Spring AOP 在很多情况下就会使用代理思想。
常见代理类型
| 类型 | 说明 |
|---|---|
| 静态代理 | 手写代理类 |
| JDK 动态代理 | 基于接口生成代理 |
| CGLIB 代理 | 基于继承生成子类代理 |
| 远程代理 | 屏蔽远程调用细节 |
| 虚拟代理 | 延迟创建重对象 |
| 保护代理 | 做权限控制 |
使用场景
- Spring AOP。
- 事务、权限、日志、监控。
- RPC 客户端代理。
- MyBatis Mapper 接口代理。
- 懒加载对象。
优点
- 不修改目标类即可增强行为。
- 增强逻辑可以复用。
- 调用方可以面向接口编程。
- 适合处理横切关注点。
缺点
- 调用链变长,调试时需要理解代理层。
- 动态代理有一定性能开销。
- 代理配置不当可能导致方法没有被增强。
- CGLIB 代理无法代理
final类或final方法。
和装饰器模式的区别
| 模式 | 重点 |
|---|---|
| 代理 | 控制访问目标对象 |
| 装饰器 | 动态增加目标对象能力 |
代理更强调“访问控制”,装饰器更强调“功能叠加”。
实战注意
在 Spring 中,同一个类内部方法互相调用时,可能绕过代理,导致事务或 AOP 不生效。这是使用代理时非常常见的问题。