设计模式-动态代理

目录

定义

代理模式的优缺点

优点

缺点

应用场景

静态代理

动态代理 

相关资料


定义

        代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色。代理类负责代理真实类,为真实类提供控制访问的功能,真实类则完成具体的业务逻辑。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

这样理解其实有点抽象,现实生活中举几个例子 :

  • 明星宣传

    假如要找一个明星来宣传某一个品牌,那么我们一般不会直接接触到明星,一般是找他的经纪人来商量,这样的话其实经纪人就算是一个代理

  • 游戏代练

    一般比如玩某些传奇等游戏,里面需要打怪才能升级,这个过程比较繁琐,就会找代练来帮助完成,这样代练就是一个代理

这种还有比如租房中介等等

代理一般分为两类,静态代理和动态代理 

  • 静态代理

静态代理是指代理类在编译期就已经确定,即需要事先手动编写一个代理类。

  • 动态代理

    动态代理则是在运行时动态生成代理类 (可以通过继承和实现接口两种方式分别来实现静态和动态,这里就都用接口实现的方式来演示,动态代理方案有两种实现:其一,通过Java本身自带 java.lang.reflect.Proxy 类来实现动态代理功能 。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类 。二者底层实现上都应用了反射和操作字节码技术。 )

    • JDK动态代理

      JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。

    • CGlib

      CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类作为真实类的子类去代理父类,即代理类继承自真实类。这种方式不需实现接口,可以作为JDK代理方式的补充方案。

代理模式的优缺点

优点

  • 代理对象可以隐藏原始对象的实现细节,使得客户端无需了解原始对象的具体实现。

  • 代理对象可以在原始对象的基础上添加额外的功能,例如缓存、安全验证、日志、权限验证等功能。

  • 代理对象可以控制对原始对象的访问,保护原始对象不被非法访问。

  • 代理对象可以在客户端和原始对象之间起到中介作用,使得客户端与原始对象之间的耦合度降低。

缺点

  • 引入代理类会增加系统的复杂性,增加了学习和理解的成本。

  • 由于增加了代理层,导致请求处理速度变慢。

应用场景

  • 日志记录代理:通过代理模式,我们可以在真实对象的方法执行前后进行日志记录,以实现日志记录、调试和性能监测等功能。

  • 缓存代理:代理模式可以用于实现对象的缓存,当客户端请求某个对象时,代理对象先检查缓存中是否存在该对象,如果存在则直接返回,否则创建新对象并缓存起来,从而提高系统性能。

  • 权限校验:某个用户是否具有访问某个特定功能的权限,可以在请求处理前让代理对象先检查一下是否拥有该方法的权限,没有则拒绝,有则可以访问

下面就通过传奇游戏代练的场景来实现下没有代理模式是怎么样的,以及有了代理模式发生了哪些变化

我们想一下传奇游戏一般玩家都会干些什么,登录,打怪,升级 , 将这些事情抽象成一个游戏玩家接口 (IGamePlayer):

public interface IGamePlayer {
    /**
     * 登录
     * @param userName
     * @param password
     */
    void login(String userName,String password);

    /**
     * 打怪
     */
    void killBoss();

    /**
     * 升级
     */
    void upgrade();
}

然后创建一个普通的玩家 (GamePlayer) 真实类

public class GamePlayer implements IGamePlayer {
    //用户名
    private String name;

    public GamePlayer(String name) {
        this.name = name;
    }

    public GamePlayer() {
    }

    public String getName() {
        return name;
    }

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

    public void login(String userName, String password) {
        System.out.println("登录名为:"+userName+"的用户登录成功");
    }

    public void killBoss() {
        System.out.println(this.name+"在打怪");
    }

    public void upgrade() {
        System.out.println(this.name+"升级成功!");
    }
}

此时类图结构如下 :

这是没有代理模式的情况

public class Main {
    public static void main(String[] args) {
        //原始,zhangsan只能自己打游戏
        GamePlayer gamePlayer = new GamePlayer("zhangsan");
        gamePlayer.login("zhangsan","123");
        gamePlayer.killBoss();
        gamePlayer.upgrade();
	}
}

