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{
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public 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;
    }

    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public 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;
    }

    @Override
    public 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。目标方法调用时要传的参数。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

不同性别人群的股骨颈骨密度随年龄的变化趋势

增龄是发生骨质疏松的危险因素。因此,中老年人需要积极防范骨质疏松,以免发生骨折等不良事件。 为了探究不同性别人群的股骨颈骨密度随年龄的变化趋势,首先创建一个df,变量有id(编号)、age(年龄…

Git之分支与版本->课程目标及知识点的应用场景,分支的场景应用,标签的场景应用

1.课程目标及知识点的应用场景 Git分支和标签的命名规范 分支 dev/test/pre/pro(即master) dev:开发环境--windows (自己的电脑) test:测试环境--windows/linux (公司专门的测试电脑 pre:灰度环境(非常大的公司非常重要的项目) pro:正式环境 灰度环境与正式环境的服务器配置…

开发者测试2023省赛--Square测试用例

测试结果 官方提交结果 EclEmma PITest 被测文件 [1/7] Square.java /*** This class implements the Square block cipher.** <P>* <b>References</b>** <P>* The Square algorithm was developed by <a href="mailto:Daemen.J@banksys.co…

Linux:安装MySQL5.7

1. 下载 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/5.7.html#downloads 2. 解压 tar -xvf mysql-5.7.26-linux-glibc2.12-x86_64.tar 再移动并重命名一下 mv mysql-5.7.26-linux-glibc2.12-x86_64 /usr/local/mysql3. 创建mysql用户组和用户并修改权限 g…

安装完 Ubuntu 22.04 LTS 后需要做的11件事情

如果你已经安装了 Ubuntu 22.04 LTS&#xff0c;接下来如何优化呢? 在本指南中&#xff0c;我们概述了一些基本步骤&#xff0c;当你熟悉 Ubuntu 22.04 LTS (Jammy Jellyfish) 时&#xff0c;你可以采取这些步骤。 (1) 更新本地存储库并升级系统 刷新本地软件包索引。因此&a…

【ArcGIS Pro二次开发】(75):ArcGIS Pro SDK二次开发中的常见问题及解决方法

ArcGIS Pro SDK二次开发路程也近一年时间了&#xff0c;这里总结一下平时遇到的一些问题和解决方法。有些问题没能解决&#xff0c;也记录下来&#xff0c;提醒自己不要忘记。 一、在VS2022中进行调试弹出错误 正常在VS2022中&#xff0c;如果要调试程序的话&#xff0c;直接按…

【C++】stack,queue和deque

stack的介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容器&#xff0c;并提供一组特定 的成…

ARM Linux 基础学习 / Linux Shell,必要命令全记录

编辑整理 by Staok。 本文部分内容摘自 “100ask imx6ull” 开发板的配套资料&#xff08;如 百问网的《嵌入式Linux应用开发完全手册》&#xff0c;在 百问网 imx6ull pro 开发板 页面 中的《2.1 100ASK_IMX6ULL_PRO&#xff1a;开发板资料》或《2.2 全系列Linux教程&#xf…

基于YOLOv8的输电线路异物识别算法应用

基于 YOLOv8 的输电线路异物识别算法应用 输电线路作为电力系统的重要一环&#xff0c;保证其安全稳定运行是十分必要的。由于长期暴露于室外&#xff0c;线路所面临的不安全因素繁多&#xff0c;异物入侵便是其中之一。异物可能会引起线路短路甚至诱发火灾&#xff0c;因此要加…

AIGC专栏8——EasyPhoto 视频领域拓展-让AIGC肖像动起来

AIGC专栏8——EasyPhoto 视频领域初拓展-让AIGC肖像动起来 学习前言源码下载地址技术原理储备Video Inference 功能说明 & 效果展示1、Text2Video功能说明a、实现原理简介b、文到视频UI介绍c、结果展示 2、Image2Video功能说明a、实现原理简介i、单图模式ii、首尾图模式 b、…

Technology Strategy Patterns 学习笔记9 - bringing it all together

1 Patterns Map 2 Creating the Strategy 2.1 Ansoff Growth Matrix 和owth-share Matrix 区别参见https://fourweekmba.com/bcg-matrix-vs-ansoff-matrix/ 3 Communicating

金和OA jc6 任意文件上传漏洞复现

0x01 产品简介 金和OA协同办公管理系统软件&#xff08;简称金和OA&#xff09;&#xff0c;本着简单、适用、高效的原则&#xff0c;贴合企事业单位的实际需求&#xff0c;实行通用化、标准化、智能化、人性化的产品设计&#xff0c;充分体现企事业单位规范管理、提高办公效率…

华为ensp:ospf末梢stub完全末梢totally Stub

现在宣告都宣告完了&#xff0c;现在要给area1做完全末梢 末梢区域 进入r2系统视图模式 ospf 1area 1 stub quit进入r1系统视图 ospf 1 area 1 stub quit 现在去r1上查看 末梢成功 完全末梢 进入r2系统视图 ospf 1 area 1stub no-summary 现在就成为完全末梢了&…

AI 绘画 | Stable Diffusion精确控制ControlNet扩展插件

ControlNet ControlNet是一个用于控制AI图像生成的插件&#xff0c;通过使用Conditional Generative Adversarial Networks&#xff08;条件生成对抗网络&#xff09;的技术来生成图像。它允许用户对生成的图像进行更精细的控制&#xff0c;从而在许多应用场景中非常有用&#…

基于Transformer架构的ChatGPT:三步带你了解它的工作原理

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 梦想从未散场&#xff0c;传奇永不落幕&#xff0c;博主会持续更新优质网络知识、Python知识、Linux知识以及各种小技巧&#xff0c;愿你我共同在CSDN进步 目录 一、Transformer架构 1. 自注意力层 2. 前馈神…

AWS云服务器EC2实例进行操作系统迁移

AWS云服务器EC2实例进行操作系统迁移 文章目录 AWS云服务器EC2实例进行操作系统迁移1. 亚马逊EC2云服务器简介1.2 亚马逊EC2云务器与弹性云服务器区别 2. 亚马逊EC2云服务器配置流程2.1 亚马逊EC2云服务器实例配置2.1.1 EC2实例购买教程2.1.1 EC2实例初始化配置2.1.2 远程登录E…

Flutter实践一:package组织

1.架构概览 为了降低Flutter工程里lib的复杂度&#xff0c;应尽量拆分一些代码成为独立的package。如图&#xff1a; 我们将通用的组件、领域模型、API、features、存储、repository等抽取成了单独的package。这时lib只剩下多国语言、基本的页面、路由等代码了&#xff1a; 这…

Linux组调度

为什么引入组调度可以参考这篇文章的讨论。核心原因是基础的调度算法都是基于任务的&#xff0c;如果用户A有10个任务&#xff0c;用户B只有1个任务&#xff0c;假设这些任务的优先级都相同&#xff0c;那么用户A得到的CPU时间将是用户B的10倍&#xff0c;这样从任务的角度看虽…

Linux应用开发基础知识——LCD上的矢量字体Freetype(六)

前言&#xff1a; 使用 buildroot 来给 ARM 板编译程序、编译库会很简单&#xff0c;以后系统讲解 buildroot 时再使用 buildroot&#xff0c;现在我们还是手工交叉编译 freetype&#xff0c;这种方法在编译、安装一些小程序时很有用。 Freetype 是开源的字体引擎库&#xff0c…

Java13新增特性

前言 前面的文章&#xff0c;我们对Java9、Java10、Java11、Java12 的特性进行了介绍&#xff0c;对应的文章如下 Java9新增特性 Java10新增特性 Java11新增特性 Java12新增特性 今天我们来一起看一下Java13这个版本的一些重要信息 版本介绍 Java 13 是在 2019 年 9 月 17 日…