【设计模式】Java 的三种代理模式

news2025/1/23 13:43:58

文章目录

  • 一、前言
  • 二、正文
    • 1、静态代理
    • 2、动态代理
    • 3、Cglib代理
    • Spring中AOP使用代理
  • 三、总结

一、前言

代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。
在这里插入图片描述

这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法。

代理模式大致有三种角色:

  • Real Subject:真实类,也就是被代理类、委托类。用来真正完成业务服务功能;
  • Proxy:代理类,将自身的请求用 Real Subject 对应的功能来实现,代理类对象并不真正的去实现其业务功能;
  • Subject:定义 RealSubject 和 Proxy 角色都应该实现的接口。
    在这里插入图片描述

代理模式有三种类型,静态代理动态代理(JDK代理,接口代理)Cglib代理(在内存中动态的创建目标对象的子类)

二、正文

1、静态代理

静态代理需要先定义接口,被代理对象与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
在这里插入图片描述

可以看见,代理类无非是在调用委托类方法的前后增加了一些操作。委托类的不同,也就导致代理类的不同。

某公司生产电视机,在当地销售需要找到一个代理销售商。那么客户需要购买电视机的时候,就直接通过代理商购买就可以。

代码示例:
电视机:

public class TV {

    private String name;//名称

    private String address;//生产地

    public TV(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "TV{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

创建公司接口:

public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();
}

公司的工厂生产电视机:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","合肥");
    }
}

代理商去下单拿货(静态代理类):

public class TVProxy implements TVCompany{

    private TVCompany tvCompany;

    public TVProxy(){

    }

    @Override
    public TV produceTV() {
        System.out.println("TV proxy get order .... ");
        System.out.println("TV proxy start produce .... ");
        if(Objects.isNull(tvCompany)){
            System.out.println("machine proxy find factory .... ");
            tvCompany = new TVFactory();
        }
        return tvCompany.produceTV();
    }
}

消费者通过代理商拿货(代理类的使用):

public class TVConsumer {

    public static void main(String[] args) {
        TVProxy tvProxy = new TVProxy();
        TV tv = tvProxy.produceTV();
        System.out.println(tv);
    }
}

输出结果:

TV proxy get order .... 
TV proxy start produce .... 
machine proxy find factory .... 
TV factory produce TV...
TV{name='小米电视机', address='合肥'}

Process finished with exit code 0

小结

  • 优点:静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。

  • 缺点:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。

如何解决静态代理中的缺点呢?答案是可以使用动态代理方式

2、动态代理

在这里插入图片描述

动态代理具有如下特点:

  • JDK动态代理对象不需要实现接口,只有目标对象需要实现接口。

  • 实现基于接口的动态代理需要利用JDK中的API,在JVM内存中动态的构建Proxy对象。

  • 需要使用到 java.lang.reflect.Proxy,和其newProxyInstance方法,但是该方法需要接收三个参数。
    在这里插入图片描述

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

有一天公司增加了业务,出售的商品越来越多,售后也需要跟上。但是公司发现原来的代理商,还要再培训才能完成全部的业务,于是就找了另外的动态代理商B
代理商B 承诺无缝对接公司所有的业务,不管新增什么业务,均不需要额外的培训即可完成。

代码示例:
公司增加了维修业务:

public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();

    /**
     * 维修电视机
     * @param tv 电视机
     * @return 电视机
     */
    public TV repair(TV tv);
}

工厂也得把维修业务搞起来:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","合肥");
    }

    @Override
    public TV repair(TV tv) {
        System.out.println("tv is repair finished...");
        return new TV("小米电视机","合肥");
    }
}

B代理商 全面代理公司所有的业务。使用Proxy.newProxyInstance方法生成代理对象,实现InvocationHandler中的 invoke方法,在invoke方法中通过反射调用代理类的方法,并提供增强方法。

public class TVProxyFactory {

    private Object target;

    public TVProxyFactory(Object o){
        this.target = o;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TV proxy find factory for tv.... ");
                Object invoke = method.invoke(target, args);
                return invoke;
            }
        });
    }
}

购买、维修这两个业务 B代理就可以直接搞定了。后面公司再增加业务,B代理也可以一样搞定。

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany target = new TVFactory();
        TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
    }
}

输出结果:

TV proxy find factory for tv.... 
TV factory produce TV...
TV proxy find factory for tv.... 
tv is repair finished...

Process finished with exit code 0

小结:

  • 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。

  • 动态代理的方式中,所有的函数调用最终都会经过 invoke 函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。

JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类,并且代理类也只能代理接
口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。
怎么解决这个问题呢?我们可以用 CGLIB 动态代理机制。

3、Cglib代理

静态代理和JDK代理都需要某个对象实现一个接口,有时候代理对象只是一个单独对象,此时可以使用Cglib代理。
在这里插入图片描述

Cglib代理可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。

C代理商不仅想代理公司,而且还想代理多个工厂的产品。

Cglib通过Enhancer 来生成代理类,通过实现MethodInterceptor接口,并实现其中的intercept方法,在此方法中可以添加增强方法,并可以利用反射Method或者MethodProxy继承类 来调用原方法。

