掌握设计模式--代理模式

news/2025/1/13 9:12:41/文章来源:https://www.cnblogs.com/dennyLee2025/p/18658876

代理模式(Proxy Pattern)

代理模式(Proxy Pattern)是一种结构型设计模式,允许你通过代理对象来控制对其他对象的访问。代理模式的主要目的是通过代理对象来控制原对象的访问、延迟加载、权限控制等。

组成结构

  • Subject(主题接口):定义了真实对象和代理对象的共同接口。
  • RealSubject(真实主题类):定义了代理类所代表的真实对象,通常实现了Subject接口。
  • Proxy(代理类):持有RealSubject的引用,并在请求传递给RealSubject之前或之后进行一些操作。

代理模式的分类

  1. 静态代理:代理类在编译时就已经确定。代理类和被代理类通常是手动创建的,代码需要在编译时就确定好。

  2. 动态代理:代理类在运行时动态生成,通常借助反射机制来生成代理对象。这种方式更灵活,可以通过代理类来代理多个不同的接口或类。

  3. 虚拟代理:通过代理类控制对某个对象的访问,常用于懒加载,只有在实际需要时才初始化对象,减少资源消耗。

  4. 保护代理:控制对对象的访问权限,通常用于权限控制等。

  5. 远程代理:用于对象在不同地址空间的访问,例如,分布式系统中,客户端通过代理访问远程对象。

由于篇幅原因,远程代理会放在另一篇文章单独举例。

根据代理模式的分类,以下是各类型代理的具体案例和应用:

1. 静态代理(Static Proxy)

静态代理是在编译时确定的,代理类和真实类都需要在编写时定义。代理类通常会实现与真实类相同的接口,并在代理类中调用真实类的方法。一个具体类对应一个代理类,通过代理类来操作具体类。

案例:静态代理的日志记录

假设我们有一个计算服务,通过代理类来记录方法执行的日志。

// Subject接口
public interface CalculationService {int add(int a, int b);int subtract(int a, int b);
}// 真实类
public class CalculationServiceImpl implements CalculationService {@Overridepublic int add(int a, int b) {return a + b;}@Overridepublic int subtract(int a, int b) {return a - b;}
}// 代理类
public class CalculationServiceProxy implements CalculationService {private CalculationServiceImpl realService;public CalculationServiceProxy() {realService = new CalculationServiceImpl();}@Overridepublic int add(int a, int b) {System.out.println("日志: add ...");return realService.add(a, b);}@Overridepublic int subtract(int a, int b) {System.out.println("日志: subtract ...");return realService.subtract(a, b);}
}// 客户端
public class Client {public static void main(String[] args) {CalculationService service = new CalculationServiceProxy();System.out.println("执行结果: " + service.add(10, 5));System.out.println("执行结果: " + service.subtract(10, 5));}
}

执行结果

日志: add ...

执行结果: 15

日志: subtract ...

执行结果: 5

2. 动态代理(Dynamic Proxy)

动态代理是在运行时动态生成的,可以使用 JDK 动态代理(Proxy.newProxyInstance())或CGLib动态代理创建一个代理对象,并在其中增强目标对象的功能。多个具体类使用一个代理类来创建代理对象。

案例:动态代理的日志记录

通过java.lang.reflect.ProxyInvocationHandler来动态创建代理类,实现类似静态代理的功能。动态代理不需要像静态代理那样在每个业务代码方法都加入日志代码,而是统一的日志记录,并且与业务代码解耦,起到低耦合的作用,日志代码(代理类)的调整不会影响到业务代码。

案例类图

image

使用JDK动态代理实现

