跳到主要内容

代理模式(Proxy)

代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理可以在不改变原始对象代码的情况下,添加额外的功能或控制逻辑。

模式定义

代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。

核心要点

  1. 控制访问:代理控制对真实对象的访问
  2. 接口一致:代理与真实对象实现相同的接口
  3. 延迟加载:可以在需要时才创建真实对象
  4. 透明性:客户端不需要知道是否使用了代理

问题场景

假设我们有一个加载大图片的类,直接加载会消耗大量时间和内存:

// 问题代码:直接加载大图片
public class Image {
private String filename;

public Image(String filename) {
this.filename = filename;
loadFromDisk(); // 构造时就加载,耗时
}

private void loadFromDisk() {
System.out.println("加载图片: " + filename);
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public void display() {
System.out.println("显示图片: " + filename);
}
}

// 使用
Image image = new Image("photo.jpg"); // 立即加载,即使不显示
image.display();

存在的问题

  • 即使不显示图片,也会立即加载
  • 加载大图片消耗大量内存和时间
  • 影响程序启动速度

解决方案

使用代理模式,创建一个图片代理,在真正需要显示时才加载图片:

// 图片接口
public interface Image {
void display();
}

// 真实图片
public class RealImage implements Image {
private String filename;

public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}

private void loadFromDisk() {
System.out.println("加载图片: " + filename);
}

@Override
public void display() {
System.out.println("显示图片: " + filename);
}
}

// 图片代理
public class ImageProxy implements Image {
private String filename;
private RealImage realImage; // 延迟初始化

public ImageProxy(String filename) {
this.filename = filename;
}

@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // 首次使用时才加载
}
realImage.display();
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
Image image = new ImageProxy("photo.jpg"); // 不加载
System.out.println("图片代理创建完成");

image.display(); // 此时才加载
image.display(); // 复用已加载的图片
}
}

输出

图片代理创建完成
加载图片: photo.jpg
显示图片: photo.jpg
显示图片: photo.jpg

模式结构

组成部分

  • Subject(主题接口):定义真实对象和代理的公共接口
  • RealSubject(真实主题):定义代理所代表的真实对象
  • Proxy(代理):持有真实主题的引用,控制对真实主题的访问
  • Client(客户端):通过主题接口与代理交互

代理类型

1. 虚拟代理(Virtual Proxy)

延迟创建开销大的对象,在真正需要时才创建:

// 数据库连接代理
public interface DatabaseConnection {
void execute(String sql);
void close();
}

// 真实数据库连接
public class RealDatabaseConnection implements DatabaseConnection {
private String url;

public RealDatabaseConnection(String url) {
this.url = url;
connect(); // 耗时操作
}

private void connect() {
System.out.println("连接数据库: " + url);
}

@Override
public void execute(String sql) {
System.out.println("执行 SQL: " + sql);
}

@Override
public void close() {
System.out.println("关闭数据库连接");
}
}

// 数据库连接代理(延迟连接)
public class DatabaseConnectionProxy implements DatabaseConnection {
private String url;
private RealDatabaseConnection realConnection;

public DatabaseConnectionProxy(String url) {
this.url = url;
}

@Override
public void execute(String sql) {
if (realConnection == null) {
realConnection = new RealDatabaseConnection(url);
}
realConnection.execute(sql);
}

@Override
public void close() {
if (realConnection != null) {
realConnection.close();
}
}
}

2. 保护代理(Protection Proxy)

控制对原始对象的访问权限:

// 文档接口
public interface Document {
void read();
void write(String content);
}

// 真实文档
public class RealDocument implements Document {
private String content;
private String owner;

public RealDocument(String content, String owner) {
this.content = content;
this.owner = owner;
}

@Override
public void read() {
System.out.println("读取内容: " + content);
}

@Override
public void write(String content) {
this.content = content;
System.out.println("写入内容: " + content);
}

public String getOwner() {
return owner;
}
}

// 保护代理(权限控制)
public class DocumentProxy implements Document {
private RealDocument document;
private String currentUser;

public DocumentProxy(RealDocument document, String currentUser) {
this.document = document;
this.currentUser = currentUser;
}

@Override
public void read() {
document.read(); // 所有人都可以读
}

@Override
public void write(String content) {
if (currentUser.equals(document.getOwner())) {
document.write(content); // 只有所有者可以写
} else {
System.out.println("权限不足:只有 " + document.getOwner() + " 可以编辑");
}
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
RealDocument doc = new RealDocument("初始内容", "张三");

Document proxy1 = new DocumentProxy(doc, "张三");
proxy1.read();
proxy1.write("新内容"); // 成功

Document proxy2 = new DocumentProxy(doc, "李四");
proxy2.read();
proxy2.write("其他内容"); // 权限不足
}
}

3. 远程代理(Remote Proxy)

为远程对象提供本地代理,隐藏网络通信细节:

// 服务接口
public interface UserService {
User getUser(Long id);
void saveUser(User user);
}

// 远程服务代理(模拟 RPC 调用)
public class RemoteUserServiceProxy implements UserService {
private String serverUrl;

public RemoteUserServiceProxy(String serverUrl) {
this.serverUrl = serverUrl;
}

@Override
public User getUser(Long id) {
// 模拟远程调用
System.out.println("调用远程服务: " + serverUrl + "/users/" + id);
String json = httpClientGet(serverUrl + "/users/" + id);
return parseUser(json);
}

@Override
public void saveUser(User user) {
System.out.println("调用远程服务: " + serverUrl + "/users");
String json = serializeUser(user);
httpClientPost(serverUrl + "/users", json);
}

private String httpClientGet(String url) {
return "{\"id\":1,\"name\":\"张三\"}";
}

private void httpClientPost(String url, String body) {
System.out.println("POST: " + body);
}

private User parseUser(String json) {
return new User(1L, "张三");
}

private String serializeUser(User user) {
return "{\"id\":" + user.getId() + ",\"name\":\"" + user.getName() + "\"}";
}
}

