代理模式:控制对象访问的中间层设计

代理模式:控制对象访问的中间层设计

一、模式核心:通过代理对象控制对目标对象的访问

在软件开发中,有时需要为对象添加一个 "代理" 来控制对它的访问,例如:

  • 远程代理:访问远程对象时(如 RPC 调用),本地代理对象伪装成远程对象
  • 虚拟代理:延迟加载目标对象(如图片未加载时显示占位符)
  • 保护代理:控制对敏感对象的访问权限(如用户权限校验)

** 代理模式(Proxy Pattern)** 通过引入代理类,在不修改目标对象的前提下,为其添加额外的逻辑或控制访问,核心解决:

  • 访问控制:在目标对象访问前后添加预处理逻辑
  • 资源优化:延迟初始化昂贵的目标对象
  • 职责分离:将非业务逻辑(如日志、事务)从目标对象中剥离

核心思想与 UML 类图(PlantUML 语法)

代理模式包含目标接口(Subject)、目标对象(RealSubject)和代理对象(Proxy),代理对象与目标对象实现相同接口,客户端通过代理间接访问目标:

二、核心实现:虚拟代理实现图片延迟加载

1. 定义目标接口(图片)

java 复制代码
public interface Image {
    void display(); // 显示图片
}

2. 实现真实主题(实际图片对象,加载耗时)

java 复制代码
public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName); // 模拟加载耗时操作
    }

    private void loadFromDisk(String fileName) {
        System.out.println("加载图片:" + fileName);
        try {
            Thread.sleep(1000); // 模拟IO延迟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void display() {
        System.out.println("显示图片:" + fileName);
    }
}

3. 实现代理对象(延迟加载,先显示占位符)

java 复制代码
public class ImageProxy implements Image {
    private String fileName;
    private RealImage realImage; // 真实对象延迟初始化

    public ImageProxy(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if (realImage == null) {
            System.out.println("显示占位符..."); // 前置处理
            realImage = new RealImage(fileName); // 首次调用时加载真实对象
        }
        realImage.display(); // 调用真实对象的显示方法
        System.out.println("记录访问日志:" + fileName); // 后置处理
    }
}

4. 客户端通过代理访问目标对象

java 复制代码
public class ClientDemo {
    public static void main(String[] args) {
        // 使用代理对象,无需直接创建RealImage
        Image image = new ImageProxy("large_image.jpg");
        
        System.out.println("第一次显示图片:");
        image.display(); // 触发延迟加载
        
        System.out.println("\n第二次显示图片:");
        image.display(); // 直接使用缓存的RealImage
    }
}

输出结果

plaintext 复制代码
第一次显示图片:
显示占位符...
加载图片:large_image.jpg
显示图片:large_image.jpg
记录访问日志:large_image.jpg

第二次显示图片:
显示图片:large_image.jpg
记录访问日志:large_image.jpg

三、进阶:实现分布式代理与动态代理

1. 远程代理(模拟 RPC 调用)

java 复制代码
// 远程服务接口(目标接口)
public interface RemoteService {
    String process(String request);
}

// 本地代理对象(伪装成远程服务)
public class RemoteServiceProxy implements RemoteService {
    private String serverUrl;

    public RemoteServiceProxy(String serverUrl) {
        this.serverUrl = serverUrl;
    }

    @Override
    public String process(String request) {
        // 通过HTTP/Netty发送请求到远程服务器
        return sendRequestToServer(request);
    }

    private String sendRequestToServer(String request) {
        System.out.println("发送远程请求:" + request + " 到 " + serverUrl);
        // 实际实现需处理网络IO和序列化
        return "远程响应:" + request.toUpperCase();
    }
}

2. 动态代理(基于 Java 反射,无需手动编写代理类)

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 动态代理处理器
public class DynamicProxyHandler implements InvocationHandler {
    private Object target; // 目标对象

    public DynamicProxyHandler(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("动态代理后置处理:" + method.getName());
        return result;
    }
}

// 创建动态代理示例
public class DynamicProxyDemo {
    public static void main(String[] args) {
        RealImage realImage = new RealImage("small_image.jpg");
        
        // 生成动态代理对象
        Image proxy = (Image) Proxy.newProxyInstance(
            Image.class.getClassLoader(),
            new Class<?>[] { Image.class },
            new DynamicProxyHandler(realImage)
        );
        
        proxy.display(); // 通过代理调用目标方法
    }
}

3. 可视化代理流程

