GoF之代理模式

2023.11.12

        代理模式是GoF23种设计模式之一,其作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。

        代理模式中的角色:代理类目标类代理类和目标类的公共接口(客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口)。

        代理模式有动态代理和静态代理两种模式,先实现静态代理作为引入。

静态代理

        考虑一个业务场景:某个项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。

第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑。这种方法可以满足需求,但显然是违背了OCP开闭原则,这种方案不可取。

第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法。这种方式也可以解决需求,但是由于采用了继承的方式,导致代码之间的耦合度较高。

第三种方案就是使用静态代理了:

先准备一个接口类和实现类:

package service;public interface OrderService {void generate();void detail();void modify();
}
package service;public class OrderServiceImpl implements OrderService{@Overridepublic void generate() {try {Thread.sleep(1234);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已生成");}@Overridepublic void detail() {try {Thread.sleep(2541);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单信息如下:******");}@Overridepublic void modify() {try {Thread.sleep(1010);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单已修改");}
}

其中Thread.sleep()方法的调用是为了模拟操作耗时。

采用静态代理为OrderService接口提供一个代理类。

package service;public class OrderServiceProxy implements OrderService{// 目标对象private OrderService orderService;// 通过构造方法将目标对象传递给代理对象public OrderServiceProxy(OrderService orderService) {this.orderService = orderService;}@Overridepublic void generate() {long begin = System.currentTimeMillis();// 执行目标对象的目标方法orderService.generate();long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");}@Overridepublic void detail() {long begin = System.currentTimeMillis();// 执行目标对象的目标方法orderService.detail();long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");}@Overridepublic void modify() {long begin = System.currentTimeMillis();// 执行目标对象的目标方法orderService.modify();long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");}
}

        这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。

        下面在客户端中使用代理对象:

import service.OrderService;
import service.OrderServiceImpl;
import service.OrderServiceProxy;public class Client {public static void main(String[] args) {//创建目标对象OrderService target = new OrderServiceImpl();//创建代理对象OrderService proxy = new OrderServiceProxy(target);//调用代理对象的代理方法proxy.generate();proxy.modify();proxy.detail();}
}

运行结果:

        以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。

        但是如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。

动态代理

        在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

        在内存当中动态生成类的技术常见的包括:

  • JDK动态代理技术:只能代理接口。
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

这里简单实现一下JDK动态代理技术:

        我们在静态代理的时候,除了以上一个接口和一个实现类之外,还要写一个代理类UserServiceProxy。在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:

import service.OrderService;
import service.OrderServiceImpl;
import service.TimerInvocationHandler;import java.lang.reflect.Proxy;public class client {public static void main(String[] args) {// 第一步:创建目标对象OrderService target = new OrderServiceImpl();// 第二步:创建代理对象OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));// 第三步:调用代理对象的代理方法// 调用代理对象的代理方法orderServiceProxy.detail();orderServiceProxy.modify();orderServiceProxy.generate();}
}

以上第二步创建代理对象是重点,其中newProxyInstance()方法有三个参数:

  • 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
  • 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
  • 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。

        接下来要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

package service;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class TimerInvocationHandler implements InvocationHandler {// 目标对象private Object target;// 通过构造方法来传目标对象public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 目标执行之前增强。long begin = System.currentTimeMillis();// 调用目标对象的目标方法Object retValue = method.invoke(target, args);// 目标执行之后增强。long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");// 一定要记得返回哦。return retValue;}
}

        InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:

  • 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
  • 第二个参数:Method method。目标方法。
  • 第三个参数:Object[] args。目标方法调用时要传的参数。

最后,可以运行客户端程序了,运行结果:

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

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

相关文章

一键创建PDF文档,高效管理您的文件资料

在繁忙的工作中,您是否曾为处理PDF文件而感到烦恼?现在,我们为您推荐一款全新的高效PDF文档管理工具——一键创建PDF文档,让您的工作效率瞬间提升! 首先,在首助编辑高手的主页面板块栏里,选择“…

华为ensp:ppp+CHAP认证

如果一头开启了认证,如果对面没有输入认证则双方无法通信 开启ppp r1和r2在系统视图模式进行同样的操作 interface Serial 1/0/0 link-protocol pppquit 开启ppp R1 去r1设置chap认证用户和密钥 进入系统视图 interface Serial 1/0/0 ppp authentication-m…

【C++笔记】优先级队列priority_queue的模拟实现

【C笔记】优先级队列priority_queue的模拟实现 一、优先级队列的介绍与使用方式1.1、优先级队列介绍1.2、优先级队列的常见使用 二、优先级队列的模拟实现1.0、仿函数的介绍1.1、构造函数1.2、优先级队列的插入push1.3、优先级队列的删除(删除堆顶元素)1.4、获取堆顶元素1.5、判…

01:2440----点灯大师

目录 一:点亮一个LED 1:原理图 2:寄存器 3:2440的框架和启动过程 A:框架 B:启动过程 4:代码 5:ARM知识补充 6:c语言和汇编的应用 A:代码 B:分析汇编语言 C:内存空间 7:内部机制 二:点亮2个灯 三:流水灯 四:按键控制LED 1:原理图 2:寄存器配置 3:代码 一:点…

python实现炒股自动化,个人账户无门槛量化交易的开始

本篇作为系列教程的引子,对股票量化程序化自动交易感兴趣的朋友可以关注我,现在只是个粗略计划,后续会根据需要重新调整,并陆续添加内容。 股票量化程序化自动交易接口 很多人在找股票个人账户实现程序化自动交易的接口&#xff0…

Django——orm模块创建表关系

django orm中如何创建表关系 1. 表关系分析 表与表之间的关系: 一对多 多对多 一对一 没有关系 判断表关系的方法: 换位思考用4张表举例: 图书表 出版社表 作者表 作者详情表图书和出版社是一对多的关系 外键字段建在多的那一方图书和作者是多对多的关系 需要创建第三张表来…

(离散数学)命题及命题的真值

答案: (5)不是命题,因为真值不止一个 (6)不是命题,因为不是陈述句 (7)不是命题,因为不是陈述句 (8)不是命题,真值不唯一

Qt 自定义按钮 区分点按与长按信号,适配触摸事件

Qt 自定义按钮 区分点按与长按信号 适配触摸事件 效果 使用示例 // 点按connect(ui.btnLeft, &JogButton::stepclicked, this, &MainWindow::btnLeft_clicked);// 长按开始connect(ui.btnLeft, &JogButton::continueOn, this, &MainWindow::slotJogLeftOn);//…

Linux 基于 LVM 逻辑卷的磁盘管理【简明教程】

一、传统磁盘管理的弊端 传统的磁盘管理:使用MBR先对硬盘分区,然后对分区进行文件系统的格式化最后再将该分区挂载上去。 传统的磁盘管理当分区没有空间使用进行扩展时,操作比较麻烦。分区使用空间已经满了,不再够用了&#xff…

Qt 事件循环

引出 UI程序之所叫UI程序,是因为需要与用户有交互,用户交互一般是通过鼠标键盘等的输入设备,那UI程序就需要有能随时响应用户交互的能力。 一个C程序的main函数大概是下面这样: int main() {...return 0; } 我们如何使程序能随…

深度学习之基于Pytorch框架的MNIST手写数字识别

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 MNIST是一个手写数字识别的数据集,是深度学习中最常用的数据集之一。基于Pytorch框架的MNIST手写数字识…

Linux - 基础IO(重定向 - 重定向模拟实现 - shell 当中的 重定向)- 下篇

前言 上一篇博客当中,我们对 文件 在操作系统当中是 如何就管理的,这个问题做了 详细描述,本篇博客将基于上篇 博客当中的内容进行 阐述,如有疑问,请参考上篇博客: Linux - 基础IO(Linux 当中…