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

十三、代理模式

文章目录

  • 1 基本介绍
  • 2 案例
    • 2.1 Sortable 接口
    • 2.2 BubbleSort 类
    • 2.3 SortTimer 类
    • 2.4 Client 类
    • 2.5 Client 类的运行结果
    • 2.6 总结
  • 3 各角色之间的关系
    • 3.1 角色
      • 3.1.1 Subject ( 主体 )
      • 3.1.2 RealObject ( 目标对象 )
      • 3.1.3 Proxy ( 代理 )
      • 3.1.4 Client ( 客户端 )
    • 3.2 类图
  • 4 动态代理的使用
    • 4.1 JDK 动态代理
      • 4.1.1 实现动态代理的步骤
      • 4.1.2 代码演示
    • 4.2 CGLIB 动态代理
      • 4.2.1 实现动态代理的步骤
      • 4.2.2 代码演示
  • 5 注意事项
  • 6 优缺点
  • 7 适用场景
  • 8 总结


1 基本介绍

代理模式(Proxy Pattern)是一种 结构型 设计模式,它在 不改变原有对象结构 的基础上,通过为其提供代理,来 增强对象的功能

代理模式分为 静态代理动态代理,本文着重讲解 静态代理,对于 动态代理,由于其实现非常复杂,所以本文只介绍如何使用 动态代理

2 案例

本案例使用 静态代理 实现了对冒泡排序进行计时的功能。

2.1 Sortable 接口

public interface Sortable { // 排序接口,实现它之后就能对 int 数组进行排序void sort(int[] nums); // 对 int 数组进行升序排序
}

2.2 BubbleSort 类

public class BubbleSort implements Sortable { // 冒泡排序@Overridepublic void sort(int[] nums) {for (int i = nums.length - 1; i > 0; i--) {for (int j = 0; j < i; j++) {if (nums[j] > nums[j + 1]) {int temp = nums[j];nums[j] = nums[j + 1];nums[j + 1] = temp;}}}}
}

2.3 SortTimer 类

public class SortTimer implements Sortable { // 排序计时器private Sortable sortable;public SortTimer(Sortable sortable) {this.sortable = sortable;}@Overridepublic void sort(int[] nums) {long start = System.currentTimeMillis(); // 记录排序的起始时间sortable.sort(nums);long end = System.currentTimeMillis(); // 记录排序的终止时间System.out.println("排序花费的时间为 " + (end - start) + " ms");}
}

2.4 Client 类

import java.util.Random;public class Client { // 客户端,测试了冒泡排序的计时功能public static void main(String[] args) {Sortable sortable = new SortTimer(new BubbleSort());sortable.sort(generateRandomArray(10000));}// 生成随机的 int 数组,长度为 length,用于测试排序private static int[] generateRandomArray(int length) {int[] arr = new int[length];Random random = new Random();for (int i = 0; i < length; i++) {arr[i] = random.nextInt(100);}return arr;}
}

2.5 Client 类的运行结果

运行结果大概为 47 毫秒,这与电脑的性能也有一定的关系。

排序花费的时间为 47 ms

2.6 总结

在没有修改 BubbleSort 类的 sort() 方法的代码的前提下,通过代理类 SortTimer,实现了对 sort() 的功能增强——增加计时功能。唯一不好的一点是需要自己实现代理类,比较麻烦。

3 各角色之间的关系

3.1 角色

3.1.1 Subject ( 主体 )

该角色负责 定义 RealObject 应该具有的 方法,Proxy 也实现 Subject 中定义的方法,从而 使 RealObject 和 Proxy 具有一致性。本案例中,Sortable 接口扮演该角色。

3.1.2 RealObject ( 目标对象 )

该角色负责 实现 Subject 定义的 方法,具备基础的功能。本案例中,BubbleSort 类扮演该角色。

3.1.3 Proxy ( 代理 )

该角色负责 使用内部聚合的 RealObject 对象实现 Subject 定义的 方法,并 对 RealObject 对的功能作一定的增强。本案例中,SortTimer 类扮演该角色。

3.1.4 Client ( 客户端 )

该角色负责 调用 Subject 定义的方法完成业务。本案例中,Client 类扮演该角色。

