当前位置: 首页 > news >正文

代理设计模式:从底层原理到源代码 详解

        代理设计模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对目标对象的访问。代理对象充当客户端和目标对象之间的中介,允许在不修改目标对象的情况下添加额外的功能(如权限控制、日志记录、延迟加载等)。以下将从底层原理到源代码层面,逐步为非专业人士详细解释代理设计模式的每个方面。


一、代理设计模式的通俗概念

1. 什么是代理?

        想象你在网上购物,想买一件衣服,但你没有时间直接去实体店。于是,你请了一个朋友(代理)帮你去店里挑衣服、付款并带回来。这个朋友就是“代理”,他代替你完成了与商店的交互。你(客户端)只需要告诉代理你的需求(比如颜色、尺码),代理会帮你处理一切细节。

 在程序设计中,代理模式也是类似的:

         - 客户端:发出请求的一方(比如你)。

         - 代理对象:代替客户端与目标对象交互的对象(比如你的朋友)。

         - 目标对象:真正完成工作的对象(比如商店里的衣服)。

        代理对象可以在客户端和目标对象之间添加额外的逻辑,比如检查你是否有足够的钱(权限控制)、记录你买了什么(日志记录),或者延迟去商店直到你确认要买(延迟加载)。

2. 为什么需要代理?

        代理模式解决的问题是:在不直接修改目标对象的情况下,控制访问或增强功能

        常见场景包括:

                 - 权限控制:只有特定用户可以访问目标对象。

                 - 延迟加载:只有在需要时才加载目标对象(比如加载大文件)。

                 - 日志记录:记录目标对象被调用的时间和参数。

                 - 远程访问:代理隐藏了目标对象在远程服务器上的细节。

3. 代理模式的本质

        代理模式的核心是封装和控制。代理对象封装了对目标对象的访问,客户端通过代理间接调用目标对象的方法。代理可以在调用前后添加额外的逻辑,但客户端无需关心这些细节。


二、代理设计模式的底层原理

代理模式的实现基于面向对象编程的以下关键概念:

 1. 接口/抽象类:代理对象和目标对象通常实现同一个接口或继承同一个抽象类,确保它们有相同的方法签名,客户端可以无缝切换。

 2. 组合/委托:代理对象通常持有一个目标对象的引用,通过委托(调用目标对象的方法)完成实际工作。

 3. 拦截和增强:代理对象在调用目标对象的方法前后插入额外的逻辑,控制访问或增强功能。

工作流程(以买衣服为例)

        你(客户端)告诉代理:“我要买一件红色的衣服。”

        代理检查你的请求(比如确认你有足够的钱)。

        代理将请求转发给商店(目标对象),商店提供衣服。

        代理可能记录日志(“你买了一件红色衣服”)。

        代理将衣服返回给你。

        在代码中,这个流程表现为:

                 - 客户端调用代理对象的方法。

                 - 代理对象执行前置逻辑(如检查权限)。

                 - 代理对象调用目标对象的方法。

                 - 代理对象执行后置逻辑(如记录日志)。

                 - 代理对象返回结果给客户端。


三、代理模式的类型

代理模式根据用途分为几种常见类型,理解这些类型有助于选择合适的实现方式:

 1. 虚拟代理(Virtual Proxy):延迟加载目标对象,适合目标对象创建成本高的情况(如加载大图片)。

 2. 保护代理(Protection Proxy):控制对目标对象的访问,通常用于权限管理。

 3. 远程代理(Remote Proxy):隐藏目标对象位于远程服务器的细节,客户端感觉像在本地调用。

 4. 智能代理(Smart Proxy):在调用目标对象时添加额外功能,如日志、计数等。

本文将以保护代理为例,详细讲解其实现,因为它简单且能清晰展示代理模式的原理。


四、代理模式的详细实现

        以下通过一个具体的例子,用 Java 语言从头实现一个保护代理,逐步解释每部分代码的原理。假设我们有一个文件访问系统,只有管理员可以删除文件,普通用户只能读取文件。

静态代理

