代理模式 (Proxy Pattern)
概述
代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
意图
- 为其他对象提供一种代理以控制对这个对象的访问
适用场景
- 远程代理:为一个对象在不同的地址空间提供局部代表
- 虚拟代理:根据需要创建开销很大的对象
- 保护代理:控制对原始对象的访问
- 智能指引:取代了简单的指针,它在访问对象时执行一些附加操作
结构
┌─────────────┐ ┌─────────────┐
│ Client │──────────>│ Proxy │
├─────────────┤ ├─────────────┤
│ │ │ - realSubject│
└─────────────┘ │ + request() │
└─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ Subject │<─────────│RealSubject │
├─────────────┤ ├─────────────┤
│ + request() │ │ + request() │
└─────────────┘ └─────────────┘
参与者
- Subject:定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy
- RealSubject:定义Proxy所代表的真实实体
- Proxy:保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体
示例代码
下面是一个完整的代理模式示例,以图片加载为例:
java
// Subject - 主题接口
public interface Image {
void display();
}
// RealSubject - 真实主题
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("显示图片: " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("从磁盘加载图片: " + fileName);
}
}
// Proxy - 代理类
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
System.out.println("从代理加载图片");
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
// 图片将从磁盘加载
image1.display();
System.out.println("");
// 图片不需要从磁盘加载
image1.display();
System.out.println("");
// 图片将从磁盘加载
image2.display();
System.out.println("");
// 图片不需要从磁盘加载
image2.display();
}
}
另一个示例 - 保护代理
java
// Subject - 主题接口
public interface BankAccount {
void deposit(double amount);
void withdraw(double amount);
double getBalance();
}
// RealSubject - 真实主题
public class RealBankAccount implements BankAccount {
private String accountNumber;
private double balance;
private String owner;
public RealBankAccount(String accountNumber, String owner, double initialBalance) {
this.accountNumber = accountNumber;
this.owner = owner;
this.balance = initialBalance;
}
@Override
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("存款成功,存入金额: " + amount);
} else {
System.out.println("存款金额必须大于0");
}
}
@Override
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("取款成功,取出金额: " + amount);
} else if (amount > balance) {
System.out.println("余额不足");
} else {
System.out.println("取款金额必须大于0");
}
}
@Override
public double getBalance() {
return balance;
}
public String getOwner() {
return owner;
}
}
// Proxy - 代理类
public class BankAccountProxy implements BankAccount {
private RealBankAccount realAccount;
private String currentUser;
public BankAccountProxy(String accountNumber, String owner, double initialBalance) {
this.realAccount = new RealBankAccount(accountNumber, owner, initialBalance);
}
public void setCurrentUser(String user) {
this.currentUser = user;
}
@Override
public void deposit(double amount) {
if (checkAccess()) {
realAccount.deposit(amount);
} else {
System.out.println("无权进行存款操作");
}
}
@Override
public void withdraw(double amount) {
if (checkAccess()) {
realAccount.withdraw(amount);
} else {
System.out.println("无权进行取款操作");
}
}
@Override
public double getBalance() {
if (checkAccess()) {
return realAccount.getBalance();
} else {
System.out.println("无权查看余额");
return 0;
}
}
private boolean checkAccess() {
// 简单的权限检查,只有账户所有者才能访问
return currentUser != null && currentUser.equals(realAccount.getOwner());
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建银行账户代理
BankAccount account = new BankAccountProxy("123456789", "张三", 1000.0);
// 设置当前用户为账户所有者
((BankAccountProxy) account).setCurrentUser("张三");
System.out.println("当前用户: 张三");
account.deposit(500.0);
account.withdraw(200.0);
System.out.println("余额: " + account.getBalance());
System.out.println();
// 设置当前用户为非账户所有者
((BankAccountProxy) account).setCurrentUser("李四");
System.out.println("当前用户: 李四");
account.deposit(500.0);
account.withdraw(200.0);
System.out.println("余额: " + account.getBalance());
}
}
动态代理示例
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// Subject - 主题接口
public interface UserService {
void addUser(String username, String password);
void deleteUser(String username);
String getUser(String username);
}
// RealSubject - 真实主题
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username, String password) {
System.out.println("添加用户: " + username);
}
@Override
public void deleteUser(String username) {
System.out.println("删除用户: " + username);
}
@Override
public String getUser(String username) {
System.out.println("获取用户: " + username);
return "User: " + username;
}
}
// InvocationHandler - 调用处理器
public class LogInvocationHandler implements InvocationHandler {
private 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() + ", 参数: " + java.util.Arrays.toString(args));
// 调用目标方法
Object result = method.invoke(target, args);
// 记录方法调用后的日志
System.out.println("方法 " + method.getName() + " 执行完成");
return result;
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建真实对象
UserService userService = new UserServiceImpl();
// 创建动态代理
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LogInvocationHandler(userService)
);
// 使用代理
proxy.addUser("张三", "123456");
proxy.getUser("张三");
proxy.deleteUser("张三");
}
}
优缺点
优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
- 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
- 保护代理可以控制对真实对象的使用权限
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂
相关模式
- 适配器模式:适配器模式改变对象的接口,而代理模式不改变对象的接口
- 装饰器模式:装饰器模式增强对象的功能,而代理模式控制对对象的访问
- 外观模式:外观模式简化接口,而代理模式保持接口不变
实际应用
- Java中的RMI(远程方法调用)
- Spring框架中的AOP(面向切面编程)
- Hibernate中的懒加载
- Java中的动态代理
- 防火墙代理
- 缓存代理
静态代理与动态代理
- 静态代理:在编译时就已经确定代理类和被代理类的关系,需要为每个被代理类创建一个代理类
- 动态代理:在运行时动态生成代理类,不需要为每个被代理类创建代理类,更加灵活
Java中的动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。
代理模式的类型
- 远程代理:为一个位于不同地址空间的对象提供一个本地代理
- 虚拟代理:根据需要创建开销很大的对象,通过代理来延迟对象的创建
- 保护代理:控制对原始对象的访问,用于对象有不同访问权限的时候
- 智能引用:取代了简单的指针,它在访问对象时执行一些附加操作,如计算引用次数等
注意事项
- 代理模式和装饰器模式的结构相似,但目的不同。代理模式控制访问,装饰器模式增加功能
- 代理模式可能会增加系统的复杂性,特别是在动态代理中
- 在使用代理模式时,需要权衡性能和功能之间的关系
- 代理模式通常与工厂模式一起使用,由工厂来创建代理对象