graph LR A[客户端请求] --> B[代理对象接收请求] B --> C[代理执行前置逻辑] C --> D[调用真实对象] D --> E[真实对象处理请求] E --> F[代理执行后置逻辑] F --> G[返回结果给客户端]

四、框架与源码中的代理模式实践

1. Spring AOP(动态代理实现切面编程)

  • 通过JdkDynamicAopProxyCglibAopProxy生成代理对象,织入切面逻辑
java 复制代码
// 代理对象调用目标方法时,自动执行@Before、@After等增强
@Service
public class UserService {
    @Transactional
    public void updateUser(User user) {
        // 业务逻辑
    }
}

2. MyBatis 映射器(Mapper Proxy)

  • MapperProxy作为代理对象,将接口方法映射为 SQL 语句执行
java 复制代码
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUser(int id);
}

// MyBatis创建MapperProxy代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUser(1); // 代理对象处理SQL执行

3. JavaScript 代理(Proxy 对象)

  • ES6 提供Proxy用于创建代理对象,拦截目标对象的操作
javascript 复制代码
const realObject = { name: "Alice" };
const proxy = new Proxy(realObject, {
    get(target, property) {
        console.log(`访问属性:${property}`);
        return target[property];
    }
});
console.log(proxy.name); // 输出:访问属性:name  Alice

五、避坑指南:正确使用代理模式的 3 个要点

1. 区分代理与装饰器模式

  • 代理模式:代理对象控制对目标对象的访问(目标对象可能不存在或需控制访问)
  • 装饰器模式:装饰对象增强目标对象的功能(目标对象必须存在且功能需扩展)

2. 处理代理对象的线程安全

  • 若代理对象持有目标对象的引用,需确保多线程环境下的线程安全
java 复制代码
public class ThreadSafeProxy implements Subject {
    private RealSubject realSubject = new RealSubject();
    private final Object lock = new Object();

    @Override
    public void request() {
        synchronized (lock) {
            realSubject.request();
        }
    }
}

3. 避免代理链过长

  • 多层代理可能导致性能损耗和调试困难,建议:
    • 合并同类代理(如日志代理与权限代理合并为一个复合代理)
    • 使用 AOP 替代多层手动代理

4. 反模式:滥用代理导致复杂度上升

  • 对于简单的功能增强,直接修改目标对象可能更高效,无需引入代理

六、总结:何时该用代理模式?

适用场景 核心特征 典型案例
远程对象访问 目标对象位于远程服务器,需通过网络调用 RPC 框架、微服务客户端
延迟加载与缓存 目标对象创建昂贵,需延迟初始化 图片 / 视频加载、大文件处理
权限控制与日志记录 需要在目标对象访问前后添加通用逻辑 权限系统、事务管理、性能监控
接口伪装与隔离 需要为复杂系统提供简单接口或伪装实现细节 模拟对象(测试场景)、遗留系统适配

代理模式通过 "中间层控制 + 解耦访问" 的设计,为对象访问添加了灵活的控制层,是构建可扩展、易维护系统的重要模式。下一篇我们将深入探讨策略模式,解析如何动态切换算法实现,敬请期待!

扩展思考:静态代理 vs 动态代理

类型 实现方式 灵活性 适用场景
静态代理 手动编写代理类(编译期确定) 低(代理类与目标类强绑定) 简单场景、无需频繁修改代理逻辑
动态代理 通过反射或字节码生成(运行时创建) 高(可动态适配多个目标类) 框架级代理(如 AOP、ORM)

理解这种差异,能帮助我们在不同场景下选择更合适的代理实现方式。

相关推荐
Cosolar10 分钟前
从零写一个 Attention Is All You Need
人工智能·面试·架构
摆烂大大王1 小时前
玩转 OpenClaw:用 TaskFlow + Heartbeat 打造自动化工作流
前端·人工智能·自动化
zhangxingchao1 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
梦想的颜色2 小时前
TypeScript 完全指南(上):从零开始掌握类型系统
前端·typescript
之歆2 小时前
Day01_ES6+ 专业指南:从基础到实战的现代JavaScript开发(下)
前端·javascript·es6
lichenyang4532 小时前
鸿蒙 MVVM 实战:从 Demo 到工程化,聊聊登录、状态管理与埋点系统设计
前端
IT_陈寒2 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
kyriewen3 小时前
AI生成代码快如闪电,但我修了三个小时——它到底帮了谁?
前端·javascript·ai编程
jiayong233 小时前
AI架构师面试题库 - 完整汇总文档
人工智能·面试·职场和发展
ayqy贾杰4 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理