1. 定义接口(统一代理和目标对象的契约)

        我们需要一个接口,定义文件操作的行为。代理和目标对象都实现这个接口,确保客户端可以用一致的方式调用它们。

 public interface FileAccess {void readFile(String fileName);void deleteFile(String fileName);
}

解释

 - FileAccess 接口定义了两个方法:readFile(读取文件)和deleteFile(删除文件)。

 - 代理和目标对象都实现这个接口,客户端通过接口调用方法,无需关心背后是代理还是目标对象。

 - 这就像你告诉代理“我要买衣服”,代理和商店都理解“买衣服”这个指令。

2. 实现目标对象(实际干活的类)

目标对象是真正执行文件操作的类,比如实际访问文件系统。

public class RealFileAccess implements FileAccess {@Overridepublic void readFile(String fileName) { System.out.println("读取文件: " + fileName); }@Overridepublic void deleteFile(String fileName) {System.out.println("删除文件: " + fileName);}}

解释

 - RealFileAccess 是目标对象,实现了 FileAccess 接口。

 - readFile 和 deleteFile 方法模拟文件操作,实际中可能涉及文件系统调用。

 - 这个类就像商店,负责实际提供衣服(执行核心逻辑)。

3. 实现代理对象(控制访问)

代理对象也实现 FileAccess 接口,但它会检查权限,并在调用目标对象之前添加控制逻辑。

public class FileAccessProxy implements FileAccess { private RealFileAccess realFileAccess; private String userRole;public FileAccessProxy(String userRole) {this.userRole = userRole;this.realFileAccess = new RealFileAccess(); // 持有目标对象引用}@Overridepublic void readFile(String fileName) {System.out.println("Proxy: 记录读请求 " + fileName);realFileAccess.readFile(fileName); // 委托给目标对象}@Overridepublic void deleteFile(String fileName) {if (userRole.equals("admin")) {System.out.println("Proxy: 有权删除文件: " + fileName);realFileAccess.deleteFile(fileName); // 委托给目标对象} else {System.out.println("Proxy: 权限不足,只有管理员才能删除文件.");}}}

解释

 - 构造函数:FileAccessProxy 接受 userRole(用户角色,如 “admin” 或 “user”),并创建目标对象 RealFileAccess。

- 持目标对象引用:代理通过 realFileAccess 字段持有目标对象的引用,用于委托调用。

 - readFile 方法:代理直接调用目标对象的 readFile,并添加日志记录(前置逻辑)。

 - deleteFile 方法:代理检查用户角色,只有管理员(userRole 为 “admin”)可以删除文件,否则拒绝访问。

 - 这就像你的朋友(代理)在去商店前检查你是否有钱(权限),然后才帮你买衣服。

4. 客户端代码(使用代理)

客户端通过代理对象访问文件系统,无需直接接触目标对象。

public class Main {public static void main(String[] args) {// 普通用户FileAccess userAccess = new FileAccessProxy(“user”);userAccess.readFile(“data.txt”);userAccess.deleteFile(“data.txt”);System.out.println("---");// 管理员FileAccess adminAccess = new FileAccessProxy("admin");adminAccess.readFile("data.txt");adminAccess.deleteFile("data.txt");}
}

输出

Proxy: 记录读请求文件 data.txt
读取文件: data.txt
Proxy: 权限不足,只有管理员才能删除文件.
---
Proxy: 记录读请求的文件:data.txt
读取文件: data.txt
Proxy: 有权删除文件: data.txt
删除文件: data.txt

解释

 - 客户端创建两个代理对象:一个普通用户(user),一个管理员(admin)。

 - 普通用户可以读取文件,但删除文件时被拒绝。

 - 管理员可以读取和删除文件。

 - 客户端只与代理交互(FileAccess 接口),无需知道目标对象或权限检查的细节。

动态代理(Dynamic Proxy)

