设计模式——Proxy(代理)设计模型
本文介绍了代理设计模式(Proxy Pattern),这是一种结构型设计模式,通过引入代理对象来控制对原对象的访问。文章详细阐述了代理模式的定义、作用、优点、缺点,并提供了类图实现。此外,还探讨了代理模式的多种使用场景,包括远程代理、虚拟代理、保护代理等,并以图片延迟加载为例,展示了虚拟代理的实际应用。
摘要
本文介绍了代理设计模式(Proxy Pattern),这是一种结构型设计模式,通过引入代理对象来控制对原对象的访问。文章详细阐述了代理模式的定义、作用、优点、缺点,并提供了类图实现。此外,还探讨了代理模式的多种使用场景,包括远程代理、虚拟代理、保护代理等,并以图片延迟加载为例,展示了虚拟代理的实际应用。
1. 代理设计模式是什么
1.1. 代理设计模式
代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做 Proxy 或 Surrogate,它是一种对象结构型模式。
在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。如上面的示例中,Tony 因为不在家,所以不能亲自接收包裹,但他可以叫 Wendy 来代他接收,这里 Wendy 就是代理,她代理了 Tony 的身份去接收快递。
1.2. 代理设计模式作用
代理对象可以在客户端和目标对象之间起到中间调和的作用,并且可以通过代理对象隐藏不希望被客户端看到的内容和服务,或者添加客户需要的额外服务。
在实现生活中能找到非常的代理模式的模型:火车票/机票的代售点;银行支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制;代表公司出席一些商务会议。
1.2.1. 优点
- 增强功能:在不修改目标对象的情况下添加额外功能(如权限校验、缓存)。
- 解耦目标对象:代理对象可以屏蔽底层实现细节,让客户端专注于业务逻辑。
- 控制访问:通过代理可以限制对目标对象的直接访问。
1.2.2. 缺点
- 增加了系统复杂性。
- 可能引入额外的性能开销(特别是动态代理)。
2. 代理设计模式类图实现
2.1. 代理设计类模型
代理模式有三个关键要素,它们分别是:
- 主题(Subject):定义“操作/活动/任务”的接口类。
- 真实主题(RealSubject):真正完成“操作/活动/任务”的具体类。
- 代理主题(ProxySubject):代替真实主题完成“操作/活动/任务”的代理类。
// 定义主题接口
interface Subject {
void request();
}
// 真实主题类
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject todo something...");
}
}
// 代理主题类
class ProxySubject implements Subject {
private RealSubject realSubject;
// 构造函数,传入真实主题对象
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
preRequest();
if (realSubject != null) {
realSubject.request();
}
afterRequest();
}
private void preRequest() {
System.out.println("preRequest");
}
private void afterRequest() {
System.out.println("afterRequest");
}
}
// 客户端调用类
public class Client {
public static void main(String[] args) {
RealSubject realObj = new RealSubject(); // 创建真实主题对象
ProxySubject proxyObj = new ProxySubject(realObj); // 创建代理主题对象
proxyObj.request(); // 调用代理对象的方法
}
}
2.2. 代理和装饰器的区别
特征 |
代理模式(Proxy) |
装饰器模式(Decorator) |
目的 |
控制对目标对象的访问 |
动态地增加目标对象的功能 |
核心关注点 |
控制访问权限、延迟加载、远程调用等 |
增强对象的功能,不改变原有对象的接口和实现 |
对象的关系 |
代理对象和目标对象通常实现相同的接口或继承自同一类 |
装饰器通过组合关系“装饰”目标对象 |
实现方式 |
代理对象替代目标对象,拦截并转发请求 |
装饰器在目标对象上增加功能,通过组合增强目标对象的行为 |
功能扩展 |
主要关注访问控制、代理行为(例如延迟加载、权限控制等) |
主要关注扩展目标对象的功能(例如日志记录、性能监控等) |
目标对象的修改 |
目标对象通常保持不变,通过代理访问 |
目标对象不修改,通过装饰器增加额外功能 |
多个实例组合 |
代理一般不会多个叠加使用(每个目标通常只有一个代理) |
可以有多个装饰器叠加使用,逐步增加功能 |
2.2.1. 使用场景
- 代理模式:
-
- 用于控制对目标对象的访问,常见场景如远程代理、虚拟代理、安全代理(权限控制)等。
- 适用于需要控制访问权限、做性能优化(如延迟加载)或者进行远程调用的情况。
- 装饰器模式:
-
- 用于动态地扩展对象的功能,常见场景如日志记录、权限检查、缓存、性能监控等。
- 适用于需要在运行时为对象添加行为的情况,可以通过组合多个装饰器来实现功能扩展。
2.2.2. 总结:
- 代理模式关注的是对目标对象的“控制”,通常用于控制访问、权限验证等。
- 装饰器模式关注的是对目标对象的“功能扩展”,可以通过多个装饰器组合,增强对象的行为。
两者的核心差异在于目的:代理模式用于控制访问,而装饰器模式用于增强功能。在设计时,需要根据需求选择适合的模式。
3. 代理设计模式使用场景
3.1. 远程代理(Remote Proxy)
- 场景:需要访问位于远程的对象(如跨网络的服务)。
- 示例:RMI (Remote Method Invocation),访问远程服务器上的对象时,客户端不直接调用目标对象,而是通过代理对象与服务器通信。
典型应用:
- 分布式系统中的服务调用。
- 数据库访问代理(如通过代理实现负载均衡)。
3.2. 虚拟代理(Virtual Proxy)
- 场景:当需要一个对象的高成本初始化或对象比较耗资源时,使用代理对象代替真正的对象,只有在真正需要时才创建目标对象。
- 示例:图片加载延迟加载机制。
典型应用:
- 图片、视频等大文件的延迟加载。
- 大型复杂对象的延迟初始化。
3.3. 保护代理(Protection Proxy)
- 场景:用于控制对目标对象的访问权限,通过代理限制某些功能的使用。
- 示例:文件系统操作代理,仅允许某些用户访问特定文件。
典型应用:
- 权限控制。
- 方法调用的访问限制。
3.4. 缓存代理(Cache Proxy)
- 场景:当目标对象返回结果可被缓存时,代理对象缓存结果,从而减少目标对象的计算或数据获取开销。
- 示例:Web 页面缓存代理,避免重复访问相同的页面。
典型应用:
- 数据缓存(如 API 数据、数据库查询结果)。
- 文件系统的读写优化。
3.5. 防火墙代理(Firewall Proxy)
- 场景:保护目标对象不受外部访问,通过代理限制某些 IP 或请求的访问。
- 示例:网络访问防火墙代理。
典型应用:
- 网络流量过滤。
- 系统安全防护。
3.6. 智能引用代理(Smart Proxy)
- 场景:在目标对象调用前后增加额外的逻辑,例如统计目标对象的方法调用次数或记录日志。
- 示例:动态代理机制中的日志记录。
典型应用:
- 方法调用统计。
- 日志记录或监控系统。
3.7. 动态代理(Dynamic Proxy)
- 场景:运行时动态创建代理对象,适合需要为大量接口提供统一代理逻辑的场景。
- 示例:Java 的
java.lang.reflect.Proxy
。
典型应用:
- 面向切面编程(AOP)。
- 框架中动态增强对象功能(如 Spring 的事务管理、权限校验)。
3.8. 图片延迟加载(虚拟代理)
- 问题:在图形界面应用中,加载高清图片耗时较长。
- 解决方案:使用代理对象先展示占位图片,待图片加载完成后再替换。
3.9. 用户权限验证(保护代理)
- 问题:系统中不同用户角色访问权限不同。
- 解决方案:代理对象在调用目标对象之前进行权限检查,确保只有有权限的用户才能调用方法。
3.10. Web 缓存代理
- 问题:频繁访问同一 API 会造成资源浪费。
- 解决方案:代理缓存 API 返回结果,减少重复请求。
3.11. 数据库连接池代理
- 问题:数据库连接昂贵,频繁创建销毁连接性能低。
- 解决方案:代理通过连接池复用现有连接,提高性能。
4. 代理设计模式示例(spring)
4.1. 通过Spring AOP实现代理设计模式
首先,在 pom.xml
中添加 Spring AOP 和 Spring Context 相关的依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version> <!-- 使用适合的版本 -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.23</version> <!-- 使用适合的版本 -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.23</version> <!-- 使用适合的版本 -->
</dependency>
</dependencies>
创建一个 UserService
类,模拟执行某些操作(如修改用户信息)。
package com.zhuangxiaoyan.hyxftest.proxy;
import org.springframework.stereotype.Service;
/**
* UserService
*
* @author xjl
* @version 2024/12/01 21:35
**/
@Service
public interface UserService {
void addUser(String name);
void deleteUser(String name);
}
package com.zhuangxiaoyan.hyxftest.proxy;
import org.springframework.stereotype.Service;
/**
* UserServiceImpl
*
* @author xjl
* @version 2024/12/01 23:18
**/
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("Adding user: " + name);
}
@Override
public void deleteUser(String name) {
System.out.println("Deleting user: " + name);
}
}
创建 AOP 切面类:AOP 切面类用于定义我们需要增强的功能(如日志记录、权限校验等)。Spring AOP 会在 UserService
方法执行前后插入增强逻辑。
package com.zhuangxiaoyan.hyxftest.proxy;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* LoggingAspect
*
* @author xjl
* @version 2024/12/01 21:36
**/
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.zhuangxiaoyan.hyxftest.proxy.UserService.addUser(..))")
public void addUserMethod() {
}
@Pointcut("execution(* com.zhuangxiaoyan.hyxftest.proxy.UserService.deleteUser(..))")
public void deleteUserMethod() {
}
@Before("addUserMethod()")
public void logBeforeAddUser() {
System.out.println("Logging before adding a user.");
}
@After("addUserMethod()")
public void logAfterAddUser() {
System.out.println("Logging after adding a user.");
}
@Before("deleteUserMethod()")
public void logBeforeDeleteUser() {
System.out.println("Logging before deleting a user.");
}
@After("deleteUserMethod()")
public void logAfterDeleteUser() {
System.out.println("Logging after deleting a user.");
}
}
代理模式测试类
package com.zhuangxiaoyan.hyxftest.test;
import com.zhuangxiaoyan.hyxftest.HyxfTestApplication;
import com.zhuangxiaoyan.hyxftest.facade.OrderFacade;
import com.zhuangxiaoyan.hyxftest.proxy.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(classes = HyxfTestApplication.class)
public class HyxfTestApplicationTests {
@Autowired
private UserService userService;
@Test
public void run() {
// 模拟处理订单
String userId = "user123";
String productId = "product456";
double amount = 99.99;
boolean result = orderFacade.processOrder(userId, productId, amount);
if (result) {
System.out.println("Order processed successfully!");
} else {
System.out.println("Order processing failed!");
}
}
@Test
public void proxyTest() {
userService.addUser("Alice");
userService.deleteUser("Bob");
}
}
4.2. 通过JDK动态代理实现代理设计模式
如果你希望手动实现代理(例如JDK动态代理),可以使用java.lang.reflect.Proxy
类。
定义接口和实现类
public interface UserService {
void addUser(String name);
void deleteUser(String name);
}
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("Adding user: " + name);
}
@Override
public void deleteUser(String name) {
System.out.println("Deleting user: " + name);
}
}
创建代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserServiceProxy implements InvocationHandler {
private final UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
public UserService createProxy() {
return (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Logging before method: " + method.getName());
Object result = method.invoke(userService, args);
System.out.println("Logging after method: " + method.getName());
return result;
}
}
使用代理
public class MainApplication {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy(userService);
// 获取代理对象
UserService userServiceProxy = proxy.createProxy();
// 调用代理对象的方法
userServiceProxy.addUser("Alice");
userServiceProxy.deleteUser("Bob");
}
}
输出结果
Logging before method: addUser
Adding user: Alice
Logging after method: addUser
Logging before method: deleteUser
Deleting user: Bob
Logging after method: deleteUser
4.3. 两种实现方式总结
- 通过Spring AOP:这种方式是Spring Boot中实现代理设计模式的推荐方式。通过
@Aspect
和切面通知,你可以在不修改目标类的情况下增强其功能,适用于日志、权限校验等横切关注点。 - 通过JDK动态代理:这种方式适用于手动实现代理模式,适合需要完全控制代理逻辑的情况。使用
java.lang.reflect.Proxy
类,你可以在运行时动态创建代理对象。
Spring AOP的优势在于,代码更加简洁,容易与Spring管理的对象一起使用,而JDK动态代理的优势在于提供了更高的灵活性,能够在运行时创建代理。
博文参考
《软件设计模式》
更多推荐
所有评论(0)