在软件开发中,我们经常需要控制对某些对象的访问——可能是为了延迟加载、添加额外功能或保护敏感资源。这正是代理模式大显身手的地方。作为结构型设计模式的重要成员,代理模式在众多知名框架和系统中扮演着关键角色。本文将全面剖析代理模式的方方面面,带你领略这一模式的精妙之处。
一、代理模式概述
1.1 什么是代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。简单来说,代理就是一个"替身",它代表另一个对象(即真实对象)执行某些操作,同时可以在访问真实对象前后添加自己的逻辑。
1.2 代理模式的核心思想
代理模式的核心在于"控制访问",它遵循了面向对象设计原则中的"单一职责原则"和"开闭原则":
单一职责:真实对象只需关注核心业务逻辑,而访问控制、日志记录等职责交给代理
开闭原则:可以在不修改真实对象代码的情况下,通过代理扩展功能
1.3 生活中的代理类比
现实生活中代理的例子比比皆是:
房屋中介:代理房东处理租房事宜
明星经纪人:代理明星安排演出和商业活动
信用卡:代理银行账户进行支付
这些代理的共同特点是:它们都代表另一个实体执行操作,同时可以添加自己的处理逻辑(如中介收取佣金、经纪人筛选邀约等)。
二、代理模式的结构与实现
2.1 UML类图
2.2 核心角色
Subject(抽象主题)
定义真实主题和代理主题的共同接口
可以是接口或抽象类
RealSubject(真实主题)
实现真实业务逻辑的类
是被代理的对象
Proxy(代理)
包含对真实主题的引用
实现与真实主题相同的接口
可以控制对真实主题的访问
2.3 Java实现示例
// 抽象主题
interface Database {
void query(String sql);
}
// 真实主题
class RealDatabase implements Database {
@Override
public void query(String sql) {
System.out.println("执行查询: " + sql);
// 实际数据库操作...
}
}
// 代理
class DatabaseProxy implements Database {
private RealDatabase realDatabase;
private List<String> blacklist = Arrays.asList("DELETE", "DROP", "TRUNCATE");
@Override
public void query(String sql) {
// 安全检查
if (containsBlacklistedKeywords(sql)) {
throw new RuntimeException("查询包含危险操作");
}
// 日志记录
System.out.println("[" + LocalDateTime.now() + "] 执行查询: " + sql);
// 延迟初始化
if (realDatabase == null) {
realDatabase = new RealDatabase();
}
// 执行实际查询
realDatabase.query(sql);
// 后置处理
System.out.println("查询完成");
}
private boolean containsBlacklistedKeywords(String sql) {
return blacklist.stream().anyMatch(sql::contains);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Database database = new DatabaseProxy();
database.query("SELECT * FROM users"); // 正常执行
database.query("DROP TABLE users"); // 抛出异常
}
}
这个示例展示了一个数据库查询代理,它实现了:
安全检查(保护代理)
日志记录(智能引用代理)
延迟初始化(虚拟代理)
三、代理模式的类型
3.1 远程代理(Remote Proxy)
特点:为位于不同地址空间的对象提供本地代表
应用场景:
RPC(远程过程调用)
Web服务客户端
分布式系统中的存根(Stub)
示例:
// 远程服务接口
interface WeatherService {
String getWeather(String city);
}
// 本地代理
class WeatherServiceProxy implements WeatherService {
@Override
public String getWeather(String city) {
// 通过网络调用远程服务
return callRemoteWeatherService(city);
}
private String callRemoteWeatherService(String city) {
// 实际网络通信逻辑...
return "Sunny";
}
}
3.2 虚拟代理(Virtual Proxy)
特点:根据需要创建开销很大的对象
应用场景:
大图加载
复杂对象初始化
资源密集型操作
示例:
class HighResolutionImage implements Image {
public HighResolutionImage(String path) {
loadImage(path); // 耗时操作
}
private void loadImage(String path) {
// 加载大图...
}
}
class ImageProxy implements Image {
private String path;
private HighResolutionImage realImage;
public ImageProxy(String path) {
this.path = path;
}
@Override
public void show() {
if (realImage == null) {
realImage = new HighResolutionImage(path); // 延迟加载
}
realImage.show();
}
}
3.3 保护代理(Protection Proxy)
特点:控制对原始对象的访问权限
应用场景:
权限控制
敏感操作保护
访问限制
示例:
interface BankAccount {
void withdraw(double amount);
double getBalance();
}
class RealBankAccount implements BankAccount {
private double balance;
@Override
public void withdraw(double amount) {
balance -= amount;
}
@Override
public double getBalance() {
return balance;
}
}
class BankAccountProxy implements BankAccount {
private RealBankAccount account;
private User user;
public BankAccountProxy(User user) {
this.user = user;
this.account = new RealBankAccount();
}
@Override
public void withdraw(double amount) {
if (user.hasPermission("WITHDRAW")) {
account.withdraw(amount);
} else {
throw new SecurityException("无取款权限");
}
}
@Override
public double getBalance() {
if (user.hasPermission("VIEW_BALANCE")) {
return account.getBalance();
} else {
throw new SecurityException("无查看余额权限");
}
}
}
3.4 智能引用代理(Smart Reference Proxy)
特点:在访问对象时执行附加操作
应用场景:
引用计数
对象池管理
缓存机制
示例:
class ExpensiveObject {
void process() {
System.out.println("处理中...");
}
}
class SmartProxy {
private ExpensiveObject realObject;
private int accessCount = 0;
public void process() {
if (realObject == null) {
realObject = new ExpensiveObject();
}
accessCount++;
System.out.println("访问次数: " + accessCount);
realObject.process();
if (accessCount >= 5) {
System.out.println("重置对象...");
realObject = null;
accessCount = 0;
}
}
}
四、代理模式的深入应用
4.1 Spring框架中的代理
Spring框架广泛使用代理模式,主要体现在:
AOP(面向切面编程)
Spring AOP使用JDK动态代理或CGLIB代理实现
为业务组件添加事务管理、日志记录等横切关注点
事务管理
@Transactional注解背后的代理机制
在方法调用前后管理事务边界
@Configuration类
配置类的代理确保@Bean方法单例性
示例:
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 数据库操作
}
}
// Spring在运行时创建代理类
class UserServiceProxy extends UserService {
private UserService target;
private PlatformTransactionManager txManager;
@Override
public void createUser(User user) {
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
try {
target.createUser(user);
txManager.commit(status);
} catch (Exception e) {
txManager.rollback(status);
throw e;
}
}
}
4.2 MyBatis中的代理
MyBatis使用JDK动态代理实现Mapper接口:
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
}
// MyBatis在运行时生成代理实现
class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 解析注解中的SQL
String sql = method.getAnnotation(Select.class).value();
// 执行SQL并返回结果
return sqlSession.selectOne(sql, args[0]);
}
}
4.3 RPC框架中的代理
远程方法调用(RPC)框架如Dubbo、gRPC都使用代理模式:
// 服务接口
public interface OrderService {
Order getOrder(long id);
}
// 客户端代理
class OrderServiceProxy implements OrderService {
private String serviceUrl;
@Override
public Order getOrder(long id) {
// 序列化参数
byte[] request = serialize(id);
// 网络调用
byte[] response = sendRequest(serviceUrl, request);
// 反序列化结果
return deserialize(response);
}
}
五、代理模式的优缺点
5.1 优点
职责分离:代理对象处理非功能性需求(如安全、日志),真实对象专注业务逻辑
开闭原则:无需修改真实对象即可扩展功能
访问控制:代理可以控制对真实对象的访问
性能优化:虚拟代理可以实现延迟加载,提高系统响应速度
5.2 缺点
复杂度增加:引入代理层会增加系统复杂度
性能开销:代理调用会带来额外的处理时间
间接性:可能使调试变得困难,因为调用栈更深
六、代理模式与相关模式的比较
6.1 代理模式 vs 装饰器模式
对比维度 | 代理模式 | 装饰器模式 |
---|---|---|
目的 | 控制访问 | 增强功能 |
关系 | 代理知道被代理对象的生命周期 | 装饰器与被装饰对象独立 |
关注点 | 访问机制(如延迟加载、权限控制) | 添加新行为 |
6.2 代理模式 vs 适配器模式
对比维度 | 代理模式 | 适配器模式 |
---|---|---|
接口 | 保持相同接口 | 转换不同接口 |
目的 | 控制访问 | 解决接口不兼容问题 |
使用时机 | 设计阶段规划 | 后期集成时使用 |
七、实际应用建议
何时使用代理模式:
需要延迟初始化(虚拟代理)
需要控制资源访问(保护代理)
需要添加横切关注点(AOP)
需要远程调用(远程代理)
实现选择:
静态代理:代理类在编译时确定,适合简单场景
动态代理:运行时生成代理类,更灵活(JDK动态代理、CGLIB)
性能考虑:
对于频繁调用的方法,注意代理带来的性能开销
考虑使用轻量级代理或直接访问
设计原则:
遵循"最少知识原则",代理不应暴露过多真实对象细节
保持代理接口简洁,避免成为"上帝对象"
结语
代理模式作为设计模式家族中的重要成员,其应用范围从简单的对象访问控制到复杂的框架实现无处不在。理解并掌握代理模式,不仅能帮助我们设计出更加灵活、安全的系统,还能深入理解众多流行框架的内部工作机制。无论是日常开发中的权限控制、日志记录,还是分布式系统中的远程调用,代理模式都展现出其强大的适应性和生命力。希望本文能为你打开代理模式的大门,助你在软件设计之路上更进一步。