设计模式-代理模式(Proxy)

1. 概念

  • 代理模式(Proxy Pattern)是程序设计中的一种结构型设计模式。它为一个对象提供一个代理对象,并由代理对象控制对该对象的访问。

2. 原理结构图

在这里插入图片描述

  • 抽象角色(Subject):这是一个接口或抽象类,它声明了代理对象和目标对象共同的接口,这样在任何使用目标对象的地方都可以使用代理对象。代理对象和目标对象对外具有一致的方法。
  • 真实角色(Real Subject):也叫被代理角色,它实现了抽象角色,定义了真实对象在关注的行为中执行的操作。
  • 代理角色(Proxy):也叫委托类,它同样实现了抽象角色,并持有真实角色的实例,可以在调用真实角色前后添加一些操作,也可以不调用真实角色而直接返回结果。

3. 代码示例

3.1 示例1(静态代理)
  • 这个案例涉及到一个银行系统,其中有一个 Account 接口,一个实现该接口的 SavingsAccount 类,以及一个代理类 AccountProxy 来代表 SavingsAccount 对象,并添加额外的日志记录功能。
// 定义 Account 接口
interface Account {  void deposit(double amount);  double withdraw(double amount);  double getBalance();  
}// 实现 Account 接口的 SavingsAccount 类
class SavingsAccount implements Account {  private double balance;  public SavingsAccount(double initialBalance) {  this.balance = initialBalance;  }  @Override  public void deposit(double amount) {  balance += amount;  }  @Override  public double withdraw(double amount) {  if (amount <= balance) {  balance -= amount;  return amount;  } else {  System.out.println("Insufficient balance!");  return 0;  }  }  @Override  public double getBalance() {  return balance;  }  
}// 接着,我们创建静态代理类 AccountProxy,它实现 Account 接口并持有一个Account类型的引用(这里是SavingsAccount对象)
class AccountProxy implements Account {  private Account account;  public AccountProxy(Account account) {  this.account = account;  }  @Override  public void deposit(double amount) {  log("Depositing " + amount);  account.deposit(amount);  log("Deposited successfully.");  }  @Override  public double withdraw(double amount) {  log("Withdrawing " + amount);  double withdrawnAmount = account.withdraw(amount);  if (withdrawnAmount > 0) {  log("Withdrawn successfully.");  }  return withdrawnAmount;  }  @Override  public double getBalance() {  return account.getBalance();  }  private void log(String message) {  System.out.println("[LOG] " + message);  }  
}// 在客户端代码中使用 AccountProxy 来代理 SavingsAccount 对象
public class BankClient {public static void main(String[] args) {// 创建SavingsAccount对象Account savingsAccount = new SavingsAccount(1000.0);// 使用AccountProxy代理SavingsAccount对象Account proxyAccount = new AccountProxy(savingsAccount);// 存款proxyAccount.deposit(500.0);// 取款double withdrawn = proxyAccount.withdraw(300.0);System.out.println("withdrawn: " + withdrawn);// 查询余额double balance = proxyAccount.getBalance();System.out.println("Current balance: " + balance);}
}
  • 输出
[LOG] Depositing 500.0
[LOG] Deposited successfully.
[LOG] Withdrawing 300.0
[LOG] Withdrawn successfully.
withdrawn: 300.0
Current balance: 1200.0
  • 在这个例子中, AccountProxy 类代理了 SavingsAccount 对象,并在每次调用方法时添加了日志记录功能。这种静态代理模式使得可以在不修改原始 SavingsAccount 类的情况下,为其添加额外的功能。但是,静态代理模式的一个缺点是,对于每个需要代理的类,都需要手动创建一个代理类,这可能导致代码冗余和难以维护。在更复杂的场景中,可能会考虑使用动态代理或AOP(面向切面编程)等更高级的技术来减少代理类的编写工作。

3.2 示例2(动态代理)
  • JDK动态代理模式在复杂场景下的一个典型应用是AOP(面向切面编程)。在这个场景下,我们可能会想要在不修改业务逻辑代码的情况下,为方法调用添加额外的行为,如日志记录、事务管理、安全检查等。下面是一个使用JDK动态代理模式的案例,模拟一个具有日志记录和性能监控功能的业务处理系统。
interface BusinessService {void executeBusinessLogic();
}class BusinessServiceImpl implements BusinessService {@Overridepublic void executeBusinessLogic() {System.out.println("Executing business logic...");// 模拟业务逻辑执行时间try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Business logic executed.");}
}class LoggingDynamicHandler {private Object target;public LoggingDynamicHandler(Object target) {this.target = target;}public Object getNewProxyInstance() {// 使用Proxy类的静态方法newProxyInstance来动态创建代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 方法调用前记录日志System.out.println("[LOG] " + method.getName() + " is called.");long startTime = System.currentTimeMillis();// 调用目标对象的方法Object result = method.invoke(target, args);// 方法调用后记录日志和性能信息long endTime = System.currentTimeMillis();long elapsedTime = endTime - startTime;System.out.println("[LOG] " + method.getName() + " completed in " + elapsedTime + " ms.");return result;}});}
}public class ProxyPatternDemo {public static void main(String[] args) {// 创建目标对象(需要被代理的对象)BusinessService businessService = new BusinessServiceImpl();// 创建LoggingDynamicHandler对象,并传入目标对象LoggingDynamicHandler handler = new LoggingDynamicHandler(businessService);// 使用LoggingDynamicHandler类的getNewProxyInstance方法获取动态代理对象BusinessService proxyBusinessService = (BusinessService) handler.getNewProxyInstance();// 通过代理对象调用方法proxyBusinessService.executeBusinessLogic();}
}
  • 将看到如下输出:
[LOG] executeBusinessLogic is called.
Executing business logic...
Business logic executed.
[LOG] executeBusinessLogic completed in 1013 ms.
  • 在这个例子中,当调用proxyBusinessService.executeBusinessLogic()时,实际上会调用InvocationHandler的invoke方法,并在其中记录日志和计算方法执行的时间。通过这种方式,我们可以在不修改BusinessServiceImpl代码的情况下,为其添加日志记录和性能监控功能。
  • JDK动态代理模式的优点在于它简单易用,只需要目标对象实现接口,就可以为其创建代理对象。然而,它也有局限性,即只能为接口创建代理对象,不能为类创建代理对象。如果需要为类创建代理对象,可以考虑使用CGLIB等第三方库。

3.3 示例3(Cglib代理)

添加依赖

<dependency>  <groupId>cglib</groupId>  <artifactId>cglib</artifactId>  <version>3.3.0</version>  
</dependency>
  • 下面是一个CGLIB代理模式的示例,假设有一个 UserService 类,该类没有实现任何接口,想要使用CGLIB来创建其代理对象,并在代理对象中添加一些额外的逻辑(例如日志记录)。
class UserService {public void addUser(String username, String password) {System.out.println("添加用户: " + username + " 密码: " + password);}
}class CglibProxyInterceptor implements MethodInterceptor {private final Object target;public CglibProxyInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 在方法执行前添加逻辑beforeMethod(method);// 调用目标方法Object result = proxy.invokeSuper(obj, args);// 在方法执行后添加逻辑afterMethod(method);return result;}private void beforeMethod(Method method) {System.out.println("开始执行方法: " + method.getName());// 这里可以添加更多逻辑,比如日志记录、权限检查等}private void afterMethod(Method method) {System.out.println("结束执行方法: " + method.getName());// 这里可以添加更多逻辑,比如异常处理、资源清理等}
}class CglibProxyFactory {public static <T> T createProxy(Class<T> clazz, Object target) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(new CglibProxyInterceptor(target));return (T) enhancer.create();}
}public class ProxyCglibDemo {public static void main(String[] args) {// 创建一个目标对象UserService userService = new UserService();// 使用CGLIB代理工厂创建代理对象UserService proxyUserService = CglibProxyFactory.createProxy(UserService.class, userService);// 调用代理对象的方法proxyUserService.addUser("Alice", "password123");}
}
  • 将看到如下输出:
开始执行方法: addUser
添加用户: Alice 密码: password123
结束执行方法: addUser
  • 在上面的示例中,UserService 类没有实现任何接口,但可以使用 CglibProxyFactory 为其创建代理对象。当调用 proxyUserService.addUser() 方法时,实际上会触发 CglibProxyInterceptor 中的 intercept 方法,可以在这个方法中添加自定义的逻辑。
  • 这个示例展示了如何为任意类创建CGLIB代理对象,并添加通用代理逻辑。在实际应用中,可以根据需要扩展CglibProxyInterceptor类,添加更多的自定义逻辑。

4. 优缺点

  • 主要作用
    • 提供对目标对象的间接访问,以实现控制、增强或简化原有对象的功能。
  • 优点
    • 降低耦合度:代理模式通过引入一个中间层,即代理对象,来实现对目标对象的访问控制,这样可以减少系统各个部分之间的直接依赖,从而提高系统的扩展性和维护性。
    • 保护目标对象:代理可以对目标对象进行封装,防止外部对目标对象的直接访问,从而起到保护的作用。
    • 功能增强:代理可以在不改变目标对象的基础上,为目标对象添加新的功能,比如日志记录、权限检查等。
  • 缺点
    • 性能损耗:由于每次调用目标对象的方法都需要先经过代理对象,因此可能会带来一定的性能损耗。
    • 复杂性增加:引入代理模式会增加代码的复杂性,需要额外编写代理对象的代码,并考虑代理对象与目标对象之间的交互。
    • 透明度问题:对于不熟悉代理模式的开发者来说,可能会觉得代码难以理解和维护,因为代理对象的存在增加了代码的间接性。

5. 应用场景

5.1 主要包括以下几个方面
  1. 远程代理:用于为远程对象提供本地代表。隐藏网络连接和通讯细节,让调用者就像访问本地对象一样访问远程服务。
  2. 安全代理:用于控制对敏感对象的访问。可以在代理中加入访问控制逻辑,以确保只有满足特定条件的请求才能访问目标对象。
  3. 智能引用代理:用于控制对象的引用计数,实现对象的缓存和再利用。常用于缓存框架和对象池中。
  4. 日志代理:用于在不改变原有代码的情况下,为目标对象的方法增加日志记录功能。可以记录方法调用的时间、参数、返回值等信息。
  5. 事务代理:用于控制复杂对象的所有事务操作,确保所有操作要么全部成功,要么全部失败。

5.2 实际应用
  1. 在客户端-服务器架构中,远程代理可以代表一个远程对象,处理网络通信细节,使得客户端可以像与本地对象交互一样与远程对象交互。
  2. 在访问控制系统中,安全代理用于控制对敏感资源的访问,例如确保用户在访问文件系统或数据库前已经通过身份验证和授权检查。
  3. 在业务层和服务层之间添加一个代理层,用于记录方法调用的详细信息,如参数、返回值和执行时间,以便于性能监控和故障排查。
  4. 在企业应用中,事务代理可以包装业务操作,确保所有操作在一个事务上下文中完成,要么全部成功提交,要么在出现错误时回滚。

6. JDK中的使用

  • AOP(面向切面编程)
    • Spring AOP 是 Spring 框架中的一个核心功能,它允许开发者定义跨越多个点的横切关注点,如安全、事务、缓存等。AOP 在运行时通过动态代理将这些横切关注点织入到目标对象的方法调用前后,从而实现对这些方法的增强。
    • Spring AOP 支持两种类型的动态代理:基于接口的 JDK 动态代理和基于继承的 CGLib 动态代理。JDK 动态代理要求目标类实现一个或多个接口,而 CGLib 动态代理则不要求目标类有接口,它通过生成目标类的子类来实现代理。
  • MyBatis
    • 拦截器(Interceptor)机制:MyBatis允许开发者自定义拦截器来拦截对Mapper接口方法的调用。这种机制也是基于代理模式实现的,拦截器可以在方法调用前后添加自定义的逻辑,比如修改参数、处理结果或者进行日志记录等。
    • Mapper接口的动态代理:在MyBatis中,当我们定义了一个Mapper接口和一个对应的XML配置文件后,我们并没有实现这个接口,但仍然可以调用它的方法来执行数据库操作。这是因为MyBatis使用动态代理来实例化这些Mapper接口。当应用程序尝试获取一个Mapper接口的实例时,MyBatis实际上返回的是一个动态生成的代理对象。这个代理对象内部持有一个指向Mapper接口的引用,并将接口方法的调用转发到这个内部引用上,进而执行相应的SQL语句。

7. 注意事项

  • 明确代理目的:代理应明确增强、控制或保护目标对象的目的。
  • 确保代理逻辑正确:代理类中的代理逻辑应准确无误,避免引入新的问题。
  • 保持目标对象不变:代理模式不应修改目标对象的代码,仅通过代理对象进行交互。
  • 考虑性能影响:代理可能会引入一定的性能开销,需权衡利弊。
  • 注意线程安全:若代理对象在多线程环境下使用,需确保线程安全。
  • 妥善处理异常:代理过程中可能出现的异常应得到妥善处理。
  • 选择合适的代理类型:根据具体需求选择静态代理、动态代理或虚拟代理等合适的代理类型。

8. 外观模式 VS 代理模式

模式目的模式架构主要角色应用场景
外观模式简化接口,减少系统的复杂性外观角色和子系统角色复杂的系统且包含多个子系统,简化系统的接口
代理模式控制对被代理类的访问代理角色和目标角色访问对象前进行一些预处理操作的场景

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

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

相关文章

ros2 launch gazebo_ros gazebo.launch.py无法启动

我的系统是ubuntu20.04&#xff0c;ros2的版本是humble&#xff0c;当运行gazebo仿真时&#xff0c;运行 ros2 launch gazebo_ros gazebo.launch.py命令&#xff0c;会出现以下问题&#xff1a; 此时&#xff0c;这个页面会卡死在第六行&#xff0c;gazebo也不会打开 但最后单…

哈希函数算法

概述 为了实现哈希集合这一数据结构&#xff0c;有以下几个关键问题需要解决&#xff1a; 哈希函数&#xff1a;能够将集合中任意可能的元素映射到一个固定范围的整数值&#xff0c;并将该元素存储到整数值对应的地址上。冲突处理&#xff1a;由于不同元素可能映射到相同的整…

C语言中局部变量和全局变量是否可以重名?为什么?

可以重名 在C语言中, 局部变量指的是定义在函数内的变量, 全局变量指的是定义在函数外的变量 他们在程序中的使用方法是不同的, 当重名时, 局部变量在其所在的作用域内具有更高的优先级, 会覆盖或者说隐藏同名的全局变量 具体来说: 局部变量的生命周期只在函数内部,如果出了…

【C++类和对象】构造函数与析构函数

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

Stacked Hourglass Networks for Human Pose Estimation 用于人体姿态估计的堆叠沙漏网络

Stacked Hourglass Networks for Human Pose Estimation 用于人体姿态估计的堆叠沙漏网络 这是一篇关于人体姿态估计的研究论文&#xff0c;标题为“Stacked Hourglass Networks for Human Pose Estimation”&#xff0c;作者是 Alejandro Newell, Kaiyu Yang, 和 Jia Deng&a…

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…

第1章、react基础知识;

一、react学习前期准备&#xff1b; 1、基本概念&#xff1b; 前期的知识准备&#xff1a; 1.javascript、html、css&#xff1b; 2.构建工具&#xff1a;Webpack&#xff1a;https://yunp.top/init/p/v/1 3.安装node&#xff1a;npm&#xff1a;https://yunp.top/init/p/v/1 …

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果 一、简单介绍 二、简单动态聚光灯效果实现原理 三、简单动态聚光灯效果…

Mysql视图与事物与字符集实验

一 视图 1.视图的定义 视图是一个虚拟表&#xff0c;其内容由查询定义。 2.视图的优点 1&#xff09;视点集中 2&#xff09;简化操作 3&#xff09;定制数据 4&#xff09;分隔合并数据 5&#xff09;安全性好 3.语法格式及限定条件 1&#xff09;语法格式&#xff1…

轻量化模块整理,即插即用

轻量化模块整理&#xff0c;即插即用&#xff08;持续更新&#xff09; 整理一些轻量化的结构&#xff0c;作为知识储备&#xff0c;可以用到后续的项目和研究中 Mobilenetv3 深度可分离卷积 MobileNetV3 是一个轻量级的深度学习模型&#xff0c;专为移动和边缘设备上的高效…

力扣HOT100 - 56. 合并区间

解题思路&#xff1a; class Solution {public int[][] merge(int[][] intervals) {// 先按照区间起始位置排序Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);int[][] res new int[intervals.length][2];int idx -1;for (int[] interval : intervals) {//直接加入的…

CSS基础之伪类选择器(如果想知道CSS的伪类选择器知识点,那么只看这一篇就足够了!)

前言&#xff1a;学习CSS就必须要学习选择器&#xff0c;在之前我们已经学习了基本选择器和复合选择器&#xff0c;但是还有几个选择器没有学习&#xff0c;这篇文章主要讲解伪类选择器。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-…

基于springboot实现视频网站管理系统【项目源码+论文说明】计算机毕业设计

基于springboot实现视频网站管理系统演示 摘要 使用旧方法对视频信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在视频信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问…

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-回铃音补偿

文章目录 前言联系我们解决问题操作步骤 前言 回铃音&#xff1a; 当别人打电话给你时&#xff0c;你的电话响铃了&#xff0c;而他听到的声音叫做回铃音。回铃音是被叫方向主叫方传送&#xff0c;也是彩铃功能的基础。我们平时打电话听到的“嘟 嘟 嘟 嘟”的声音&#xff0c;就…

asp.net core 网页接入微信扫码登录

创建微信开放平台账号&#xff0c;然后创建网页应用 获取appid和appsecret 前端使用的vue&#xff0c;安装插件vue-wxlogin 调用代码 <wxlogin :appid"appId" :scope"scope" :redirect_uri"redirect_uri"></wxlogin> <scri…

机器学习和深度学习 --李宏毅(笔记与个人理解)Day 18

Day 18 Spatial Transformer Layer 因为单纯的cNN无法做到scaling&#xff08;放大&#xff09;and rotation&#xff08;转&#xff09;&#xff0c;所以我们引入&#xff1b; 实战中也许我们可以做到 是因为 我们的training data 中包含了对data 的augmentation&#xff1b; …

解锁智能未来:用Ollama开启你的本地AI之旅

Ollama是一个用于在本地运行大型语言模型&#xff08;LLM&#xff09;的开源框架。它旨在简化在Docker容器中部署LLM的过程&#xff0c;使得管理和运行这些模型变得更加容易。Ollama提供了类似OpenAI的API接口和聊天界面&#xff0c;可以非常方便地部署最新版本的GPT模型并通过…

企业邮箱价格调查:找到适合你的最佳选择

企业邮箱价格从免费到几百元的都有&#xff0c;价格不同获得的功能和服务也不同&#xff0c;按需购买。企业邮箱多少钱一年&#xff1f;企业邮箱价格。Zoho Mail企业邮箱轻量版300元/5用户/年&#xff0c;高级版200元/用户/年&#xff0c;套件版150元/用户/元。具体的价格取决于…

《HF经理》:一、管理误区

1、不善授权: 原因&#xff1a;不信任下属&#xff0c;惯性思维&#xff08;任务一来自己冲到最前面&#xff09; 对策&#xff1a;从个人成功到带领团队成功。培养并信任下属。 2、不主动寻求上级支持: 原因&#xff1a;上级不仅仅是你的监督和考核者&#xff0c;还是你的支…

计算机网络 Cisco静态路由实验

一、实验要求与内容 1、路由器的基本配置 &#xff08;1&#xff09;命名 &#xff08;2&#xff09;关闭域名解析 &#xff08;3&#xff09;设置路由接口IP地址 2、配置静态路由以实现所有客户机都能互相通信 3、配置默认路由 4、了解ping命令和trace&#xff08;跟踪…