十三、代理模式
文章目录
- 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 类图
说明:虽然图中没有指出 Client 使用了 Proxy 和 RealObject,但实际上 Client 只在创建对象时使用了它们。
4 动态代理的使用
在 Java 中,动态代理主要有两种实现方式:
- JDK 动态代理:通过 实现 目标对象实现的 接口 来生成代理类,当目标对象没有实现的接口时,这种方法无法使用。
- CGLIB 动态代理:通过 继承 目标对象的类来生成代理类,不需要目标对象实现接口。
这两种方式都是通过 反射 的机制在 运行时 创建具体的代理类,不需要像静态代理那样硬编码,只不过会延缓应用程序的启动。
4.1 JDK 动态代理
JDK 动态代理是 Java 原生支持 的代理方式,要求目标类必须实现至少一个接口,其核心是 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口。
4.1.1 实现动态代理的步骤
- 定义一个(或多个)接口 和 其实现类(目标对象的类)。
- 创建一个实现了
InvocationHandler
接口的 处理器类,用于处理代理对象上的方法调用。 - 使用
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 实现动态代理的步骤
- 添加 CGLIB 依赖。
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version> </dependency>
- 创建一个实现了
MethodInterceptor
接口的 拦截器类。 - 使用
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
类。
- JDK 动态代理:使用位于
相关文章:

十三、代理模式
文章目录 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 动态…...

Unity物理模块 之 2D效应器
本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正 1.什么是效应器 2D 效应器 - Unity 手册 2D 效应器是与 2D 碰撞器一起使用的组件,相当于预先编写好的插…...

一款手机壳凭什么卖800元?Casetify品牌策略全解析 | 品牌出海
Casetify官网 巴黎奥运会,张怡宁的手机壳火了。 张怡宁在现场观战并使用手机的照片在网上流传,不是因为这位奥运前冠军,而是她的手机壳。这款满是「花花绿绿」图案的手机壳,迅速被网友发掘出是Casetify品牌的名为「炫彩花卉」的…...

【Rust光年纪】并发编程利器:探索 Rust 异步库与并行处理工具
构建高效异步应用:Rust 异步库详细解读 前言 在当今软件开发领域,Rust语言作为一种快速、安全和并发性能出色的编程语言,备受开发者青睐。随着Rust生态系统的不断扩大,越来越多的异步库和并行处理工具被引入到Rust开发中。本文将…...

