Java设计模式-代理模式

Java设计模式-代理模式

模式概述

代理模式简介

核心思想:通过定义一个代理对象(Proxy),控制对真实对象(RealSubject)的访问。代理对象在真实对象的方法执行前后添加额外逻辑(如权限校验、日志记录、延迟加载等),从而在不修改真实对象的前提下扩展其功能,实现客户端与真实对象的解耦。

模式类型:结构型设计模式(关注对象的组成与交互,通过代理控制访问)。

作用

  • 控制访问:限制真实对象的直接访问(如保护代理仅允许授权用户调用)。
  • 功能增强:在真实对象方法前后添加通用逻辑(如日志记录、性能监控)。
  • 解耦客户端与真实对象:客户端仅依赖代理接口,无需感知真实对象的存在或修改。
  • 延迟加载:虚拟代理可推迟真实对象的创建(如图片懒加载,仅用户查看时加载)。

典型应用场景

  • 远程代理(RPC调用):客户端通过代理对象调用远程服务(如Dubbo的服务消费者代理)。
  • 虚拟代理(Lazy Loading):图片/文件懒加载(如网页中图片未滚动到可视区域不加载)。
  • 保护代理(Permission Control):接口权限校验(如Spring Security的访问控制代理)。
  • 缓存代理(Cache):数据库查询缓存(如MyBatis的一级/二级缓存代理)。
  • 智能引用(Smart Reference):对象访问统计(如记录方法调用次数)。

我认为:代理模式是"对象的代言人",在不改变真实对象的前提下,为其添加"保护壳"或"增强功能",让客户端更安全、便捷地使用对象。

课程目标

  • 理解代理模式的核心思想和经典应用场景
  • 识别应用场景,使用代理模式解决功能要求
  • 了解代理模式的优缺点

核心组件

角色-职责表

角色 职责 示例类名
主题接口(Subject) 定义真实主题与代理的共同接口,声明需要实现的方法 UserService
真实主题(RealSubject) 实现主题接口,包含具体业务逻辑(被代理的真实对象) RealUserService
代理(Proxy) 实现主题接口,持有真实主题的引用,在方法调用前后添加额外逻辑(如校验、日志) UserServiceProxy

类图

下面是一个简化的类图表示,展示了代理模式中的主要角色及其交互方式:
实现 实现 引用 使用 <<interface>> UserService +queryUserInfo(userId: String) RealUserService +queryUserInfo(userId: String) UserServiceProxy -realService: UserService -currentUserRole: String +UserServiceProxy(realService: UserService, currentUserRole: String) +queryUserInfo(userId: String) Client +main(args: String[])


传统实现 VS 代理模式

案例需求

案例背景 :实现一个用户信息服务(UserService),提供查询用户信息的方法(queryUserInfo)。要求:仅允许管理员(role=admin)调用该方法,且调用时记录操作日志。

传统实现(痛点版)

代码实现

java 复制代码
// 真实主题:具体实现用户信息查询
class RealUserService{
    @Override
    public String queryUserInfo(String userId) {
        // 模拟数据库查询
        return "用户信息:ID=" + userId + ", 姓名=张三, 年龄=25";
    }
}

// 传统实现:客户端直接调用真实对象(无权限校验和日志)
public class Client {
    public static void main(String[] args) {
        UserService realService = new RealUserService();
        String userId = "1001";

        // 问题1:需在客户端手动添加权限校验(代码冗余)
        if (!"admin".equals(getCurrentUserRole())) {  // 假设获取当前用户角色
            throw new SecurityException("无权限查询用户信息");
        }

        // 调用真实对象方法
        String userInfo = realService.queryUserInfo(userId);

        // 问题2:需在客户端手动添加日志记录(重复代码)
        System.out.println("日志:用户" + getCurrentUserId() + "查询了用户" + userId + "的信息");
    }

    // 模拟当前用户信息(假设)
    private static String getCurrentUserRole() {
        return "guest";  // 测试时改为"admin"可绕过校验
    }

    private static String getCurrentUserId() {
        return "admin_123";
    }
}

痛点总结

  • 代码冗余:每个客户端调用真实对象时都需重复编写权限校验、日志记录等逻辑。
  • 可维护性差:若需修改校验规则(如新增角色)或日志格式,需同步修改所有客户端代码。
  • 真实对象暴露 :客户端直接依赖真实对象(RealUserService),可能意外修改其状态或绕过校验逻辑。

代理模式 实现(优雅版)

代码实现

java 复制代码
// 1. 主题接口:定义用户信息服务
interface UserService {
    String queryUserInfo(String userId);
}

// 2. 真实主题:具体实现用户信息查询(不修改)
class RealUserService implements UserService {
    @Override
    public String queryUserInfo(String userId) {
        // 模拟数据库查询
        return "用户信息:ID=" + userId + ", 姓名=张三, 年龄=25";
    }
}

