设计模式-代理模式(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.rhkb.cn/news/308306.html

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

相关文章

AIGC专栏10——EasyAnimate 一个新的类SORA文生视频模型 轻松文生视频

AIGC专栏10——EasyAnimate 一个新的类SORA文生视频模型 &#x1f4fa;轻松文生视频 学习前言源码下载地址技术原理储备&#xff08;DIT/Lora/Motion Module&#xff09;什么是Diffusion Transformer (DiT)LoraMotion Module EasyAnimate简介EasyAnimate原理界面展示快速启动云…

主干网络篇 | YOLOv8更换主干网络之ConvNext | 全新的纯卷积模型

前言:Hello大家好,我是小哥谈。2022年,Facebook AI Research和UC Berkeley一起发表了一篇文章A ConvNet for the 2020s,在文章中提出了ConvNeXt纯卷积神经网络,它对标的是2021年非常火的Swin Transformer,通过一系列实验比对,在相同的FLOPs下,ConvNeXt相比Swin Transfo…

软考系规第2章思维导图,软硬件网络和次新技术大杂烩

虽然目前系统规划与管理师的教程是否改版存在不确定性&#xff0c;但是不影响咱们先概要了解当前的教程&#xff0c;使用思维导图的方式粗读教程。 为了帮助你更好的学习系规教程&#xff0c;降低系规教程阅读门槛&#xff0c;指尖疯特发起了教程伴读活动&#xff0c;通过伴读脑…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.4 月末操作:货币折算

2.6.4 月末操作&#xff1a;货币折算 如果一个公司代码启用了多个本位币&#xff0c;如下表所示&#xff0c;则在平时记账时&#xff0c;系统会在凭证行项目中同时体现出多个本位币的金额。 图2.6.4-1 两个本位币的金额都会实时更新到科目余额中。因此&#xff0c;在月末可以直…

2024最新版守约者二级域名分发系统

主要功能 二级域名管理&#xff1a;我们的系统提供全面的二级域名管理服务&#xff0c;让您轻松管理和配置二级域名。 域名分发&#xff1a;利用我们先进的域名分发技术&#xff0c;您可以自动化地分配和管理域名&#xff0c;确保每个用户或客户都能及时获得所需的域名资源。 自…

虚幻引擎启动报错记录

0x00007FFEF0C8917C (UnrealEditor-CoreUObject.dll)处(位于 UnrealEditor.exe 中)引发的异常: 0xC0000005: 写入位置 0x0000000000000030 时发生访问冲突。 解决办法&#xff1a;首先查看堆栈信息&#xff0c;我的项目启动是因为默认场景编译不过&#xff0c;进到编辑器配置文…

08 Php学习:if语句、Switch语句

PHP 条件语句 当您编写代码时&#xff0c;您常常需要为不同的判断执行不同的动作。您可以在代码中使用条件语句来完成此任务。 在 PHP 中&#xff0c;提供了下列条件语句&#xff1a; if 语句 - 在条件成立时执行代码 if…else 语句 - 在条件成立时执行一块代码&#xff0c;…

【vim 学习系列文章 20 -- a:mode 的值有哪些?】

请阅读【嵌入式开发学习必备专栏 之 Vim】 文章目录 a:mode 的值有哪些?举例Vim 底部状态栏设置 a:mode 的值有哪些? 在 Vim 脚本语言中&#xff0c;a:mode 常常用于函数内部&#xff0c;以获取该函数被调用时 Vim 正处于的模式。它主常用于那些可以从不同模式下被调用的函数…

铸造大型基础平板的结构应该怎样设计

设计大型基础平板的结构时&#xff0c;需要考虑以下几个方面&#xff1a; 地质条件&#xff1a;首先要了解工程所在地的地质条件&#xff0c;包括土质、地下水位、地震状况等。根据地质条件来选择合适的基础类型&#xff0c;如浅基、深基或地下连续墙等。 荷载分析&#xff1a…

【C++】深度解析--拷贝构造函数(从0开始,详解浅拷贝到深拷贝,小白一看就懂!!!)

目录 一、前言 二、拷贝构造函数 &#x1f34e;概念解析 &#x1f95d;特性解析 &#x1f4a6;为什么拷贝构造函数使用传值方式会引发无穷递归调用&#xff1f; &#x1f4a6;为什么拷贝构造函数的形参中要加入 const 修饰 &#x1f4a6;若未显式定义&#xff0c;编译器会生…

支持向量机模型

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个支持向量机模型程序,最后打印5个条件分别的影响力。 示例一 为了计算每个条件对一件事情发生的影响力&#xff0c…

word从零基础到高手【办公】

第1课 - word基础操作快速入门第2课 - 让你效率10倍提升的快捷操作第3课 - word排版快速入门第4课 - 排版实战案例讲解第5课 - 搞定论文排版全过程第6课 - 让你的word更强大的神技第7课 - 提高工作效率必备的批量操作 资料截图如下: 发送: "word办公" 获取提取码

代码随想录算法训练营DAY25|C++回溯算法Part.2|216. 组合总和III、17.电话号码的字母组合

文章目录 216. 组合总和III题意理解树形结构伪代码实现剪枝操作CPP代码实现 17.电话号码的字母组合解题思路树形结构伪代码实现隐藏回溯CPP代码 216. 组合总和III 力扣题目链接 文章讲解&#xff1a;216. 组合总和III 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算…

数据库(2)

目录 6.buffer pool,redo log buffer和undo logo&#xff0c;redo logo,bin log概念以及关系&#xff1f; 7.从准备更新一条数据到事务的提交的流程描述&#xff1f; 8.能说下myisam和innodb的区别吗&#xff1f; 9.说下MySQL的索引有哪些吧&#xff1f; 10.什么是B树&…

C语言-详解内存函数

文章目录 1.memcpy使用和模拟实现1.1 memcpy函数的使用规则1.2 memcpy函数的使用1.2 模拟实现memcpy函数 2.memmove 函数的使用和模拟实现2.1 memmove 函数使用规则2.2 memmove函数的使用2.3 模拟实现memmove函数2.3.1 从后往前移2.3.2 从前往后移 2.4 算法实现2.4.1 从前往后移…

使用docker制作Android镜像(实操可用)

一、安装包准备 1、准备jdk 下载地址&#xff1a;Java Downloads | Oracle 注意版本&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 参考下面的 对照表&#xff0c;不然后面构建镜像报错&#xff0c;就是版本不对。 我就是因为下载的jdk17&…

边缘计算平台原理、关键功能以及技术优势

1、什么是边缘计算及其工作原理&#xff1f; 边缘计算是一种分布式计算模型&#xff0c;它将数据处理和存储靠近数据源头和最终用户的边缘设备上&#xff0c;从而减少了数据传输和延迟。边缘计算旨在解决云计算模型所面临的问题&#xff0c;例如延迟高、带宽瓶颈和安全性等问题…

虚幻引擎架构自动化及蓝图编辑器高级开发进修班

课程名称&#xff1a;虚幻引擎架构自动化及蓝图编辑器高级开发进修班 课程介绍 大家好 我们即将推出一套课程 自动化系统开发。 自动化技术在项目开发的前中后期都大量运用。如何您是一家游戏公司&#xff0c;做的是网络游戏&#xff0c;是不是经常会遇到程序员打包加部署需…

Energia学习案例

案例一&#xff1a;编写程序&#xff0c;实现每次按下按键&#xff0c;红色LED灯改变状态&#xff08;初始点亮&#xff09;&#xff0c;在窗口监视窗中显示按击次数。[要求用计时器实现按键消抖] #include"Timer.h" //包含计时器头文件volatile int stateHIGH; //灯…

防止邮箱发信泄露服务器IP教程

使用QQ邮箱,网易邮箱,189邮箱,新浪邮箱,139邮箱可能会泄露自己的服务器IP。 泄露原理&#xff1a;服务器通过请求登录SMTP邮箱服务器接口&#xff0c;对指定的收件人发送信息。 建议大家使用商业版的邮箱&#xff0c;比如阿里云邮箱发信等 防止邮件发信漏源主要关注的是确保邮件…