        动态代理是一种在运行时动态生成代理对象的技术,主要用于在不修改原始类代码的情况下,增强或控制目标对象的行为。其核心思想是通过 反射 和 接口 在运行时生成代理类,实现对目标方法的拦截和增强。

下面以租房为例:

现有租房的接口Rent

房东类Host 实现Rent接口

代理类RentHandler实现InvocationHandler接口

1. 动态代理的核心组件

组件作用关键类/接口
抽象接口定义代理类和真实类共同的行为Rent(租房接口)
真实对象实际执行业务逻辑的类Host(房东类)
调用处理器拦截方法调用并增强逻辑InvocationHandler
动态代理类运行时生成的代理对象Proxy.newProxyInstance()

2. 动态代理的设计步骤

(1) 定义抽象接口

代理类和真实类必须实现相同的接口,确保方法调用的兼容性。

public interface Rent {void rent();int getPrice();
}
(2) 实现真实对象(被代理类)
public class Host implements Rent {@Overridepublic void rent() {System.out.println("房东出租房子");}@Overridepublic int getPrice() {return 5000;}
}
(3) 实现调用处理器(InvocationHandler

负责拦截方法调用,并插入增强逻辑(如日志、权限检查、事务管理等)。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class RentHandler implements InvocationHandler {private final Object target; // 被代理的真实对象(如 Host)public RentHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强(如权限检查)System.out.println("[代理] 正在检查租客信用...");// 调用真实对象的方法Object result = method.invoke(target, args);
​​​​​​​// 后置增强(如日志记录)System.out.println("[代理] 租房完成,签订合同");// 可修改返回值(如砍价)if ("getPrice".equals(method.getName())) {return (int) result - 500; // 代理砍价 500 元}return result;}
}
(4) 动态生成代理对象

使用 Proxy.newProxyInstance() 在运行时生成代理类:

import java.lang.reflect.Proxy;public class Client {public static void main(String[] args) {// 1. 创建真实对象Rent host = new Host();// 2. 创建调用处理器,并关联真实对象RentHandler handler = new RentHandler(host);// 3. 动态生成代理对象Rent proxy = (Rent) Proxy.newProxyInstance(Rent.class.getClassLoader(), // 使用接口的类加载器new Class[]{Rent.class},    // 代理类实现的接口handler                     // 方法调用的处理器);// 4. 通过代理对象调用方法proxy.rent();          // 会触发 RentHandler.invoke()int price = proxy.getPrice(); // 代理修改返回值System.out.println("最终价格:" + price);}
}

输出结果:

[代理] 正在检查租客信用...
房东出租房子
[代理] 租房完成,签订合同
[代理] 正在检查租客信用...
最终价格:4500

3. 动态代理的适用场景

  1. AOP(面向切面编程)

    日志记录、性能统计、事务管理。
  2. RPC(远程方法调用)

    动态代理隐藏网络通信细节(如 Dubbo、gRPC)。
  3. 权限控制

    在方法调用前检查用户权限。
  4. 缓存代理

    缓存方法返回值,避免重复计算。

4. 动态代理的优缺点

✅ 优点

  • 无侵入性:无需修改原有代码,直接增强功能。

  • 灵活扩展:一个 InvocationHandler 可代理多个接口。

  • 符合开闭原则:新增功能不影响原有逻辑。

❌ 缺点

  • 基于接口:只能代理接口,不能代理类(需用 CGLIB 弥补)。

  • 性能开销:反射调用比直接调用稍慢(但现代 JVM 已优化)。

动态代理 vs. 静态代理

特性动态代理静态代理
代理类生成时机运行时动态生成编译时手动编写
代码量少(通用性强)多(每个代理类需单独实现)
灵活性高(可代理任意接口)低(需为每个类编写代理)
性能稍慢(反射调用)快(直接调用)


五、代理模式的详细原理拆解

1. 接口的作用

  • 接口(如 FileAccess)确保代理和目标对象有相同的方法签名,客户端可以用统一的方式调用。
  • 这实现了开闭原则:可以替换不同的代理或目标对象,而不修改客户端代码。
  • 类似于你在网上购物时,无论是通过朋友(代理)还是直接去商店,购买流程(接口)是一致的。

2. 代理的控制逻辑

  • 代理通过持有的目标对象引用(realFileAccess)将请求委托给目标对象。
  • 代理可以在调用前后添加逻辑:
    • 前置逻辑:如权限检查、日志记录。
    • 后置逻辑:如清理资源、返回结果处理。
  • 在例子中,deleteFile 的权限检查是前置逻辑,日志记录是前置和后置逻辑的结合。

3. 客户端的透明性

  • 客户端通过接口(FileAccess)调用方法,无需知道背后是代理还是目标对象。
  • 这实现了封装:客户端只关心结果,不关心权限检查或日志记录的实现细节。

4. 延迟加载(虚拟代理的扩展)

虽然本例是保护代理,但可以扩展为虚拟代理。例如,realFileAccess 可以在第一次调用时才创建:

if (realFileAccess == null) {realFileAccess = new RealFileAccess(); // 延迟初始化
}

这就像你的朋友等到你确认要买衣服时才去商店,节省时间和资源。


六、代理模式的优点和缺点

优点

控制访问:代理可以限制对目标对象的访问(如权限检查)。

功能增强:可以在不修改目标对象的情况下添加日志、缓存等功能。

解耦:客户端与目标对象隔离,降低耦合度。

灵活性:可以动态切换代理逻辑(如根据用户角色选择不同代理)。

缺点

复杂性增加:引入代理对象使系统结构更复杂。

性能开销:代理的额外逻辑可能增加调用时间。

维护成本:需要维护代理和目标对象的同步(方法签名一致)。


七、实际应用场景

代理模式在现实开发中非常常见:

 1. Spring AOP:Spring 框架使用动态代理实现切面编程(如日志、事务管理)。

 2. 数据库连接池:代理控制数据库连接的分配和回收。

 3. Web 框架:代理处理 HTTP 请求的认证、路由等。

 4. 图片延迟加载:网页中图片只有在滚动到可视区域时才加载(虚拟代理)。


八、总结

        代理设计模式通过引入一个代理对象,控制对目标对象的访问,并在不修改目标对象的情况下添加额外功能。其核心是接口统一、委托调用、逻辑增强

通过保护代理的例子,我们看到:

 - 接口定义了代理和目标对象的契约。

 - 代理对象通过持有目标对象引用,拦截和增强客户端请求。

 - 客户端通过接口透明调用,无需关心代理的内部逻辑。

相关文章:

  • 物理机检查磁盘坏道方式
  • prtobuf的原理
  • 【Luogu】动态规划一
  • TS-300B浊度传感器详解(STM32)
  • STM32单片机入门学习——第46节: [14-1] WDG看门狗
  • Redis在.NET平台中的各种应用场景
  • AI日报 - 2025年4月23日
  • 代理模式(Proxy Pattern)详解:以延迟加载图片为例
  • NLP高频面试题(五十)——大模型(LLMs)分词(Tokenizer)详解
  • 【C++】Json-Rpc框架项目介绍(1)
  • Agent框架LangGraph:实现一个简单的Plan-and-Execute Agent
  • 电子电器架构 --- 面向下一代车辆的演进式(发展演变的)汽车网关
  • 仅追加KV数据库
  • 实验一 进程控制实验
  • 2023蓝帽杯初赛内存取证-4
  • NVIDIA 自动驾驶技术见解
  • 从零到多智能体:Google Agent开发套件(ADK)入门指南
  • C语言教程(十三):C 语言中 enum(枚举)的详细介绍
  • 武装Burp Suite工具:RouteVulScan插件_被动扫描发现漏洞.
  • shared_ptr八股收集 C++
  • 深一度|王励勤二次创业从未停步,带领中国乒乓直面挑战
  • “电化长江”的宜昌成果:船舶航运停靠都能用电,助力一江清水向东流
  • 具身智能资本盛宴:3个月37笔融资,北上深争锋BAT下场,人形机器人最火
  • 秦洪看盘|热点凌乱难抑多头雄心
  • 我们的免疫系统,是世界上最好的“医生”
  • 江西九江市人大常委会原副主任戴晓慧主动交代问题,接受审查调查