设计模式-动态代理

目录

定义

代理模式的优缺点

优点

缺点

应用场景

静态代理

动态代理 

相关资料


定义

        代理模式(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());}}
}

相关资料

        设计模式之禅第二版

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

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

相关文章

【UE5.1 角色练习】01-使用小白人蓝图控制商城角色移动

目录 效果 步骤 一、导入资源 二、控制角色移动 三、更换角色移动动作 效果 步骤 一、导入资源 新建一个工程,然后在虚幻商城中将角色动画的相关资源加入工程,这里使用的是“动画初学者内容包”和“MCO Mocap Basics” 将我们要控制的角色添加进…

C# Winform+Halcon结合标准视觉工具

介绍 winform与halcon结合标准化工具实例 软件架构 软件架构说明 基于NET6 WINFORMHALCON 实现标准化视觉检测工具 集成相机通讯 集成PLC通讯 TCP等常见通讯 支持常见halcon算子 图形采集blob分析高精度匹配颜色提取找几何体二维码提取OCR识别等等 。。。 安装教程 …

PMR-440N7Q韩国施耐德三和相序继电器EOCR-PMR

韩国施耐德三和EOCR继电器PMR-440N7Q PMR-440-N 直流电动机保护器:DCL、DOCR-S/H 欠电流继电器:EUCR-3C 交流电压继电器:EOVR、EVR-PD、EVR-FD、EUVR 韩国三和EOCR电动机保护器:EOCR-SS、EOCR-SS1/SS2、EOCR-AR、EOCR-ST、EOCR-SP、EOCR-SP1/SP2、EOCR-SE、EOCR-SE2/SE PMR-44…

刘邦的创业团队是沛县人,朱元璋的则是凤阳;要创业,一个县人才就够了

当人们回顾刘邦和朱元璋的创业经历时,总是会感慨他们起于微末,都创下了偌大王朝,成就无上荣誉。 尤其是我们查阅史书时,发现这二人的崛起班底都是各自的家乡人,例如刘邦的班底就是沛县人,朱元璋的班底是凤…

新手去做抖音小店,多久稳定出单?出单的前提需要做好哪些工作?

大家好,我是电商小V 很多小伙伴经常去咨询的一个问题,那就是我是新手去做抖音小店多久才能够稳定出单呢? 根据我多年做店的经验来说,新手运营抖店的时候,只要自己的做店思路正确,执行力到位,一…

Vue3实战笔记(22)—路由Vue-Router 实战指南(路由传参)

文章目录 前言一、路由router-link二、路由传参1.query方式2.params方式3.props传参 总结 前言 vue-router 是 Vue.js 官方路由管理器。它和 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得易如反掌。 前面提到过简单的使用路由,直到上文使用404界面…

电脑常用的PDF阅读器-嗨动PDF编辑器!带你详细了解它

电脑常用的PDF阅读器-嗨动PDF编辑器!在数字化信息爆炸的时代,PDF格式的文件因其易于打印和保留原始格式等优点,成为了人们日常工作和学习的常用格式。而对于PDF文件的处理,一款功能强大、操作简便的PDF阅读器是必不可少的。今天&a…

红酒与美食的完善搭配艺术

在美食的世界里,红酒总是扮演着不可或缺的角色。它与美食的搭配,是一门深奥的艺术。云仓酒庄雷盛红酒,作为一款备受欢迎的红酒品牌,以其卓着的品质和丰富的口感,成为了红酒与美食搭配的典范。 雷盛红酒,源…

重生奇迹mu任务大全都有哪些

1、新手任务适用职业:剑士,弓箭手,魔法师,召唤前面的任务不过是一些根据提示打怪,这个都很容易完成,很多玩家因为感觉没有必要和奖励过低,直接选择已经熟悉游戏知识选项,跳过任务过程…

MQTT_服务器的安装_1.3

此例子是以Windows系统安装开源版本的EMQX 下载 EMQX 下载并解压 解压如图 进入bin 文件夹在文件目录中输入cmd回车 启动服务器 然后在cmd中输入下面的代码(会弹出一个访问网络的选项,确认可以访问网络) emqx start 结果如图(…

干什么副业好呢?

选择适合自己的副业可以根据个人的兴趣、技能和时间来决定。以下是一些常见的副业选择 1. 在线销售 可以在电商平台上开设自己的网店,销售自己感兴趣的产品,如手工艺品、服装、配饰等。 2. 做任务 网上我还在做的致米宝库,一个月有个一千多…

【React】 打包扫描出现高风险文件 YUI 版本太低 JSEncrypt

漏洞定位 扫出漏洞的情况,多是在说下面几个工具: jquery js-cookie jsencrypt 参考链接 YUI:2.9.0 (Link) http://www.cvedetails.com/cve/CVE-2012-5883/ 1.于是在打包后的代码中搜索 YUI(不区分大小写,不进行全字匹配&…