设计模式-动态代理

news2024/12/25 9:20:17

目录

定义

代理模式的优缺点

优点

缺点

应用场景

静态代理

动态代理 

相关资料


定义

        代理模式(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.coloradmin.cn/o/1677115.html

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

相关文章

Spring WebFlux 初探-响应式编程-021

🤗 ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱,有温度,有质量,有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace The Nex…

Unity射击游戏开发教程:(17)添加推进器推进和推进器推进动画

添加推进器打开功能 我们可以添加一个推进器栏,用于跟踪玩家使用推进器增强(按住左 Shift 键)的时间。当未使用推力时,将会有一段延迟,直到推力条开始再生。当棒再生时,可以使用推进器,但再生过程将重新开始。 我们将使用 Unity 的 UI Slider 组件,因此我们将其添加到已…

【挑战30天首通《谷粒商城》】-【第一天】10、环境-docker安装mysql

文章目录 课程介绍一、docker 安装 mysql Stage 1:下载镜像文件 Stage 1-1:打开官网查看镜像 Stage 1-2:拉取镜像 Stage 1-3:查看拉取的镜像 Stage 2:创建实例并启动 A:mysql(5.7版)…

代码随想录—— 填充每个节点的下一个右侧节点指针(Leetcode116)

题目链接 层序遍历 /* // Definition for a Node. class Node {public int val;public Node left;public Node right;public Node next;public Node() {}public Node(int _val) {val _val;}public Node(int _val, Node _left, Node _right, Node _next) {val _val;left _…

【完整过程】Windows下记录PadleOCR训练自己的ocr模型

一、前期准备 1、代码 参考的博主使用的是2.6版本的 博主的paddleocr代码 下面这个是官方的,可能已经更新了(我用的是官网当前最新版) paddleocr的源代码 注意:最好把上面两个代码都下载下来,后面都会用到 参考博…

HTML静态网页成品作业(HTML+CSS)——自动化专业介绍设计制作(4个页面)

🎉不定期分享源码,关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 🏷️本套采用HTMLCSS,未使用Javacsript代码,共有4个页面。 二、作品演示 三、代…

从需求到实现:能源软件服务商如何量身定制企业解决方案

能源行业需要数字化转型的原因主要有以下几点:首先,数字化技术可以提高生产效率和安全性,通过实时监控和智能调度降低事故风险,并实现远程控制和自动化生产。其次,数字化转型有助于推动能源行业的创新发展,…

Android ndk获取手机内部存储卡的根目录方法

如下所示: 代码语言:javascript jclass envcls env- FindClass("android/os/Environment"); //获得类引用if (envcls nullptr) return 0;//找到对应的类,该类是静态的返回值是FilejmethodID id env- GetStaticMethodID(envcls,…

Nodejs 第七十章(OSS)

OSS OSS(Object Storage Service)是一种云存储服务,提供了一种高度可扩展的、安全可靠的对象存储解决方案 OSS 对象存储以对象为基本存储单元,每个对象都有唯一的标识符(称为对象键)和数据。这些对象可以…

Java | Leetcode Java题解之第90题子集II

题目&#xff1a; 题解&#xff1a; class Solution {List<Integer> t new ArrayList<Integer>();List<List<Integer>> ans new ArrayList<List<Integer>>();public List<List<Integer>> subsetsWithDup(int[] nums) {Arra…

oracle多条重复数据,取最新的

1、原理讲解-可直接看2 筛选出最新的 SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (PARTITION BY LOCALAUTHID ORDER BY LASTUPDATETIME DESC) AS rn FROM USER_LOCALAUTH_STATE t ) t WHERE t.rn 1; 解释&#xff1a; 这个序号是基于[LOCALAUTHID]字段进行分…

前端 finalShell 与 docker 创建 服务连接 部署项目

准备 fianlShell 下载地址 官网下载地址 要哪个下那个&#xff0c; 我的是第一个 下载后 安装 打开 选择 SSH 进行配置 配置后点击确定即可 1、yum install -y docker -y 表示不询问&#xff0c;使用默认配置进行安装 检测版本 装最新的 2、 yum list installed | gre…

wangeditor覆盖el-select下拉框解决方法

问题&#xff1a; 发现wangeditor的层级过高&#xff0c;一开始一直想要修改全局css&#xff0c;奈何半天找不到&#xff0c;网上一大堆解决方法都无法生效&#xff0c;自定义的css样式也无法覆盖。 如果有尝试好久的朋友可以参考我的解决方案&#xff1a; 关键语句&#xf…

关于JVM内存模型和堆内存模型的理解

文章目录 前言一、JVM 内存模型的理解1.第一部分&#xff1a;线程共享区&#xff08;堆和方法区&#xff09;2.第二部分&#xff1a;线程独占区&#xff08;程序计数器、虚拟机栈和本地方法栈&#xff09;3.JVM的几个知识点3.1 垃圾回收就指线程共享区&#xff08;堆和方法区&a…

3.3 整型

本节必须掌握的知识点&#xff1a; 整型数据类型的取值范围 示例八 代码分析 汇编解析 获取数据类型的取值范围 3.3.1 整型数据类型取值范围 整型是用来表示限定范围内连续整数的数据类型。表3-1列出了C语言编译器定义的整型数据类型及其大小和取值范围。 类型 存储大小…

【卫星影像三维重建随记】obj模型及其纹理材质文件介绍

obj模型及纹理材质介绍 1.介绍1.1 背景1.2 带有纹理色彩的obj三维模型 2.带有纹理信息obj三维模型文件解析2.1 三维模型数据2.2 obj文件内容2.3mtl文件内容 3 参考 1.介绍 1.1 背景 OBJ格式是一种简单且通用的三维模型文件格式&#xff0c;支持多边形网格和基本的几何体类型&…

IDEA运行main方法,为什么要编译整个工程?

每次在IDEA中导入工程后&#xff0c;想写一个类去测试一些数据&#xff0c;有时候只是写一个main方法进行简单的输出&#xff1b; 但是每次运行一个main方法&#xff0c;整个工程都会重新编译一下&#xff0c;耗时不短 在Eclipse就不会有这个问题&#xff1b; 为什么会编译整…

华为昇腾310B1平台视频解码失败[ERROR] Send frame to vdec failed, errorno:507018

目录 1 [ERROR] Send frame to vdec failed, errorno:507018 2 bug解决尝试1 3 bug解决尝试2 4 最终解决方法 参考文献&#xff1a; 1 [ERROR] Send frame to vdec failed, errorno:507018 某项目中的代码运行报错 [ERROR] Send frame to vdec failed, errorno:507018 Ac…

Spring Boot:异常处理

Spring Boot 前言使用自定义错误页面处理异常使用 ExceptionHandler 注解处理异常使用 ControllerAdvice 注解处理异常使用配置类处理异常使用自定义类处理异常 前言 在 Spring Boot 中&#xff0c;异常处理是一个重要的部分&#xff0c;可以允许开发者优雅地处理应用程序中可…

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

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