3.2 类图

alt text
说明:虽然图中没有指出 Client 使用了 Proxy 和 RealObject,但实际上 Client 只在创建对象时使用了它们。

4 动态代理的使用

在 Java 中,动态代理主要有两种实现方式:

  • JDK 动态代理:通过 实现 目标对象实现的 接口 来生成代理类,当目标对象没有实现的接口时,这种方法无法使用。
  • CGLIB 动态代理:通过 继承 目标对象的类来生成代理类,不需要目标对象实现接口。

这两种方式都是通过 反射 的机制在 运行时 创建具体的代理类,不需要像静态代理那样硬编码,只不过会延缓应用程序的启动。

4.1 JDK 动态代理

JDK 动态代理是 Java 原生支持 的代理方式,要求目标类必须实现至少一个接口,其核心是 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。

4.1.1 实现动态代理的步骤

  1. 定义一个(或多个)接口 和 其实现类(目标对象的类)。
  2. 创建一个实现了 InvocationHandler 接口的 处理器类,用于处理代理对象上的方法调用。
  3. 使用 Proxy.newProxyInstance() 方法 创建代理对象,该方法需要三个参数:类加载器实现的接口列表InvocationHandler 对象实例

4.1.2 代码演示

以上面为冒泡排序计时为例,Sortable 接口、BubbleSort 类不变,只需要修改 SortTimer 类、Client 类的代码,测试的结果没有明显变化。

SortTimer 类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 排序计时器,实现了 InvocationHandler 接口的处理器类
public class SortTimer implements InvocationHandler {private Sortable sortable;public SortTimer(Sortable sortable) {this.sortable = sortable;}// 获取 Sortable 的代理对象public static Sortable getProxy(Sortable sortable) {return (Sortable) Proxy.newProxyInstance(sortable.getClass().getClassLoader(), // 获取 sortable 的类加载器sortable.getClass().getInterfaces(), // 获取 sortable 实现的接口// 创建一个新的 SortTimer 对象,要求这个对象的类实现 InvocationHandler 接口new SortTimer(sortable));}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long start = System.currentTimeMillis(); // 记录排序的起始时间Object res = method.invoke(sortable, args);long end = System.currentTimeMillis(); // 记录排序的终止时间System.out.println("排序花费的时间为 " + (end - start) + " ms");return res;}
}

Client 类:

import java.util.Random;public class Client { // 客户端,测试了冒泡排序的计时功能public static void main(String[] args) {Sortable sortable = SortTimer.getProxy(new BubbleSort());sortable.sort(generateRandomArray(10000));}// 生成随机的 int 数组,长度为 length,用于测试排序private static int[] generateRandomArray(int length) {int[] arr = new int[length];Random random = new Random();for (int i = 0; i < length; i++) {arr[i] = random.nextInt(100);}return arr;}
}

4.2 CGLIB 动态代理

CGLIB 动态代理是 Java 中另一种 强大 的代理技术,允许在不实现接口的情况下对类进行代理。CGLIB 通过 继承目标对象的类 来创建代理对象,也就是说,通过这种方式创建的代理对象的类 是 目标对象的类 的 子类。它适用于那些没有实现接口的类。

4.2.1 实现动态代理的步骤

  1. 添加 CGLIB 依赖
    <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
    </dependency>
    
  2. 创建一个实现了 MethodInterceptor 接口的 拦截器类
  3. 使用 Enhancer 来生成代理对象,需要设置 要代理类拦截器对象实例

4.2.2 代码演示

以上面为冒泡排序计时为例,BubbleSort 类可以不实现 Sortable 接口,删除 Sortable 接口,修改 SortTimer 类、Client 类的代码,测试的结果没有明显变化。

注意:最好不要在 JDK 17 的环境下运行如下的代码,会报错,推荐使用 JDK 8。

BubbleSort 类:

public class BubbleSort { // 冒泡排序public void sort(int[] nums) {for (int i = nums.length - 1; i > 0; i--) {for (int j = 0; j < i; j++) {if (nums[j] > nums[j + 1]) {int temp = nums[j];nums[j] = nums[j + 1];nums[j + 1] = temp;}}}}
}