结果:

登录名为:zhangsan的用户登录成功
zhangsan在打怪
zhangsan升级成功!

静态代理

        我们此时假如叫了一个代练,让代练除了帮我打升级以外,还得在开始代练前后通知我一下,让我别挤你的账号,创建一个代理类,继承游戏玩家接口GamePlayerProxy (代理类)

public class GamePlayerProxy implements IGamePlayer {
    //被代理的目标对象
    private GamePlayer gamePlayer;

    //通过构造传入进来
    public GamePlayerProxy(GamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    public GamePlayerProxy() {
    }

    public GamePlayer getGamePlayer() {
        return gamePlayer;
    }

    public void setGamePlayer(GamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    //对下面方法进行代理
    public void login(String userName, String password) {
        System.out.println("通知用户代练,login开始了");
        this.gamePlayer.login(userName,password);
        System.out.println("通知用户代练,login结束了");
    }

    public void killBoss() {
        System.out.println("通知用户,代练killBoss开始了");
        this.gamePlayer.killBoss();
        System.out.println("通知用户,代练killBoss结束了");
    }

    public void upgrade() {
        System.out.println("通知用户,代练upgrade开始了");
        this.gamePlayer.upgrade();
        System.out.println("通知用户,代练upgrade结束了");
    }
}

此时类图结构 :

这样的话我自己就其实不需要打游戏了

public class Main {
    public static void main(String[] args) {
        //原始,zhangsan只能自己打游戏
//        GamePlayer gamePlayer = new GamePlayer("zhangsan");
//        gamePlayer.login("zhangsan","123");
//        gamePlayer.killBoss();
//        gamePlayer.upgrade();

        //zhangsan找了代练
        GamePlayer gamePlayer = new GamePlayer("zhangsan");
        GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);
        gamePlayerProxy.login("zhangsan","123");
        gamePlayerProxy.killBoss();
        gamePlayerProxy.upgrade();
	}
}

结果 :

通知用户代练,login开始了
登录名为:zhangsan的用户登录成功
通知用户代练,login结束了
通知用户,代练killBoss开始了
zhangsan在打怪
通知用户,代练killBoss结束了
通知用户,代练upgrade开始了
zhangsan升级成功!
通知用户,代练upgrade结束了

        这其实就是静态代理,创建一个类,实现指定目标对象的接口,然后重写方法,内部会保存目标对象,然后重写的方法中会去调用目标对象的方法,前后可以干些非主业务的事情

静态代理的缺点其实比较明显 , 主要有以下几点 :

  • 代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理,这在程序规模稍大时就无法胜任了。

  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,这增加了代码维护的复杂度。

  • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法,这样就出现了大量的代码重复。

下面我们来看下动态代理是如何解决这些问题的

动态代理 

        JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:

  1. 创建InvocationHandler接口的代理类工厂:在调用Proxy类的静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。

  2. InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke方法,并将被调用的方法名、参数等信息传递给该方法。

  3. 调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。

  4. invoke方法调用:在invoke方法中,会再通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。

代理类工厂 :

public class EnhanceHandler implements InvocationHandler {
    //目标对象
    private Object obj;

    public EnhanceHandler(Object obj){
        this.obj = obj;
    }

    //获取jvm在内存中生成的代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        System.out.println("通知用户,代练" + methodName +"开始了");
        method.invoke(obj,args);
        System.out.println("通知用户,代练" + methodName + "结束了");
        return null;
    }
}
public class Main {
    public static void main(String[] args) {        
EnhanceHandler enhanceHandler = new EnhanceHandler(new GamePlayer("zhangsan"));
        IGamePlayer proxy = (IGamePlayer) enhanceHandler.getProxy();
        proxy.login("zhangsan","123");
        proxy.killBoss();
        proxy.upgrade();
    }
}

结果如下 :

通知用户,代练login开始了
登录名为:zhangsan的用户登录成功
通知用户,代练login结束了
通知用户,代练killBoss开始了
zhangsan在打怪
通知用户,代练killBoss结束了
通知用户,代练upgrade开始了
zhangsan升级成功!
通知用户,代练upgrade结束了