机器学习第一课
1.背景 有监督学习:有标签(连续变量(回归问题:时间序列等)、分类变量(分类)) 无监督学习:没有标签(聚类、关联(相关性分析:哪些相关…...

C语言典型例题32
《C程序设计教程(第四版)——谭浩强》 习题2.9 编程序用getchar函数读入两个字符给c1,c2,然后分别用putchar函数和printf函数输出这两个字符。 (1)变量c1,c2应该定义为字符型或者整型吗&#x…...

第二十五天学习笔记2024.8.9
1、通过frp内网穿透共享数据库信息 [root1 ~]# mysql -p密码 mysql> create user li% identified by 1; mysql> create database test; mysql> grant all on test.* to li; [root1 ~]# tar -xf frp_0.33.0_linux_amd64.tar.gz [root1 ~]# cd frp_0.33.0_linux_a…...

sqlserver将一张表导出成txt
bcp zjwb_sb_20111122.dbo.ep_pb_groupvisitplace out c:/1.txt -n -U sa -P sa...

YOLOv8+DeepSort实现
目录 1,YOLOv8算法简介 2,DeepSort算法介绍 1. SORT目标追踪 3,实现流程 1.检测 2. 生成detections 3. 卡尔曼滤波预测 4.使用匈牙利算法将预测后的tracks和当前帧中的detections进行匹配 5. 卡尔曼滤波更新 4,代码实现 …...

「链表」链表原地算法合集:原地翻转|原地删除|原地取中|原地查重 / LeetCode 206|237|2095|287(C++)
概述 对于一张单向链表,我们总是使用双指针实现一些算法逻辑,这旨在用常量级别空间复杂度和线性时间复杂度来解决一些问题。 所谓原地算法,是指不使用额外空间的算法。 现在,我们利用双指针实现以下四种行为。 //Definition fo…...

【STM32】SPI通信和RTC实时时钟
个人主页~ SPI通信和RTC实时时钟 SPI通信一、简介二、硬件电路三、基本原理四、SPI时序1、时序基本单元2、时序 五、FLASH操作注意事项1、写入操作2、读取操作 六、SPI外设1、简介2、结构 七、传输方式1、主模式全双工连续传输2、非连续传输 RTC实时时钟一、Unix时间戳二、BKP1…...

DAMA学习笔记(十三)-大数据和数据科学
1.引言 大数据不仅指数据的量大,也指数据的种类多(结构化的和非结构化的,文档、文件、音频、视频、流数据等),以及数据产生的速度快。数据科学家是指从从数据中探究、研发预测模型、机器学习模型、规范性模型和分析方法…...

【Java】Java 中的 toLowerCase() 方法详解
我最爱的那首歌最爱的angel 我到什么时候才能遇见我的angel 我最爱的那首歌最爱的angel 我不是王子也会拥有我的angel 🎵 张杰《云中的angel》 在 Java 编程中,字符串处理是一个非常常见的任务。为了便于开发者操作和格式化字符串&…...

Linux: 进程概念详解
1. 冯诺依曼体系结构 截至目前,我们所认识的计算机,都是有一个个的硬件组件组成 。 【注意】: a. 这里的存储器指的是内存 b. 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备) c.外…...

【C++】模板详细讲解(含反向迭代器)
欢迎来到我的Blog,点击关注哦💕 前言: C的模板在是泛型编程的重要组成部分,编写在不同类型上工作的代码,而无需为每个类型编写重复的代码,这有助于减少代码冗余并提高代码的可维护性。 模板 模板的介绍 …...

haproxy七层代理详解之-完整安装部署流程及负载均衡实现-及热更新方法
一.负载均衡 1.1负载均衡时什么 负载均衡:Load Balance,简称LB,是一种服务或基于硬件设备等实现的高可用反向代理技术,负载均网络流量等)分担给指定的一个或多个后端特定的服务器或设备,从而提高了衡将特定的业务(web服务、公司…...

C++11 bind
bind bind 用来将可调用对象和参数一起进行绑定。可调用对象包括普通函数、全局函 数、静态函数、类静态函数甚至是类成员函数,参数包括普通参数和类成员。绑定后的 结果,可以使用 std::function 进行保存,并延迟调用到我们需要的时候。 绑…...

LeetCode199 二叉树的右视图
前言 题目: 199. 二叉树的右视图 文档: 代码随想录——二叉树的右视图 编程语言: C 解题状态: 成功解决! 思路 二叉树层序遍历问题的变种,右视图即意味着二叉树每层的最后一个节点。 代码 /*** Definiti…...

数据赋能(172)——开发:数据挖掘——影响因素、直接作用、主要特征
影响因素 主要影响因素如下: 数据类型与属性: 数据类型和对象的不同属性会使用不同的数据类型来描述,如年龄可能是整数类型,而生日则是日期类型。数据挖掘时需要对不同的数据类型进行不同的处理,这直接影响到挖掘算法…...

Vue:Vue3-TypeScript-Pinia-Vite-pnpm / 基础项目 / 20240807
一、项目技术栈 / 依赖 序号技术栈版本解释1node20.14.02vue 3.4.31 3vite 5.3.4 4TypeScript 5.2.2 5 types/node 22.0.2 解决TypeScript项目中缺少对应模块的类型定义文件的问题6 element-plus 2.7.8 ui组建7 types/js-cookie js-cookie 3.0.6 3.0.5 8 sass 1.77.8 9 hu…...

windows Qt 录屏 录音
启动录屏录音: connect(&m_Process, &QProcess::readyReadStandardOutput, [&]() {qDebug() << "Standard output:" << QString::fromLocal8Bit(m_Process.readAllStandardOutput()); });connect(&m_Process, &QProcess…...

AAC中的ADTS格式分析
😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的…...

iOS内存管理---MRC vs ARC
系列文章目录 iOS基础—Block iOS基础—Protocol iOS基础—KVC vs KVO iOS网络—AFNetworking iOS网络—NSURLSession iOS内存管理—MRC vs ARC iOS基础—Category vs Extension iOS基础—多线程:GCD、NSThread、NSOperation iOS基础—常用三方库:Mason…...

【数学分析笔记】第1章第1节:集合(2)
这节我自己补了一些内容,要不然听不太懂陈纪修老师讲的 1. 集合与映射 1.3 子集与真子集 假如有 S \textbf{S} S和 T \textbf{T} T两个集合,其中, S \textbf{S} S的所有元素都属于 T \textbf{T} T,则称 S \textbf{S} S是 T \te…...

大话设计模式:七大设计原则
目录 一、单一职责原则(Single Responsibility Principle, SRP) 二、开放封闭原则(Open-Closed Principle, OCP) 三、依赖倒置原则(Dependency Inversion Principle, DIP) 四、里氏替换原则&am…...

利用多商家AI智能名片小程序提升消费者参与度与个性化体验:重塑零售行业的忠诚策略
摘要:在数字化浪潮席卷全球的今天,零售行业正经历着前所未有的变革。消费者对于购物体验的需求日益多样化、个性化,而零售商则面临着如何将一次性购物者转化为品牌忠诚者的巨大挑战。多商家AI智能名片小程序作为一种新兴的数字营销工具&#…...

Scala 闭包
Scala 闭包 Scala 闭包是一个非常重要的概念,它允许我们创建可以在稍后某个时间点执行的功能片段。闭包是一个函数,它捕获了封闭范围内的变量,即使在函数外部,这些变量也可以在函数内部使用。这使得闭包成为处理异步操作、回调和…...

前端JS总结(中)
目录 前言 正文 对象: 分类: 自定义对象: 内置对象: 重点: 常用内置对象: 字符串对象:String 获取字符串长度: 大小写转换: 获取某个字符: 截取字…...

elasticsearch的match_phrase匹配及其可能导致的查询问题
目录 1.match_phrase使用介绍 2.规避可能产生的查询问题 解决方式 一.查询和索引分词器一致,即都使用max_word或者都使用smart 二.使用slop增加匹配的容忍度 3.参考文档 1.match_phrase使用介绍 elasticsearch的match_phrase查询是全文查询,主要用…...

C++快速理解之继承
一、继承和派生 1.是什么? C 中的继承是类与类之间的关系,与现实世界中的继承类似 例如:儿子继承父亲的财产 继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程 例如: 类B继承…...