4. 智能引用代理(Smart Reference)

在访问对象时添加额外操作,如引用计数、缓存:

// 资源接口
public interface Resource {
void access();
}

// 真实资源
public class RealResource implements Resource {
private String name;

public RealResource(String name) {
this.name = name;
}

@Override
public void access() {
System.out.println("访问资源: " + name);
}
}

// 智能引用代理(引用计数)
public class ResourceProxy implements Resource {
private RealResource resource;
private int accessCount = 0;

public ResourceProxy(RealResource resource) {
this.resource = resource;
}

@Override
public void access() {
accessCount++;
System.out.println("访问次数: " + accessCount);
resource.access();
}

public int getAccessCount() {
return accessCount;
}
}

// 缓存代理
public class CacheProxy implements UserService {
private UserService realService;
private Map<Long, User> cache = new HashMap<>();

public CacheProxy(UserService realService) {
this.realService = realService;
}

@Override
public User getUser(Long id) {
User cached = cache.get(id);
if (cached != null) {
System.out.println("从缓存获取用户: " + id);
return cached;
}

User user = realService.getUser(id);
cache.put(id, user);
return user;
}

@Override
public void saveUser(User user) {
realService.saveUser(user);
cache.remove(user.getId()); // 更新缓存
}
}

实际应用案例

1. JDK 动态代理

Java 提供的动态代理机制,可以在运行时创建代理:

// 调用处理器
public class LoggingHandler implements InvocationHandler {
private Object target;

public LoggingHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法: " + method.getName());
long start = System.currentTimeMillis();

Object result = method.invoke(target, args);

long end = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (end - start) + "ms");

return result;
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
UserService realService = new UserServiceImpl();

UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[] { UserService.class },
new LoggingHandler(realService)
);

proxy.getUser(1L);
}
}

2. Spring AOP

Spring AOP 使用代理模式实现面向切面编程:

// 目标类
@Service
public class UserService {
public User getUser(Long id) {
return userRepository.findById(id);
}
}

// 切面定义
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
System.out.println("开始执行: " + methodName);

Object result = joinPoint.proceed();

System.out.println("执行完成: " + methodName);
return result;
}
}

// Spring 会自动创建代理
// JDK 动态代理(有接口)或 CGLIB(无接口)

3. MyBatis Mapper 代理

MyBatis 使用动态代理创建 Mapper 接口的实现:

// Mapper 接口
public interface UserMapper {
User selectById(Long id);
List<User> selectAll();
int insert(User user);
}

// MyBatis 自动创建代理实现
// 开发者只需要定义接口和 SQL 映射文件

4. Hibernate 懒加载

Hibernate 使用代理实现实体的懒加载:

@Entity
public class Order {
@Id
private Long id;

@ManyToOne(fetch = FetchType.LAZY) // 懒加载
private User user; // 代理对象,访问时才加载

// getters and setters
}

// 使用
Order order = orderRepository.findById(1L);
// 此时 user 是代理对象,没有加载数据

User user = order.getUser(); // 此时才从数据库加载

代理模式 vs 装饰器模式

特性代理模式装饰器模式
目的控制访问添加功能
对象创建代理控制客户端控制
生命周期管理对象生命周期不管理生命周期
关注点访问控制、延迟加载功能增强

代理模式 vs 适配器模式

特性代理模式适配器模式
接口保持接口不变改变接口
目的控制访问接口转换
关系代理与真实对象实现相同接口适配器实现目标接口

优缺点分析

优点

  1. 单一职责原则:代理将控制逻辑与业务逻辑分离
  2. 开闭原则:可以在不修改真实对象的情况下引入代理
  3. 延迟加载:虚拟代理可以延迟创建开销大的对象
  4. 访问控制:保护代理可以控制访问权限
  5. 透明性:客户端不需要知道是否使用了代理

缺点

  1. 增加复杂性:引入额外的代理类
  2. 响应延迟:代理层可能增加响应时间
  3. 调试困难:多层代理可能导致调试困难

使用建议

何时使用代理

  • 需要延迟初始化开销大的对象
  • 需要控制对对象的访问权限
  • 需要为远程对象提供本地代理
  • 需要在访问对象时添加额外操作(日志、缓存)

何时避免使用代理

  • 直接访问对象更简单
  • 代理会导致性能问题
  • 不需要任何代理功能

最佳实践

  1. 选择合适的代理类型:根据需求选择虚拟代理、保护代理等
  2. 保持接口一致:代理与真实对象实现相同接口
  3. 考虑动态代理:Java 动态代理可以减少代码量
  4. 注意线程安全:代理中的状态需要考虑并发访问

小结

代理模式是一种常用的结构型模式:

代理类型应用场景
虚拟代理延迟加载大对象
保护代理权限控制
远程代理远程调用
智能引用引用计数、缓存

练习

  1. 实现一个数据库连接池代理,控制连接的获取和释放
  2. 使用 JDK 动态代理实现一个通用的日志代理
  3. 实现一个缓存代理,缓存方法返回值
  4. 分析 Spring AOP 的代理实现原理

参考资源