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设计模式系列文章。😊

相关推荐
CHEN5_02几秒前
【Java集合】List,Map,Set-详细讲解
java·windows·list
Seven97几秒前
剑指offer-25、复杂链表的复制
java
磊磊落落1 分钟前
如何使用 Spring Event 实现内部模块间的轻松解耦?
java
jokr_5 分钟前
C++ STL 专家容器:关联式、哈希与适配器
java·c++·哈希算法
猿java19 分钟前
为什么服务设计需要考虑限流?
java·面试·架构
JIngJaneIL22 分钟前
家庭事务管理系统|基于java和vue的家庭事务管理系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·家庭事务管理系统
qianmoq1 小时前
第06章:map():数据变形金刚,想变什么变什么
java
用户2707912938181 小时前
Java 的基本数据类型有哪些?它们对应的包装类是什么?
java
qq_396242981 小时前
【tomcat管理session,配置redis来管理session实现多个tomcat共享,原因是tomcat配置redis配置账号或者密码错误等】
java·redis·tomcat
用户1481819018491 小时前
设备拉黑删除
java