• 每日一个设计模式之【代理模式】


    每日一个设计模式之【代理模式

    ☁️前言🎉🎉🎉

      大家好✋,我是知识汲取者😄,今天给大家带来一篇有关代理模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性可移植性可维护性,同时还能让你的代码更加精炼,具备艺术美感

    推荐阅读

    • 设计模式导学:🚪传送门
    • 每日一个设计模式系列专栏:🚪传送门
    • 设计模式专属Gitee仓库:✈启程
    • 设计模式专属Github仓库:🚀上路
    • 知识汲取者的个人主页:💓点我哦

    🌻模式概述

    • 什么是代理模式

      代理模式(Proxy Pattern)也称委托模式,是一种结构型模式,

    • 代理模式的作用:通过增加一个中间层,增强方法

      增强方法是指增强方法的功能,让原本被代理类的方法具有更多其它的功能,这个类似于装饰器模式,只需要再代理类中增加代码逻辑,以此让被代理类的方法增添许多新功能

    • 代理模式的优缺点

      • 优点

        • 扩展性高。想要改动功能,可以再保留原有类的同时增加一个代理类,从而无需修改原有代码,符合开闭原则
        • 安全性高。通过代理类,可以控制被委托对象的访问控制,提高系统的安全性
        • 耦合度低。客户端只需要和代理类进行交互,不需要和具体功能的实现类进行交互,降低了系统的耦合度

        ……

      • 缺点

        • 增加了系统的复杂度,因为多添加一个中间层,而且一些代理模式实现起来也较为复杂(例如远程代理)

        ……

    • 代理模式的适用场景

      • 当客户端对象需要访问远程主机中的对象时可以使用远程代理
      • 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理
      • 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理
      • 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理
      • 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理
      • 想要实现AOP编程,可以使用动态代理
    • 代理模式的角色划分

      • 抽象主题(Subject):声明了真实主题和代理主题的共同接口
      • 真实主题(RealSubject):具体的被代理类(被委托类),是被代理的具体对象,实现了真实的业务操作,当Proxy都无法胜任工作时,RealSubject就会出场
      • 代理主题(Proxy):具体的代理类(委托类),它包含了真实主题的引用,代理对象也可以有自己的一些附属操作,帮助真实主题更好地处理请求
      • 请求者(Client):系统的外部交互类,与代理对象进行交互,对应本文中的测试类
    • 代理模式的分类

      代理模式各种各样,且应用范围很广,常见的可以大致将代理模式分为两大类:静态代理和动态代理。此外,还存在虚拟代理、远程代理和访问代理

      • 虚拟代理(Virtual Proxy):虚拟代理只有当正真需要实例时,它才会生成和初始化实例对象。它能够很大程度节约内存,类似于懒加载
      • 远程代理(Remote Proxy):远程让我们可以让我们完全不必在意RealSubject角色是否在远程网络上,可以如同它自己身体一样(透明地)调用它的方法。它能够很大程度提高系统分布式应用的能力,Java的RMI(RemoteMethodInvocation,远程方法调用)就相当于Remote Proxy
      • 访问代理(Access Proxy):也称保护代理(Protect Proxy),访问代理用于在调用RealSubject角色的功能时设置访问权限,只允许指定的用户调用方法,而当其它用户调用方法时则直接报错
      • 缓冲代理(Buffer Proxy):为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间
      • 智能引用代理(Smart Proxy):为某一个对象的引用提供额外操作
      • 静态代理(Static Proxy):代理类和被代理类的关系在运行前(也就是编译阶段)就确定了
      • 动态代理(Dynamic Proxy):代理类和被代理类的关系在运行时确定

    🌱代理模式的实现

    🍀静态代理

    在静态代理中,代理类和被代理类的关系在编译阶段就已经确定了,它们都需要实现一个相同的接口

    温馨提示:静态代理是对目标对象的方法进行手动增强,不灵活又麻烦,因此开发过程中基本用不到。(静态代理是理解动态代理的基础,因此也很有必要进行学习)

    • 静态代理的优点:相较于动态代理实现起来较为简单

    • 静态代理存在的弊端

      • 存在代码冗余。代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复
      • 可维护性差。不易维护,一旦接口更改,代理类和目标类都需要更改,在实际项目中应用较少
      • 灵活度低。一旦确定了代理关系就很难进行更改

    实例

    问题描述:对于明星,某一个地方想请明星去开演唱会唱歌,它的请求会先被明星的经纪人收到,得到经纪人的同意后,才会让明星去那个地方开演唱会,演唱结束后再由经纪人安排到其它地方进行延长

    问题分析:上诉问题中的明星(Star)就可以看作是一个Subject,而经纪人(Broker)就可以看作是应该Proxy,它负责帮助明星处理日常的事物,而地方就是Client(也就是下文中的Test类)

    image-20221117201623170

    创建抽象主题
    创建真实主题
    创建代理主题
    编写测试类
    • Step1:创建抽象主题

      package example1;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 
       * @description 所有明星的抽象类
       */
      public interface Star {
      
          /**
           * 唱歌
           * @param name 歌名
           */
          void sing(String name);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • Step2:创建真实主题

      package example1;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title
       * @description 具体的某一个明星
       */
      public class StarImp implements Star{
          private String name;
      
          public StarImp() {
              this.name = "薛之谦";
          }
      
          /**
           * 唱歌
           * @param name 歌名
           */
          @Override
          public void sing(String name) {
              System.out.println(this.name + "正在唱" + name);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • Step3:创建代理主题

      package example1;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 经纪人
       * @description 明星的静态代理类
       */
      public class Broker implements Star{
      
          //委托对象
          private Star target;
      
          public Broker() {
              this.target = new StarImp();
          }
      
          /**
           * 唱歌方法的代理方法
           * @param name 歌名
           */
          @Override
          public void sing(String name) {
              System.out.println("经纪人同意明星去演唱");
              target.sing(name);
              System.out.println("演唱结束,经纪人安排明星前往另一个地方开演唱会");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
    • Step4:编写测试类

      package example1;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 测试类
       * @description 用于测试静态代理
       */
      public class Test {
          public static void main(String[] args) {
              Star broker = new Broker();
              broker.sing("天后");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      测试结果:

      image-20221117201801060

    🍀动态代理

    在动态代理中,代理类和被代理类之间的关系是在运行阶段确定的,常见的动态代理有JDK代理和CGLIB代理

    • JDK代理基于接口进行动态代理,使用反射技术

      JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,只能对该类所实现接口中定义的方法进行代理,同时代理类需要实现目标类的接口(基于这种特性,可以趣称JDK代理方式为”拜把子模式“),否则报java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to com.hhxy.proxy.Calculator异常,这在实际编程中有一定的局限性,而且使用反射的效率也不高,生成的代理类在com.sun.proxy包下,类名为$proxy数字

    • CGLib代理基于类进行动态代理,使用字节码技术

      比使用反射的效率要高,但CGLib不能对final修饰的方法进行代理,因为CGLib原理是动态生成被代理类的子类,生成的代理类和目标类在同一包下且为被代理类的子类(基于这种特性,可以趣称CGLib代理方式为“认干爹模式”)

    • 两者的比较:JDK代理是使用反射实现,效率相对较低,但是由于它是Java封装好了的,使用起来相对简单,但是只能对该类所实现接口中定义的方法进行代理;而CGLib是使用字节码技术,效率相对高很多,并且几乎可以代理任意的方法(除了final修饰的方法),但是实现起来较为复杂

    🐳JDK代理

    JDK1.3开始支持JDK动态代理

    示例

    接着以静态代理的示例来实现

    创建抽象主题
    创建具体主题
    创建代理主题
    编写测试类
    • Step1:创建抽象主题

      和静态代理的代码一致,详情请参考Github或Gitee仓库,略……

    • Step2:创建具体主题

      和静态代理的代码一致,详情请参考Github或Gitee仓库,略……

    • Step3:创建代理主题

      package example2.jdk;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 经纪人
       * @description 明星的代理类
       */
      public class Broker{
      
          //委托对象
          private Star target;
      
          public Broker() {
              this.target = new StarImp();
          }
      
          /**
           * 获取代理对象
           */
          public Star getProxy(){
              /**
               * 参数一: loader表示目标类的类加载器
               * 参数二: interfaces表示对象实现的所有接口对象的Class对象的数组
               *  参数三: h表示匿名内部类对象,它是JDK动态代理类实现的核心代码
               */
              return (Star) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                      new InvocationHandler() {
                          /**
                           * 唱歌方法的代理方法
                           * @param proxy 代理类对象,即Broker对象
                           * @param method 要执行的代理方法,即StarImp的sing方法
                           * @param args args为代理方法的参数列表,即StarImp的sing方法的所有参数
                           * @return
                           */
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              System.out.println("经纪人同意明星去演唱");
                              Object result = method.invoke(target, args);
                              System.out.println("演唱结束,经纪人安排明星前往另一个地方开演唱会");
                              return result;
                          }
                      });
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
    • Step4:编写测试类

      package example2.jdk;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 测试类
       * @description 用于测试JDK动态代理
       */
      public class Test {
          public static void main(String[] args) {
              Broker broker = new Broker();
              Star proxy = broker.getProxy();
              proxy.sing("丑八怪");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      测试结果:

      image-20221117220711785

    🐳CGLib代理

    Spring在5.X之前默认的动态代理实现一直是JDK动态代理。但是从5.X开始,Spring就开始默认使用CGLib来作为动态代理实现。并且SpringBoot从2.X开始也转向了CGLib动态代理实现

    什么导致了Spring体系整体转投CGLib呢,JDK动态代理又有什么缺点呢?

    CGLib是一个开源项目,它的底层是字节码处理框架ASM,CGLib提供了比JDK更为强大的动态代理。主要相比JDK动态代理的优势有:

    1. CGLib动态代理的应用范围更广。JDK动态代理只能基于接口,是“拜把子模式”,只能对该类所实现接口中定义的方法进行代理,得到的代理对象只能赋值给接口变量;CGLib动态代理是基于类,是“认干爹模式”,CGLib是通过生成子类来实现的,代理对象既可以赋值给实现类变量,又可以赋值给接口变量

    2. CGLib的性能更高。JDK底层使用反射技术(通过解释执行),而CGLib底层是使用字节码技术(通过编译执行),,解释执行的代码要比编译执行的代码要慢

      关于反射可以参考这篇文章:一文带你快速了解Java中的反射机制

    示例

    接着以静态代理的示例来实现

    创建抽象主题
    创建具体主题
    创建代理主题
    编写测试类
    导包
    • Step1:导包

      • 方式一:CGLib所依赖的Jar包如下所示,使用Maven导包

                
                <dependency>
                    <groupId>cglibgroupId>
                    <artifactId>cglib-nodepartifactId>
                    <version>3.3.0version>
                dependency>
                <dependency>
                    <groupId>cglibgroupId>
                    <artifactId>cglibartifactId>
                    <version>3.3.0version>
                dependency>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
      • 方式二:直接下载jar包

        image-20221117231908536

    • Step2:创建抽象主题

      和静态代理的代码一致,详情请参考Github或Gitee仓库,略……

    • Step3:创建具体主题

      和静态代理的代码一致,详情请参考Github或Gitee仓库,略……

    • Step4:创建代理主题

      package example2.cglib;
      
      import net.sf.cglib.proxy.Enhancer;
      import net.sf.cglib.proxy.MethodInterceptor;
      import net.sf.cglib.proxy.MethodProxy;
      
      import java.lang.reflect.Method;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 经纪人
       * @description 明星的代理类
       */
      public class Broker implements MethodInterceptor {
      
          //字节码增强器对象
          private Enhancer enhancer;
      
          //委托对象
          private Star target;
      
          public Broker() {
              this.target = new StarImp();
              this.enhancer = new Enhancer();
          }
      
          public Star getProxy(){
              //将被代理的类设置为父类
              enhancer.setSuperclass(StarImp.class);
              //设置拦截器
              /*
              当前这个Broker类本质是一个拦截器,在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,
              来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口
               */
              enhancer.setCallback(this);
              //返回代理类对象
              return (Star) enhancer.create();
          }
      
          /**
           * 唱歌方法的代理方法
           * @param o 被代理对象,即StarImp对象
           * @param method 被代理的方法,即StarImp的sing方法
           * @param objects 被代理的方法的参数列表,即StarImp的sing方法的所有参数
           * @param methodProxy 代理对象
           * @return
           */
          @Override
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
              System.out.println("经纪人同意明星去演唱");
              Object result = methodProxy.invokeSuper(o, objects);
              System.out.println("演唱结束,经纪人安排明星前往另一个地方开演唱会");
              return result;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
    • Step5:编写测试类

      package example2.cglib;
      
      /**
       * @author ghp
       * @date 2022/11/17
       * @title 测试类
       * @description 用于测试JDK动态代理
       */
      public class Test {
          public static void main(String[] args) {
              Broker broker = new Broker();
              Star proxy = broker.getProxy();
              proxy.sing("天外来物");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      测试结果:

      image-20221117232145437

    🍀拓展

    先占坑,未完待续……相信未来的我一定会完成下面这一部分的内容的😈

    🐳虚拟代理
    🐳远程代理
    🐳访问代理
    🐳缓冲代理
    🐳智能引用代理
    🐳Javassist动态代理

    Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。相对于BCEL、ASM等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

    在日常使用中,javassit通常被用来动态修改字节码。它也能用来实现动态代理的功能。

    详情请参考:动态代理大揭秘,带你彻底弄清楚动态代理!

    🐳ByteBuddy动态代理

    ByteBuddy(字节码伙计),是一个大名鼎鼎的开源库,和Cglib一样,也是基于ASM实现。还有一个名气更大的库叫Mockito,相信不少人用过这玩意写过测试用例,其核心就是基于ByteBuddy来实现的,可以动态生成mock类,非常方便。另外ByteBuddy另外一个大的应用就是java agent,其主要作用就是在class被加载之前对其拦截,插入自己的代码。

    ByteBuddy非常强大,是一个神器。可以应用在很多场景。

    详情请参考:动态代理大揭秘,带你彻底弄清楚动态代理!

    🌲总结

    • 相关设计模式
      • Adapter 模式:Adapter模式适配了两种具有不同接口(API)的对象,以使它们可以一同工作。而在Proxy模式中,Proxy角色与RealSubject角色的接口(API)是相同的(透明性)
      • Decorator 模式:Decorator模式与Proxy模式在实现上很相似,不过它们的使用目的不同。Decorator模式的目的在于增加新的功能。而在Proxy模式中,与增加新功能相比,它更注重通过设置代理人的方式来减轻本人的工作负担

    自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O


    上一篇

    下一篇

    参考文章

    在次致谢🌹🌹🌹

  • 相关阅读:
    互联网智慧工地源码,“互联网+建筑大数据”SaaS微服务架构,支持PC端、手机端、数据大屏端
    【Java 进阶篇】手把手教你创建 Bootstrap 旅游网站
    Vue.js 循环语句
    laravel自定义日志保存文件加上日期
    java-php-net-python-校园后勤计算机毕业设计程序
    JavaScript大作业 基于HTML+CSS+JavaScript站酷静态页面官网7页
    phpqrcode生成二维码
    MYSQL逻辑架构与存储引擎
    ISP 基础知识储备
    Python学习笔记第四十三天(NumPy 算术函数)
  • 原文地址:https://blog.csdn.net/qq_66345100/article/details/127914192