看到 B代理商承接了公司(接口)的多种业务,那么此时C代理商又从中发现新的商机, B
只能代理某个公司的产品,而我不仅想要代理公司产品,而且对接不同的工厂,拿货渠道更广,赚钱更爽快。于是Cglib就用上了。

代码示例:

public class TVProxyCglib implements MethodInterceptor {

    //给目标对象创建一个代理对象
    public Object getProxyInstance(Class c){
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(c);
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("TVProxyFactory enhancement.....");
        Object object = methodProxy.invokeSuper(o, objects);
        return object;
    }
}

新代理的B工厂

public class TVFactoryB {

    public TV produceTVB() {
        System.out.println("tv factory B producing tv.... ");
        return new TV("华为电视机", "南京");
    }

    public TV repairB(TV tv) {
        System.out.println("tv B is repair finished.... ");
        return tv;
    }
}

C代理可以直接和公司合作,也可以和工厂打交道。并且可以代理任何工厂的产品。

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
        System.out.println("==============================");

        TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
        TV tv = tvFactoryB.produceTVB();
        tvFactoryB.repairB(tv);
    }
}

输出结果:

TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv.... 
TVProxyFactory enhancement.....
tv B is repair finished.... 

Process finished with exit code 0

Spring中AOP使用代理

Spring中AOP的实现有JDK和Cglib两种,如下图:
在这里插入图片描述

如果目标对象需要实现接口,则使用JDK代理。
如果目标对象不需要实现接口,则使用Cglib代理。

三、总结

  1. 静态代理:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能。

  2. JDK动态代理:需要代理类实现某个接口,使用Proxy.newProxyInstance方法生成代理类,并实现InvocationHandler中的invoke方法,实现增强功能。

  3. Cglib动态代理:无需代理类实现接口,使用Cblib中的Enhancer来生成代理对象子类,并实现MethodInterceptor中的intercept方法,在此方法中可以实现增强功能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是转化率优化(CRO)?网站转化率不高,可以看看这篇文章

你是否将人们带到你的网站&#xff0c;但只是让他们中的一小部分人完成了该页面的目标&#xff1f;你可以每天有成千上万的网站访问者到达。但如果你的网站没有设置成鼓励转换&#xff0c;你就不会说服网站访问者去做。这使得他们的整个访问几乎毫无价值&#xff0c;特别是如果…

MySQL-中间件mycat(三)

目录 &#x1f341;高可用方案 &#x1f341;安装配置 HAProxy &#x1f342;安装 HAProxy &#x1f342;启动验证 &#x1f341;配置 Keepalived &#x1f342;安装 Keepalived &#x1f342;修改配置文件 &#x1f342;启动验证 &#x1f342;测试高可用 &#x1f341;mycat …

经典transformer视觉模型总结

Vision Transformer 模型 ViT: AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE 是 2020 年 Google 团队提出的将 Transformer 应用在图像分类的模型。 ViT 在 Transformer 架构的视觉模型的地位类似 ResNet 模型。因为其模型“简单”且效果好,可…

Doris单机版安装和初步使用

参考官方文档 https://doris.apache.org/zh-CN/docs/dev/get-starting/ 下载安装包 下载 - Apache Doris Index of /apache/doris/1.2/1.2.2-rc01 前置修改 #修改 /etc/security/limits.conf, 执行命令 vim /etc/security/limits.conf #添加以下 * soft nofile 204800 *…

【模电实验】基尔霍夫定律、叠加定理和戴维南定理验证实验

实验目的 验证基尔霍夫电流定律&#xff08;KCL&#xff09;和电压定律&#xff08;KVL&#xff09;加深对该定理的理解验证叠加定理&#xff0c;加深对该定理的理解验证戴维南定理&#xff0c;掌握有源二端口网络的开路电压&#xff0c;短路电流和入端等效电阻的测定方法通过实…

Pod探针解析及实战(k8s)

一、探针类型 1.1livenessProbe存活探针 用于判断容器是否存活&#xff08;running状态&#xff09;&#xff0c;如果LivenessProbe探针探测到容器不健康&#xff0c;则kubelet杀掉该容器&#xff0c;并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针…

cmake创建windows工程编译环境

1.1 为什么需要CMake 你或许听过好几种 Make 工具&#xff0c;例如 GNU Make &#xff0c;QT 的 QMake &#xff0c;微软的 MS NMake&#xff0c;BSD PMake&#xff0c;Makepp等等。这些 Make 工具遵循着不同的规范和标准&#xff0c;所执行的 Makefile 格式也千差万别。这样就…

ubuntu虚拟机增加磁盘后,虚拟机内部应该如何分配对应空间

fdisk -l 输入命令 parted /dev/sda 输入命令 unit s 设置Size单位&#xff0c;方便追加输入 输入命令 p free 查看详情 输入命令 resizepart 3 追加容量到sda3 输入命令 83886046s 空闲容量区间Free Space结束位置 输入命令 q 退出 输入命令 pvresize /dev/sda3 更新pv物…

