代理模式
代理模式中文讲解
代理模式(Proxy Pattern)是一种结构型设计模式,属于 GoF(Gang of Four)提出的23种设计模式之一。它的核心目标是通过引入一个代理对象来控制对目标对象的访问,在不修改目标对象的情况下添加额外的功能(如权限检查、日志记录、延迟加载等)。代理模式就像一个“中介”,在客户端和目标对象之间充当桥梁。
以下用中文详细讲解代理模式的定义、结构、代码示例、应用场景、优缺点以及在 Servlet 环境中的使用,重点突出其原理和实际应用。
1. 什么是代理模式?
代理模式通过代理对象间接访问目标对象,允许在访问前后添加额外逻辑(如验证、缓存)。它解决了以下问题:
- 问题:直接访问目标对象可能导致权限控制不足、性能问题或复杂性(如远程调用、延迟初始化)。
- 解决方案:引入一个代理类,实现与目标对象相同的接口,控制访问并添加额外功能。
关键特点:
- 访问控制:代理控制对目标对象的访问,添加权限、日志等。
- 透明性:客户端通过代理接口操作,无需知道代理的存在。
- 功能增强:在不修改目标对象的情况下扩展功能。
代理模式类型:
- 静态代理:代理类在编译时确定,代码中明确定义。
- 动态代理:运行时动态生成代理对象(如 Java 的
java.lang.reflect.Proxy
)。 - 常见变体:
- 保护代理:控制访问权限。
- 虚拟代理:延迟加载目标对象。
- 远程代理:管理远程对象访问。
- 智能代理:添加额外逻辑(如缓存、日志)。
2. 代理模式的结构
代理模式包含以下角色:
角色 | 描述 |
---|---|
抽象主题(Subject) | 定义目标对象和代理的公共接口,声明核心方法(如 request() )。 |
真实主题(Real Subject) | 实现抽象主题,提供核心业务逻辑。 |
代理(Proxy) | 实现抽象主题,持有真实主题引用,控制访问并添加额外逻辑。 |
客户端(Client) | 通过抽象主题接口调用代理,间接访问真实主题。 |
UML 类图(文字描述):
- Subject:接口,声明
request()
方法。 - RealSubject:实现 Subject,提供核心功能。
- Proxy:实现 Subject,持有 RealSubject 引用,增强
request()
。 - 客户端通过 Subject 接口调用 Proxy,Proxy 委托 RealSubject。
3. 代码示例
静态代理示例
以下是一个静态代理示例,模拟通过代理控制对数据库服务的访问,添加日志功能。
// 抽象主题:数据库服务
interface DatabaseService {
void query(String sql);
}
// 真实主题:数据库实现
class RealDatabaseService implements DatabaseService {
@Override
public void query(String sql) {
System.out.println("执行 SQL 查询: " + sql);
}
}
// 代理:添加日志功能
class LoggingProxy implements DatabaseService {
private RealDatabaseService realService;
public LoggingProxy(RealDatabaseService realService) {
this.realService = realService;
}
@Override
public void query(String sql) {
System.out.println("日志: 开始执行查询 [" + sql + "]");
realService.query(sql);
System.out.println("日志: 查询完成");
}
}
// 测试
public class ProxyTest {
public static void main(String[] args) {
DatabaseService realService = new RealDatabaseService();
DatabaseService proxy = new LoggingProxy(realService);
proxy.query("SELECT * FROM users");
}
}
输出:
日志: 开始执行查询 [SELECT * FROM users]
执行 SQL 查询: SELECT * FROM users
日志: 查询完成
说明:
DatabaseService
是抽象主题,定义查询接口。RealDatabaseService
是真实主题,执行实际查询。LoggingProxy
是代理,添加日志功能,控制对realService
的访问。- 客户端通过
DatabaseService
接口调用代理,透明访问。
动态代理示例
Java 的动态代理使用 java.lang.reflect.Proxy
动态生成代理类。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 抽象主题
interface DatabaseService {
void query(String sql);
}
// 真实主题
class RealDatabaseService implements DatabaseService {
@Override
public void query(String sql) {
System.out.println("执行 SQL 查询: " + sql);
}
}
// 动态代理处理器
class LoggingHandler implements InvocationHandler {
private final 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() + " 方法");
Object result = method.invoke(target, args);
System.out.println("日志: 执行完成");
return result;
}
}
// 测试
public class DynamicProxyTest {
public static void main(String[] args) {
DatabaseService realService = new RealDatabaseService();
DatabaseService proxy = (DatabaseService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new LoggingHandler(realService)
);
proxy.query("SELECT * FROM users");
}
}
输出:
日志: 开始执行 query 方法
执行 SQL 查询: SELECT * FROM users
日志: 执行完成
说明:
LoggingHandler
实现InvocationHandler
,动态处理方法调用。Proxy.newProxyInstance
生成代理对象,运行时动态创建。- 动态代理更灵活,适用于多种接口和场景。
4. 代理模式的应用场景
- 权限控制:如限制对敏感资源的访问(保护代理)。
- 延迟加载:如延迟初始化数据库连接或大对象(虚拟代理)。
- 日志记录:在方法调用前后记录日志(智能代理)。
- 远程调用:如 RMI 或 Web 服务代理(远程代理)。
- Servlet 相关:控制请求处理、添加日志或权限检查。
Servlet 示例:代理请求处理器,添加权限检查。
import javax.servlet.http.*;
import java.io.IOException;
// 抽象主题:请求处理器
interface RequestProcessor {
void process(HttpServletRequest req, HttpServletResponse resp) throws IOException;
}
// 真实主题:实际处理器
class RealRequestProcessor implements RequestProcessor {
@Override
public void process(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h1>处理请求成功</h1>");
}
}
// 代理:添加权限检查
class AuthProxy implements RequestProcessor {
private final RealRequestProcessor realProcessor;
public AuthProxy(RealRequestProcessor realProcessor) {
this.realProcessor = realProcessor;
}
@Override
public void process(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String userRole = req.getParameter("role");
if ("admin".equals(userRole)) {
realProcessor.process(req, resp);
} else {
resp.setContentType("text/html;charset=UTF-8");
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "无权限访问");
}
}
}
// Servlet
public class ProxyServlet extends HttpServlet {
private final RequestProcessor processor = new AuthProxy(new RealRequestProcessor());
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
processor.process(req, resp);
}
}
输出(访问 /proxy?role=admin
):
<h1>处理请求成功</h1>
输出(访问 /proxy?role=user
):
403 Forbidden: 无权限访问
说明:
RequestProcessor
是抽象主题,定义处理接口。RealRequestProcessor
执行实际请求处理。AuthProxy
代理检查用户角色,仅允许管理员访问。- Servlet 使用代理控制访问,增强安全性。
5. 代理模式的优缺点
优点:
- 访问控制:有效控制对目标对象的访问(如权限、延迟加载)。
- 功能增强:添加日志、缓存等功能,无需修改目标类。
- 解耦:客户端与目标对象解耦,通过代理交互。
- 符合开闭原则:通过代理扩展功能,原类保持不变。
缺点:
- 复杂性:引入代理类,增加代码量和复杂度。
- 性能开销:代理层可能引入额外调用开销。
- 动态代理限制:仅适用于接口(Java 动态代理),CGLIB 可代理类但更复杂。
6. 注意事项
- 静态 vs 动态代理:
- 静态代理适合简单场景,代码明确但不灵活。
- 动态代理适合复杂场景,运行时生成代理,但需熟悉反射。
- 中文编码问题:
- 如果涉及中文(如错误消息),确保 Servlet 使用 UTF-8:
java resp.setContentType("text/html;charset=UTF-8");
- 对中文参数编码(如
URLEncoder.encode("无权限", "UTF-8")
)。
- 线程安全:
- 代理对象在 Servlet 环境中需考虑多线程访问。
- 真实主题(如
RealRequestProcessor
)应线程安全或无状态。
- 性能优化:
- 缓存代理实例,减少动态代理创建开销。
- 避免在代理中执行复杂逻辑。
- 与其他模式结合:
- 装饰器模式:代理控制访问,装饰器增强功能。
- 适配器模式:代理保持接口一致,适配器转换接口。
- 工厂模式:动态创建代理对象。
- 常见问题解决:
- 代理失效:检查代理是否正确实现接口或持有目标对象。
- 性能瓶颈:优化代理逻辑,减少反射调用。
- 异常处理:代理需捕获并处理目标对象的异常。
7. 与其他模式的区别
特性 | 代理模式 | 装饰器模式 | 适配器模式 |
---|---|---|---|
目的 | 控制对象访问 | 动态增强功能 | 转换接口兼容 |
实现方式 | 组合或继承 | 组合 | 组合或继承 |
关注点 | 访问控制 | 功能扩展 | 接口兼容 |
接口一致性 | 与原对象相同 | 与原对象相同 | 转换到新接口 |
8. 总结
- 代理模式通过代理对象控制目标对象访问,适合权限控制、延迟加载等场景。
- 核心是抽象主题、真实主题和代理,支持静态和动态代理。
- 在 Servlet 中,可用于权限检查、日志记录等增强功能。
- 优点是访问控制和解耦,缺点是复杂性和性能开销。
- 注意中文编码、线程安全和代理类型选择。
如果需要更复杂的示例(如动态代理+CGLIB、虚拟代理)、Servlet 集成细节,或与其他模式(如装饰器、适配器)的深入对比,请告诉我!