此时类图结构如下 :

其实这里 Proxy会在运行时动态生成IGamePlayer的实现类然后重写方法,然后我们调用代理对象方法的时候就会先到继承了InvocationHandler接口对象的invoke方法中,内部会再次调用真实对象的方法

流程图 : 

PS : 默认情况下JVM是不保存动态创建代理类字节码对象的,可以在main方法中配置代理参数让字节码保留

//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

执行完之后,会在项目根目录生成代理类字节码对象。

JVM运行时生成的代理类对象

public final class $Proxy0 extends Proxy implements IGamePlayer {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void upgrade() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void killBoss() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("upgrade");
            m5 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("killBoss");
            m4 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

相关资料

        设计模式之禅第二版

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

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

相关文章

分布式搜索-elaticsearch基础 概念

什么是elaticsearch: 倒排索引:就是将要查询的内容分成一个个词条,在将词条文档id存入,词条是唯一的。 文档词条总结: mysql和Elasticsearch概念对比: 架构: 基本概念总结:

uniapp 实现下拉刷新 下滑更新

效果图 在app或者小程序中向下滑动 会出现刷新数据 ,而上拉到底 需要更新数据 功能实现 主要俩种方式 依赖生命周期 在page.json中开启 page.json "style" : {"navigationBarTitleText" : "小小练习","backgroundTextStyle": &qu…

Transformer模型详解05-Decoder 结构

文章目录 简介结构原理第一个 Multi-Head Attention第二个 Multi-Head AttentionSoftmax 预测输出单词 简介 Transformer 模型由编码器(Encoder)和解码器(Decoder)两部分组成。这里我会着重描述解码器的结构以及在预训练、输入输…

StNet: Local and Global Spatial-Temporal Modeling for Action Recognition 论文阅读

StNet: Local and Global Spatial-Temporal Modeling for Action Recognition 论文阅读 Abstract1 Introduction2 Related Work3 Proposed Approach4 Experiments5 Conclusion 文章信息: 原文链接:https://ojs.aaai.org/index.php/AAAI/article/view/4…

期权(1):基本概念,权利金,定金,买方,卖方,零和游戏,对赌协议

期权是合约,权利金就是定金! 合约到期时 买方可以选择行权,也可以选择不行权。代价就是定金损失。因此亏损封顶,但盈利无限。卖方赚的就是买方的定金,盈利封顶,但亏损无限。 从这里,我们看出…

短视频拍摄+直播间搭建视觉艺术实战课:手把手场景演绎 从0-1短视频-8节课

抖音短视频和直播间你是否遇到这些问题? 短视频是用手机拍还是相机拍?画面怎么拍都没有质感 短视频产量低,拍的素材可用率低 看到别人用手机就能把短视频拍好自己却无从下手 明明已经打了好几盏灯了,但是画面还是比较暗 直播软件参数不会设置,电脑…

【Python探索之旅】列表

目录 特点 入门 访问元素 新增元素 修改元素 插入元素 删除元素 完结撒花 前言 在Python中,列表(List)是最常用的数据结构之一,类似于其他语言,如Java,与其不同啊Python中不需要声明数据类型。它提供了一种灵活且高效的方式…

MySQL创建索引报错 Specified key was too long;max key length is 1000 bytes.

MySQL对创建索引的大小有限制,一般索引键最大长度总和不能超过1000个字节。 问题描述 MySQL创建索引时报错 Specified key was too long;max key length is 1000 bytes. 解决办法 (1) 修改存储引擎 InnoDB的索引字段长度限制大于MyISAM,可以尝试改成…

ansible利用playbook 部署lamp架构

搭建参考:ansible批量运维管理-CSDN博客 定义ansible主机清单 [rootansible-server ~]# vim /etc/hosts 192.168.200.129 host01 192.168.200.130 host02 [rootansible-server ~]# vim /etc/ansible/hosts [webserver] host01 host02 在ansible端编写index.html…

【windows小知识#1】ISO镜像,OEM、Retail这些到底是什么意思

汇总一下每个版本windows会衍生哪些镜像出来,以windows7为例 这些文件名代表的是不同版本和不同语言的Windows 7操作系统的安装光盘映像(ISO文件)。这些文件主要区分为以下几个方面: 语言:这些文件都是中文版&#x…

平地惊雷,GPT-4o 凌晨震撼发布

GPT-4o 今日凌晨,OpenAI 2024 年春季发布会召开,OpenAI 通过短短 28 分钟的发布会,发布了「再次震惊世界」的 GPT-4o,其中 o 是指 omni(全能)的意思。 一款「全新交互(支持 文本/音频/视频 组合…

【AI学习】聊两句昨夜OpenAI的GPT-4o

蹭个热点,聊两句昨夜的大事件——OpenAI发布GPT-4o,我看到和想到的一点东西。 首先是端到端方法,前面关于深度学习的文章,对端到端的重要性做了一些学习,对端到端这个概念有了一些理解。正如Richard Sutton在《苦涩的…

直播预约丨《袋鼠云大数据实操指南》No.2:实时开发,如何成为数据智能化的有效驱动力

近年来,新质生产力、数据要素及数据资产入表等新兴概念犹如一股强劲的浪潮,持续冲击并革新着企业数字化转型的观念视野,昭示着一个以数据为核心驱动力的新时代正稳步启幕。 面对这些引领经济转型的新兴概念,为了更好地服务于客户…

C语言中的混合运算

1 混合运算 类型强制转换场景 整型数进行除法运算时&#xff0c;如果运算结果为小数&#xff0c;那么存储浮点数时一定要进行强制转换。例子&#xff1a; #include <stdio.h> //运算强制转换 int main(void) {int i5;//整型float ji/2;//这里做的是整型运算&#xff0…

【Linux取经路】进程通信之匿名管道

文章目录 一、进程间通信介绍1.1 进程间通信是什么&#xff1f;1.2 进程间通信的目的1.3 进程通信该如何实现 二、管道2.1 匿名管道2.1.1 站在文件描述符角度深入理解管道2.1.2 接口使用2.1.3 PIPE_BUFFER 和 Pipe capacity2.1.4 管道中的四种情况2.1.5 管道特征总结 2.2 匿名管…

PDF文件转换为CAD的方法

有时候我们收到一个PDF格式的设计图纸&#xff0c;但还需要进行编辑或修改时&#xff0c;就必须先将PDF文件转换回CAD格式。分享两个将PDF转换回CAD的方法&#xff0c;一个用到在线网站&#xff0c;一个用到PC软件&#xff0c;大家根据情况选择就可以了。 ☞在线CAD网站转换 …

STM32 CANFD 基础知识留档

讲得比较细的文章但可能有问题自行判定 附1 附2 前言 CAN2.0 协议中数据段波特率和仲裁段波特率默认是一致&#xff0c;因此只需要关注传输波特率即可 CANFD 协议是向下兼容 CAN2.0 的数据通讯&#xff0c;因此实际使用中需要配置 STM32H7 系列支持的标准是 Compliant with …

vue-cropper裁剪图片 vue

效果图 1.配置环境 npm install vue-cropper 2.代码 <template><div class"cropper-content"><div class"cropper-box"><div class"cropper"><vue-cropper ref"cropper" :img"option.img" :…

手撸XXL-JOB(三)——本地定时任务管理平台

引言 在XXL-JOB中&#xff0c;有一个xxl-job-admin项目&#xff0c;这个就相当于定时任务的调度平台&#xff0c;我们参考XXL-JOB&#xff0c;也添加这么一个调度平台&#xff0c;由于篇幅有限&#xff0c;我们先实现一个本地的定时任务调度平台&#xff0c;至于如何调用远程的…

element-ui的表单中,输入框、级联选择器的长度设置

使用<el-col>控制输入框的长度 <el-form-item label"姓名" label-width"80px"><el-col :span"15"><el-input v-model"form.name" autocomplete"off"></el-input></el-col></el-form…