【计算机网络】Linux 系统是如何收发网络包的?

【计算机网络】Linux 系统是如何收发网络包的&#xff1f; 文章目录 【计算机网络】Linux 系统是如何收发网络包的&#xff1f;网络模型Linux 网络协议栈Linux 接收网络包的流程Linux 发送网络包的流程总结 网络模型 为了使得多种设备能通过网络相互通信&#xff0c;和为了解决…

空格在科技类文章的排版中对于阅读体验的影响

© 2018 sparanoid © 2018-2023 Conmajia 第一部分援引自《中文文案排版指北》 研究显示&#xff0c;打字的时候不喜欢在中文和英文之间加空格的人&#xff0c;感情路都走得很辛苦&#xff0c;有七成的比例会在 34 岁的时候跟自己不爱的人结婚&#xff0c;而其余三成的…

分布式锁-Redisson

分布式锁 1、分布式锁1.1 本地锁的局限性1.1.1 测试代码1.1.2 使用ab工具测试(单节点)1.1.3 本地锁问题演示(集群情况) 1.2 分布式锁实现的解决方案1.3 使用Redis实现分布式锁(了解即可)1.3.1 编写代码1.3.2 压测 1.4 使用Redisson解决分布式锁1.4.1 实现代码1.4.1 压测1.4.2 可…

DS1302

DS1302时钟芯片简介 DS1302是DALLAS公司推出的涓流充电时钟芯片&#xff0c;内含一个实时时钟/日历和31字节静态RAM&#xff0c;可以通过串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、星期、月、年的信息&#xff0c;每个月的天数和闰年的天数可自动调整&a…

深度分析Netflix的投资价值,虽面临激烈竞争,但前景无限光明

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 公司介绍 Netflix(NFLX)是一家在视频流媒体领域非常成功的公司&#xff0c;运营着全球最大的视频流媒体订阅平台之一&#xff08;目前已经有超过2.3亿的付费会员&#xff09;&#xff0c;它的商业模式也比较简单&#xff0…

Linux环境jdk安装教程及详细步骤

下载jdk包&#xff1a; 下载地址&#xff1a;https://www.oracle.com/cn/java/technologies/javase/javase8u211-later-archive-downloads.html 这里点击下载后&#xff0c;需要登录才可以下载&#xff0c;没有帐号就注册一下即可。 将下载的文件放至服务器/usr/local/jdk目录…

4.13~4.17(PE文件结构预习+hook+进程hellow)

常见PE文件结构 常见的PE文件&#xff1a;exe、dll、sys Ag&#xff1a; exe就不用多说&#xff0c;就是可执行文件 dll动态链接库 对于 Windows 操作系统&#xff0c;操作系统的大部分功能都由 DLL 提供 &#xff08;https://learn.microsoft.com/zh-cn/troubleshoot/window…

三:slab分配器

目录 slab分配器 基本概念 slab分配内存 主要结构体 kmem_cache per cpu freelist slab分配器 基本概念 针对小粒度内存分配 伙伴系统以页4kb为最小分配单位&#xff0c;但对于一些时候&#xff0c;这太大了&#xff0c;会造成严重的内存浪费&#xff0c;产生大量内存碎…

【mac】iterm2通过rz命令往服务器上传文件

需要的资源文件在这里iterm2-zmodem&#xff0c;设置的0积分&#xff0c;如果csdn给调了&#xff0c;点这里下载bak 1、通过命令行打开bin文件夹 cd /usr/local/binopen . 2、把上面下载的俩文件复制进去 3、还是在/usr/local/bin下调整权限 cd /usr/local/binchmod 777 ite…

华为云上云实践:Windows环境下优化云硬盘EVS的创建、挂载和初始化

本文主要讲解华为云云硬盘 EVS 的在 Windows 服务器上创建、挂载及云硬盘初始化等基本操作&#xff0c;快速掌握华为云云硬盘 EVS 操作方法。 文章目录 一、前言二、前期准备&#xff1a;华为云 EVS 采购三、挂载非共享云硬盘 EVS五、初始化云硬盘 EVS 一、前言 华为云 EVS&am…

C嘎嘎~~【初识C++ 上篇】

初识C 上篇 &#x1fac5;1. C关键字&#x1fac5; 2.命名空间&#x1f937;‍♂️2.1命名空间的定义&#x1f937;‍♂️2.2命名空间的使用 &#x1fac5; 3.C输入 & 输出 转眼间&#xff0c; 就进入C这个新的篇章啦&#xff01; 我带着些许心悸 和 激动&#xff1a; 心悸…

Wing IDE 解决鼠标悬浮

Wing IDE 解决鼠标悬浮 通过修改文件配置&#xff0c;解决鼠标悬浮没有出现变量值和函数没有自动提示的问题。 配置文件路径查看&#xff1a; 打开该文件夹下的下图配置文件&#xff1a; 添加下图两行配置&#xff0c;然后重启wingide即可。 Wing IDE 常用快捷键 调节字…