SortTimer 类:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;// 排序计时器,实现了 MethodInterceptor 接口的拦截器类
public class SortTimer implements MethodInterceptor {private BubbleSort bubbleSort;public SortTimer(BubbleSort bubbleSort) {this.bubbleSort = bubbleSort;}// 获取 BubbleSort 的代理对象public static BubbleSort getProxy(BubbleSort bubbleSort) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(bubbleSort.getClass()); // 设置要代理的类enhancer.setCallback(new SortTimer(bubbleSort)); // 设置拦截器对象实例return (BubbleSort) enhancer.create(); // 创建代理对象}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {long start = System.currentTimeMillis(); // 记录排序的起始时间Object res = method.invoke(bubbleSort, args);long end = System.currentTimeMillis(); // 记录排序的终止时间System.out.println("排序花费的时间为 " + (end - start) + " ms");return res;}
}

Client 类:

public class Client { // 客户端,测试了冒泡排序的计时功能public static void main(String[] args) {BubbleSort bubbleSort = SortTimer.getProxy(new BubbleSort());bubbleSort.sort(generateRandomArray(10000));}// 生成随机的 int 数组,长度为 length,用于测试排序private static int[] generateRandomArray(int length) {int[] arr = new int[length];Random random = new Random();for (int i = 0; i < length; i++) {arr[i] = random.nextInt(100);}return arr;}
}

5 注意事项

  • 代理对象与目标对象的接口一致性:代理对象应该与目标对象 实现相同的接口(仅限 静态代理 和 Java 代理),以便在代理对象中可以透明地替换目标对象,客户端代码无需修改即可使用代理对象。
  • 代理行为的合理性:在创建代理对象之前,需要明确代理的目的,例如是为了 延迟加载安全控制日志记录,还是其他目的,代理对象中的代理逻辑应该 合理且高效,避免不必要的复杂性和性能开销。
  • 代理对象的管理:需要妥善管理代理对象的生命周期,确保代理对象在适当的时候被创建和销毁。
  • 静态代理与动态代理的选择
    • 静态代理:如果代理类 在编译时就已经确定,且 不需要频繁更换代理逻辑,可以选择静态代理。
    • 动态代理:如果代理类 在运行时才能确定,或者 需要频繁更换代理逻辑,可以选择动态代理。
  • 避免过度使用:虽然代理模式可以带来很多好处,但也需要避免过度使用。过度使用代理模式可能会增加系统的 复杂性 和维护成本。

6 优缺点

优点

  • 降低系统耦合度:代理模式可以在 客户端目标对象 之间起到一个 中介 的作用,客户端 不直接访问 目标对象,而是通过代理对象来 间接访问,这样可以降低系统的耦合度。
  • 增强系统扩展性:当需要在目标对象上添加新的功能时,可以通过 修改代理类 来实现,而不需要修改目标对象,这提高了系统的可扩展性。
  • 保护目标对象:代理对象可以 控制对目标对象的访问,比如 限制访问权限实现延迟加载(只在需要使用目标对象时创建,其他情况只使用代理对象自身的功能)等,从而保护目标对象不被过度使用或错误使用。
  • 实现远程代理:在 分布式系统 中,远程代理可以 隐藏远程对象的存在细节,使得客户端可以像调用本地对象一样调用远程对象,简化了分布式系统的复杂性。

缺点

  • 增加系统复杂度:引入代理模式后,系统中会增加代理类,这可能会增加系统的复杂度,特别是当系统中存在大量代理类时,会使得系统难以理解和维护。
  • 请求处理速度可能变慢:由于客户端的请求需要 先经过代理对象才能到达目标对象,因此代理模式可能会 降低请求的处理速度,特别是当代理对象需要执行额外的处理逻辑时。
  • 过度使用代理:如果过度使用代理模式,可能会使得系统变得复杂且难以理解。在设计时需要根据实际需求权衡是否使用代理模式。
  • 代理类的编写和维护:编写和维护代理类需要一定的时间和精力,特别是在目标对象接口频繁变化的情况下,代理类也需要进行相应的修改。