// 3. 代理类:控制访问并增强功能(权限校验+日志记录)
class UserServiceProxy implements UserService {
    private final UserService realService;  // 持有真实主题引用
    private final String currentUserRole;   // 当前用户角色(可通过构造注入)

    public UserServiceProxy(UserService realService, String currentUserRole) {
        this.realService = realService;
        this.currentUserRole = currentUserRole;
    }

    @Override
    public String queryUserInfo(String userId) {
        // 前置逻辑:权限校验
        if (!"admin".equals(currentUserRole)) {
            throw new SecurityException("无权限查询用户信息");
        }

        // 调用真实对象方法
        String result = realService.queryUserInfo(userId);

        // 后置逻辑:日志记录
        System.out.println("日志:用户" + getCurrentUserId() + "查询了用户" + userId + "的信息");

        return result;
    }

    // 模拟当前用户ID(假设)
    private String getCurrentUserId() {
        return "admin_123";
    }
}

// 4. 客户端使用:仅依赖代理类
public class Client {
    public static void main(String[] args) {
        UserService realService = new RealUserService();
        UserService proxy = new UserServiceProxy(realService, "admin");  // 注入当前用户角色

        try {
            String userInfo = proxy.queryUserInfo("1001");  // 客户端仅调用代理方法
            System.out.println(userInfo);
        } catch (SecurityException e) {
            System.out.println("错误:" + e.getMessage());
        }
    }
}

优势

  • 代码复用:权限校验、日志记录逻辑集中在代理类中,新增客户端无需重复编写。
  • 可维护性高 :修改校验规则(如支持super_admin角色)或日志格式时,仅需调整代理类。
  • 访问控制:客户端仅能通过代理访问真实对象,避免直接修改真实对象状态或绕过校验。
  • 解耦客户端与真实对象 :客户端依赖接口(UserService),而非具体实现(RealUserService),符合依赖倒置原则。

局限

  • 类数量增加 :每个需要代理的主题需定义对应的代理类(如UserServiceProxy),可能增加系统类膨胀(动态代理可缓解此问题)。
  • 代理逻辑侵入:若代理需要添加大量额外逻辑(如多个校验步骤),可能导致代理类代码冗余(可通过组合模式或AOP优化)。
  • 性能开销:代理方法调用需经过额外逻辑(如校验、日志),可能轻微影响性能(对性能敏感场景需权衡)。

模式变体

  • 静态代理:代理类在编译期手动编写(如上述案例),灵活性低但性能高(无反射开销)。
  • 动态代理:运行时通过反射生成代理类(如JDK动态代理、CGLIB),支持为多个接口动态生成代理,减少代码冗余(适用于Spring AOP)。
  • 远程代理(Remote Proxy) :封装远程服务调用(如RPC框架中的StubSkeleton),隐藏网络通信细节(如Dubbo的服务消费者代理)。
  • 虚拟代理(Virtual Proxy):延迟创建真实对象(如图片懒加载,仅用户查看时加载真实图片资源)。
  • 保护代理(Protection Proxy) :控制对真实对象的访问权限(如Spring Security的MethodSecurityInterceptor)。
  • 缓存代理(Cache Proxy):缓存真实对象的方法结果(如MyBatis的二级缓存代理)。

最佳实践

建议 理由
代理类实现主题接口 确保代理与真实对象行为一致,客户端无感知(符合里氏替换原则)。
代理职责单一 仅处理访问控制或功能增强,不添加与核心逻辑无关的业务规则(避免代理类膨胀)。
使用动态代理减少冗余 对于需要为多个接口生成代理的场景(如Spring AOP),动态代理可避免手动编写多个静态代理类。
明确代理的创建方式 通过工厂类或依赖注入(如Spring的@Bean)创建代理对象,提高测试灵活性(支持Mock真实对象)。
控制代理的粒度 避免为单个方法生成代理(如仅对queryUserInfo添加日志),保持代理的通用性。
文档说明代理行为 在代理类注释中明确说明其添加的额外逻辑(如"此代理会校验管理员权限并记录日志"),便于维护。

一句话总结

代理模式通过引入代理对象控制对真实对象的访问,在不修改真实对象的前提下扩展其功能,是实现访问控制、功能增强和系统解耦的经典工具。

如果关注Java设计模式内容,可以查阅作者的其他Java设计模式系列文章。😊

相关推荐
考虑考虑3 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261353 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊4 小时前
Java学习第22天 - 云原生与容器化
java
渣哥6 小时前
原来 Java 里线程安全集合有这么多种
java
间彧6 小时前
Spring Boot集成Spring Security完整指南
java
间彧7 小时前
Spring Secutiy基本原理及工作流程
java
数据智能老司机7 小时前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
Java水解8 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
数据智能老司机8 小时前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
洛小豆10 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试