代理模式(Proxy)
代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理可以在不改变原始对象代码的情况下,添加额外的功能或控制逻辑。
模式定义
代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
核心要点
- 控制访问:代理控制对真实对象的访问
- 接口一致:代理与真实对象实现相同的接口
- 延迟加载:可以在需要时才创建真实对象
- 透明性:客户端不需要知道是否使用了代理
问题场景
假设我们有一个加载大图片的类,直接加载会消耗大量时间和内存:
// 问题代码:直接加载大图片
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 适配器模式
| 特性 | 代理模式 | 适配器模式 |
|---|---|---|
| 接口 | 保持接口不变 | 改变接口 |
| 目的 | 控制访问 | 接口转换 |
| 关系 | 代理与真实对象实现相同接口 | 适配器实现目标接口 |
优缺点分析
优点
- 单一职责原则:代理将控制逻辑与业务逻辑分离
- 开闭原则:可以在不修改真实对象的情况下引入代理
- 延迟加载:虚拟代理可以延迟创建开销大的对象
- 访问控制:保护代理可以控制访问权限
- 透明性:客户端不需要知道是否使用了代理
缺点
- 增加复杂性:引入额外的代理类
- 响应延迟:代理层可能增加响应时间
- 调试困难:多层代理可能导致调试困难
使用建议
何时使用代理
- 需要延迟初始化开销大的对象
- 需要控制对对象的访问权限
- 需要为远程对象提供本地代理
- 需要在访问对象时添加额外操作(日志、缓存)
何时避免使用代理
- 直接访问对象更简单
- 代理会导致性能问题
- 不需要任何代理功能
最佳实践
- 选择合适的代理类型:根据需求选择虚拟代理、保护代理等
- 保持接口一致:代理与真实对象实现相同接口
- 考虑动态代理:Java 动态代理可以减少代码量
- 注意线程安全:代理中的状态需要考虑并发访问
小结
代理模式是一种常用的结构型模式:
| 代理类型 | 应用场景 |
|---|---|
| 虚拟代理 | 延迟加载大对象 |
| 保护代理 | 权限控制 |
| 远程代理 | 远程调用 |
| 智能引用 | 引用计数、缓存 |
练习
- 实现一个数据库连接池代理,控制连接的获取和释放
- 使用 JDK 动态代理实现一个通用的日志代理
- 实现一个缓存代理,缓存方法返回值
- 分析 Spring AOP 的代理实现原理