7 适用场景

  • 远程代理:当 对象位于远程服务器上 时,可以使用代理模式来进行 远程访问。代理对象可以隐藏实际对象的细节,客户端通过代理对象来访问远程对象,而无需了解远程对象的实现细节。
  • 虚拟代理:当 创建一个对象 需要很长时间 或 消耗大量资源 时,可以使用代理模式来 延迟对象的创建。代理对象会尽量满足各种方法调用,当需要时才真正创建真正的对象。
  • 安全代理:当 需要控制对对象的访问权限 时,可以使用代理模式。代理对象可以控制客户端对真实对象的访问权限。
  • 缓存代理:当 需要缓存对象 时,可以使用代理模式。代理对象可以在获取真实对象之前首先检查缓存中是否存在对象,如果存在则直接返回缓存对象,降低数据库的访问压力,提高系统的响应速度。
  • 日志记录代理:当 需要对对象的方法调用进行日志记录 时,可以使用代理模式。代理对象可以在调用真实对象的方法之前或之后记录日志。

8 总结

代理模式 是一种 结构型 设计模式,在 不改变原有对象结构 的基础上,通过为其 提供代理,来 增强对象的功能,如虚拟代理、安全代理、日志记录代理等功能。使用代理模式可以 降低系统耦合度增强系统扩展性。但是在使用 代理模式 时需要注意不要过度使用,如果过度使用,则会降低系统的响应速度。

代理模式 共有两种:

  • 静态代理:提前在源文件中写代理类(硬编码),实现起来比较简单。
  • 动态代理:不需要提前写代理类,而是在运行程序时通过 反射 动态生成代理类,实现起来比较复杂,但可以使用现有的技术:
    • JDK 动态代理:使用位于 java.lang.reflect 包中的 Proxy 类 和 InvocationHandler 接口。
    • CGLIB 动态代理:添加 CGLIB 的依赖,使用其内部的 MethodInterceptor 接口 和 Enhancer 类。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 八问八答搞懂Transformer内部运作原理
  • 贪心算法part03
  • 中场接杀放网前小球
  • VSCode 使用总结
  • HTTP状态码_五类
  • ComsolMatlab 螺旋孔多孔超材料高温吸声性能研究
  • 【微服务】Spring Cloud中如何使用Eureka
  • 在Visual Studio/Qt Creator 中使用CMake安装和使用vcpkg包
  • 全新在线客服系统源码(pc+h5+uniapp+公众号小程序+抖音)附搭建接入教程
  • 基于RK3568 Android11 移除长按电源按键弹窗的对话框中的 [关机] 和 [紧急呼救] 选项(详细分析)
  • Jenkins 部署Vue项目指引: Vue项目本地跨域代理 、解决ERR_UNSAFE_PORT
  • 轨迹优化 | 基于ESDF的共轭梯度优化算法(附ROS C++/Python仿真)
  • 驾驭企业数字化转型的利器:《TOGAF®标准第10版》
  • qt自定义控件遇到的找不到头文件的问题
  • CentOS的根目录下,/bin 和 /sbin 用途和权限
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • CSS 专业技巧
  • JSDuck 与 AngularJS 融合技巧
  • nodejs实现webservice问题总结
  • npx命令介绍
  • PaddlePaddle-GitHub的正确打开姿势
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 二维平面内的碰撞检测【一】
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 经典排序算法及其 Java 实现
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 思考 CSS 架构
  • 详解NodeJs流之一
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 正则表达式
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #define、const、typedef的差别
  • #php的pecl工具#
  • $.ajax,axios,fetch三种ajax请求的区别
  • $.each()与$(selector).each()
  • $L^p$ 调和函数恒为零
  • (06)Hive——正则表达式
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (24)(24.1) FPV和仿真的机载OSD(三)
  • (Note)C++中的继承方式
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (九十四)函数和二维数组
  • (三) diretfbrc详解
  • (图)IntelliTrace Tools 跟踪云端程序
  • (一)Dubbo快速入门、介绍、使用
  • (转)c++ std::pair 与 std::make
  • *上位机的定义
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • []常用AT命令解释()
  • [AI 大模型] Meta LLaMA-2