import java.lang.reflect.*;public interface CalculationService {int add(int a, int b);int subtract(int a, int b);
}public class CalculationServiceImpl implements CalculationService {@Overridepublic int add(int a, int b) {return a + b;}@Overridepublic int subtract(int a, int b) {return a - b;}
}public class LoggingInvocationHandler implements InvocationHandler {private Class<?> targetClass;  // 目标类的 Class 对象public LoggingInvocationHandler(Class<?> targetClass) {this.targetClass = targetClass;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在方法调用前打印日志System.out.println("日志: 执行方法 " + method.getName() + " 参数为 " + args[0] + " 和 " + args[1]);// 使用反射创建目标对象实例Object target = targetClass.getDeclaredConstructor().newInstance();// 使用反射调用目标对象的方法return method.invoke(target, args);  // 调用目标类的方法并传递参数}
}public class Client {public static void main(String[] args) {try {// 使用反射创建代理对象Class<?> targetClass = CalculationServiceImpl.class;  // 获取目标类的 Class 对象// 创建动态代理CalculationService proxy = (CalculationService) Proxy.newProxyInstance(targetClass.getClassLoader(),  // 类加载器targetClass.getInterfaces(),  // 接口列表new LoggingInvocationHandler(targetClass)  // 传入目标类的 Class 对象);// 通过代理对象调用方法System.out.println("执行结果: " + proxy.add(10, 5));System.out.println("执行结果: " + proxy.subtract(10, 5));} catch (Exception e) {e.printStackTrace();}}
}

执行结果

日志: 执行方法 add 参数为 10 和 5

执行结果: 15

日志: 执行方法 subtract 参数为 10 和 5

执行结果: 5

使用CGLib动态代理实现

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

创建 CGLIB 代理类

public class CGLibProxyExample {// 使用 CGLIB 动态代理public static void main(String[] args) {// 创建目标对象CalculationServiceImpl realService = new CalculationServiceImpl();// 创建 CGLIB 代理对象CalculationServiceImpl proxyService = (CalculationServiceImpl) Enhancer.create(CalculationServiceImpl.class,  // 目标类new MethodInterceptor() {      // 方法拦截器@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 方法调用前打印日志System.out.println("日志: 执行方法 " + method.getName() + " 参数为 " + args[0] + " 和 " + args[1]);// 调用目标方法return proxy.invokeSuper(obj, args);  // 调用父类(目标类)的方法}});// 通过代理对象调用方法System.out.println("执行结果: " + proxyService.add(10, 5));System.out.println("执行结果: " + proxyService.subtract(10, 5));}
}

3. 虚拟代理(Virtual Proxy)

虚拟代理通常用于对象的懒加载(Lazy Initialization)。当某个对象的创建非常昂贵(如需要加载大量数据或资源),而并非每次都需要使用时,虚拟代理可以推迟对象的创建,直到需要时才创建

案例:虚拟代理的懒加载

模拟一个大型图片的加载,只有在需要显示图片时,才加载图片数据。

// Subject接口
public interface Image {void display();
}// 真实对象
public class RealImage implements Image {private String fileName;public RealImage(String fileName) {this.fileName = fileName;loadFromDisk();}private void loadFromDisk() {System.out.println("Loading image: " + fileName);}@Overridepublic void display() {System.out.println("Displaying image: " + fileName);}
}// 代理类
public class ProxyImage implements Image {private RealImage realImage;private String fileName;public ProxyImage(String fileName) {this.fileName = fileName;}@Overridepublic void display() {if (realImage == null) {realImage = new RealImage(fileName);}realImage.display();}
}// 客户端
public class Client {public static void main(String[] args) {Image image = new ProxyImage("test.jpg");image.display();  // 第一次调用时加载图片image.display();  // 第二次调用时直接显示}
}

执行结果

Loading image: test.jpg

Displaying image: test.jpg

Displaying image: test.jpg

4. 保护代理(Protective Proxy)

保护代理控制访问权限,通常用于安全控制。例如,代理对象可以检查用户权限,只有在满足条件时才允许访问真实对象。

案例:保护代理

假设我们有一个银行账户服务,保护代理会在执行操作之前验证用户是否有足够的权限。

// 账户服务接口
public interface BankAccountService {void withdraw(int amount);void deposit(int amount);
}// 真实对象
public class BankAccountServiceImpl implements BankAccountService {private int balance = 1000;@Overridepublic void withdraw(int amount) {if (balance >= amount) {balance -= amount;System.out.println("转出: " + amount + ", 剩余余额: " + balance);} else {System.out.println("资金不足!");}}@Overridepublic void deposit(int amount) {balance += amount;System.out.println("转入: " + amount + ", 剩余余额: " + balance);}
}// 保护代理
public class BankAccountServiceProxy implements BankAccountService {private BankAccountServiceImpl realService;private String userRole;public BankAccountServiceProxy(String userRole) {realService = new BankAccountServiceImpl();this.userRole = userRole;}@Overridepublic void withdraw(int amount) {if ("admin".equals(userRole)) {realService.withdraw(amount);} else {System.out.println("访问被拒绝:权限不足!");}}@Overridepublic void deposit(int amount) {realService.deposit(amount);}
}// 客户端
public class Client {public static void main(String[] args) {BankAccountService userService = new BankAccountServiceProxy("user");userService.withdraw(200);  // Should be deniedBankAccountService adminService = new BankAccountServiceProxy("admin");adminService.withdraw(200);  // Should be allowed}
}

执行结果

访问被拒绝:权限不足!

转出: 200, 剩余余额: 800

优缺点和应用场景

优点

  • 提供了对真实对象的控制,可以增加额外的功能,比如访问控制、缓存、延迟加载、日志记录等等。
  • 通过动态代理可以减少代码冗余,提高代码的灵活性和可扩展性。

缺点

  • 增加了系统的复杂度,因为每个真实对象都需要有一个代理类来配合工作。
  • 如果代理层次过多,可能会影响性能,尤其是动态代理在性能上会有一定的损耗。

应用场景:

  1. 延迟加载:当一个对象的创建成本非常高,且不一定每次都需要使用该对象时,可以通过代理来控制对象的创建时机,避免不必要的资源浪费。

  2. 访问控制:通过代理类可以控制对原对象的访问权限,只有在满足特定条件下才允许访问。

  3. 远程代理:当对象在远程服务器上时,可以通过代理类来模拟远程对象的行为。

  4. 日志记录和性能监控:在代理类中可以加入日志记录、性能监控等功能,不需要修改真实对象的代码。

总结

这些不同类型的代理模式根据应用场景和需求各自发挥作用,以上只是常见的几种情况。

比如:Spring AOP使用了动态代理模式+自定义注解实现的切面编程;JavaRMI Java平台之间的远程方法调用 ;RPC框架实现跨平台的远程过程调用。

代理模式的思想不仅仅是代码的编写,比如服务器的正向代理和反向代理。代理对象同样可以起到服务访问日志记录(审计);请求服务器只知道代理服务器的IP,对于目标服务器起到安全保护,同时可以设置IP的黑白名单限流等;服务器网络之间的解耦,比如目标服务器IP地址发生变化时只需要调整代理服务器的配置即可,而无需调整客户端请求的目标地址。

掌握设计模式及其编程思想才能做到以不变应万变,真正做到学以致用,举一反三的效果。

image

需要查看往期设计模式文章的,可以在个人主页中或者文章开头的集合中查看,可关注我,持续更新中。。。


超实用的SpringAOP实战之日志记录

2023年下半年软考考试重磅消息

通过软考后却领取不到实体证书?

计算机算法设计与分析(第5版)

Java全栈学习路线、学习资源和面试题一条龙

软考证书=职称证书?

软考中级--软件设计师毫无保留的备考分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/868586.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端实现 HTML 网页转 PDF 并导出

有个新需求,当点击【下载】按钮时,直接将当前 html页面下载为 PDF。通过 html2canvas + jsPDF 可实现PDF单页下载,甚至是多页下载,记录分享一下~ 最后有样式源码,可自取🫡有个新需求,当点击【下载】按钮时,直接将当前 html页面下载为 PDF。通过 html2canvas + jsPDF 可…

AI应用实战课学习总结(5)回归分析预测实战

本文介绍了机器学习中的起点:回归分析,并进行了一个电商用户生命周期价值(LTV)的分析预测实战,最后还进行了多种回归模型的拟合效果对比,相信你会有一个直观的印象。大家好,我是Edison。 最近入坑黄佳老师的《AI应用实战课》,记录下我的学习之旅,也算是总结回顾。 今天…

Gitlab Runner安装与配置

由于格式和图片解析问题,为了更好阅读体验可前往 阅读原文本篇使用Docker安装Gitlab runner进行runner的安装和注册,其他方式请参考官方文档非Docker安装Gitlab runner请确保runner版本和gitlab版本兼容以及docker相关版本兼容问题下载镜像 docker pull gitlab/gitlab-runner…

笔记本电脑清灰以及升级硬盘与内存

1、工具准备(1)螺丝刀:最好使用手柄较粗(容易发力)、有磁吸(螺丝不易掉)、以及批头可以替换的螺丝刀。(2)撬片:拆机常用的是三角撬片,越薄越好;没有的话也可以用废弃的银行卡替代。(3)磁吸定位板:用于存放螺丝,没有的话也可以用瓶盖替代。(4)气吹与毛刷:用于…

读量子霸权02数字时代的终结

2000年前希腊人创造的安提基西拉仪器是计算机雏形,可计算月球运转。安提基西拉代表古代模拟宇宙巅峰。巴比奇未实现最先进机械计算机梦想。图灵被誉为“计算机科学之父”,提出图灵机概念。数字计算机比模拟计算机准确。图灵测试提出机器能否像人思考。1. 爱琴海的海底 1.1. 2…

行为树(BehaviorTree )的实现与应用

前言 我最近学习使用C#脚本实现Unity行为树,并使用行为树实现了对“空洞骑士”中,“假骑士”的AI行为逻辑的简单实现。本文主要记录了在这个过程中的一些要点。 行为树的原理及实现教程来自这位大佬的博客:游戏AI行为决策——Behavior Tree(行为树) 一、运作逻辑 行为树的…

48. django下载与基本使用

1.版本 django1.x:默认不支持异步 django2.x:默认不支持异步 django3.x:自带异步功能 2. 下载 2.1 pip安装pip install django==3.2.122.2 安装注意事项 计算机名称不能出现中文 注意python解释器版本与django版本的兼容性 项目中的文件名称不能出现中文 多个项目文件尽量不…

互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(一):从.NET IoT入门开始

前言 为什么我会想着制作一个智能桌面机器人呢?自问自答一下,看过我之前文章的小伙伴应该都知道我之前有为稚晖君开源的ElectronBot桌面机器人开发过一个桌面上位机软件叫电子脑壳,由于ElectronBot桌面机器人必须连接电脑才能使用,所以限制比较多,网友又对独立版本的桌面机…

C#进阶-在Ubuntu上部署ASP.NET Core Web API应用

随着云计算和容器化技术的普及,Linux 服务器已成为部署 Web 应用程序的主流平台之一。ASP.NET Core 作为一个跨平台、高性能的框架,非常适合在 Linux 环境中运行。本篇博客将详细介绍如何在 Linux 服务器上部署 ASP.NET Core Web API 应用,包括部署准备、应用发布、配置反向…

【CodeForces训练记录】Codeforces Round 996 (Div. 2)

训练情况赛后反思 开局连WA就知道这把完蛋了,应该要掉大分了,A题没考虑清楚,B题犯傻了一时间没看出来结论 A题 当且仅当两个人贴贴的时候,轮到谁走谁就输,后手可以把先手逼到两边,如果两人之间有一段距离,两人都必须往中间靠,如果两个人都往同一边走距离不变为无效操作…

2025 特斯拉 焕新 Model Y 增减配置详细参数对比分析图解 All In One

2025 特斯拉 焕新 Model Y 增减配置详细参数对比分析图解 All In One2025 特斯拉 焕新 Model Y 增减配置详细参数对比分析图解 All In One 焕新 Model Y 增配风阻降低到 0.22 Cd ✅ 车身变长到 4797 mm ✅ 车头保险杠新增一颗摄像头 ✅ 新增前排座椅通风 ✅ 新增后排座椅电动折…

Kernel Memory 让 SK 记住更多内容

Kernel Memory (KM) 是一种多模态 AI 服务,专注于通过自定义的连续数据混合管道高效索引数据集。它支持检索增强生成(RAG)、合成记忆、提示工程以及自定义语义记忆处理。KM 支持自然语言查询,从已索引的数据中获取答案,并提供完整的引用和原始来源链接。 通过 KM 我们可以…