代理模式

代理模式中文讲解

代理模式(Proxy Pattern)是一种结构型设计模式,属于 GoF(Gang of Four)提出的23种设计模式之一。它的核心目标是通过引入一个代理对象来控制对目标对象的访问,在不修改目标对象的情况下添加额外的功能(如权限检查、日志记录、延迟加载等)。代理模式就像一个“中介”,在客户端和目标对象之间充当桥梁。

以下用中文详细讲解代理模式的定义、结构、代码示例、应用场景、优缺点以及在 Servlet 环境中的使用,重点突出其原理和实际应用。


1. 什么是代理模式?

代理模式通过代理对象间接访问目标对象,允许在访问前后添加额外逻辑(如验证、缓存)。它解决了以下问题:

  • 问题:直接访问目标对象可能导致权限控制不足、性能问题或复杂性(如远程调用、延迟初始化)。
  • 解决方案:引入一个代理类,实现与目标对象相同的接口,控制访问并添加额外功能。

关键特点

  • 访问控制:代理控制对目标对象的访问,添加权限、日志等。
  • 透明性:客户端通过代理接口操作,无需知道代理的存在。
  • 功能增强:在不修改目标对象的情况下扩展功能。

代理模式类型

  1. 静态代理:代理类在编译时确定,代码中明确定义。
  2. 动态代理:运行时动态生成代理对象(如 Java 的 java.lang.reflect.Proxy)。
  3. 常见变体
  • 保护代理:控制访问权限。
  • 虚拟代理:延迟加载目标对象。
  • 远程代理:管理远程对象访问。
  • 智能代理:添加额外逻辑(如缓存、日志)。

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. 代理模式的优缺点

优点

  1. 访问控制:有效控制对目标对象的访问(如权限、延迟加载)。
  2. 功能增强:添加日志、缓存等功能,无需修改目标类。
  3. 解耦:客户端与目标对象解耦,通过代理交互。
  4. 符合开闭原则:通过代理扩展功能,原类保持不变。

缺点

  1. 复杂性:引入代理类,增加代码量和复杂度。
  2. 性能开销:代理层可能引入额外调用开销。
  3. 动态代理限制:仅适用于接口(Java 动态代理),CGLIB 可代理类但更复杂。

6. 注意事项

  1. 静态 vs 动态代理
  • 静态代理适合简单场景,代码明确但不灵活。
  • 动态代理适合复杂场景,运行时生成代理,但需熟悉反射。
  1. 中文编码问题
  • 如果涉及中文(如错误消息),确保 Servlet 使用 UTF-8:
    java resp.setContentType("text/html;charset=UTF-8");
  • 对中文参数编码(如 URLEncoder.encode("无权限", "UTF-8"))。
  1. 线程安全
  • 代理对象在 Servlet 环境中需考虑多线程访问。
  • 真实主题(如 RealRequestProcessor)应线程安全或无状态。
  1. 性能优化
  • 缓存代理实例,减少动态代理创建开销。
  • 避免在代理中执行复杂逻辑。
  1. 与其他模式结合
  • 装饰器模式:代理控制访问,装饰器增强功能。
  • 适配器模式:代理保持接口一致,适配器转换接口。
  • 工厂模式:动态创建代理对象。
  1. 常见问题解决
  • 代理失效:检查代理是否正确实现接口或持有目标对象。
  • 性能瓶颈:优化代理逻辑,减少反射调用。
  • 异常处理:代理需捕获并处理目标对象的异常。

7. 与其他模式的区别

特性代理模式装饰器模式适配器模式
目的控制对象访问动态增强功能转换接口兼容
实现方式组合或继承组合组合或继承
关注点访问控制功能扩展接口兼容
接口一致性与原对象相同与原对象相同转换到新接口

8. 总结

  • 代理模式通过代理对象控制目标对象访问,适合权限控制、延迟加载等场景。
  • 核心是抽象主题、真实主题和代理,支持静态和动态代理。
  • 在 Servlet 中,可用于权限检查、日志记录等增强功能。
  • 优点是访问控制和解耦,缺点是复杂性和性能开销。
  • 注意中文编码、线程安全和代理类型选择。

如果需要更复杂的示例(如动态代理+CGLIB、虚拟代理)、Servlet 集成细节,或与其他模式(如装饰器、适配器)的深入对比,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注