设计模式 -第1部分 避免浪费- 第2章 Proxy 模式 - 只在必要时生成

437 阅读11分钟

第1部分 避免浪费-Proxy 模式

注:其内容主要来自于【日】-结城浩 著《图解设计模式》20章节 极力推荐大家阅读原著

博客中的所有部分图均来自书中,同时也对作者表示深深的敬重

往期篇章:

1️⃣ 第1章 Flyweight 模式 - 共享对象避免浪费

第2章 Proxy 模式 - 只在必要时生成

image-20230526002019933

2.1 Proxy 模式

Proxy 是“代理人”的意思,这里也就是代理别人进行工作的人。当不一定需要本人亲力亲为的事的时候,那么此时就可以交由代理人去完成。但代理毕竟归根结底其实也只是一个代理,而并非本人。所以,当遇到代理人无法处理的事情的时候就需要本人亲自去解决。

举一个生活中常见的例子:

比如说,某位明星可能需要进行一场巡游演唱会,而在举行演唱会需要场地的选用,现场的台幕搭建和一些琐碎的对接工作。那么这些工作是不需要明星亲力亲为的,只需要将精力放到唱歌上既可,因此每一位明星都有专属的经纪人,而这里的经纪人则会负责这些琐事。但最终的现场表演经纪人是无法完成的,需要明星亲自完成。

那么在这整个的过程当中,明星,经纪人所充当的角色分别为 “代理对象” 与 ”代理人“。

而在我们的面向对象编程当中,“代理对象” 和 “代理人” 两者都是对象。如果 ”本人“ 太忙,有些工作无法完成,就需要将其交给 ”代理人“ 对象负责。

image-20230526111728407

其实这样的例子在生活中并不少见,比如顾客选购海外商品,就需要中间商代理者去到海外进行代购,于是海外代购这种行业也由此产生。

2.2 做了别人的嫁衣

上面简单概述了一个代理模式的基本作用,这里我们借用程杰老师在《大话设计模式》中 7.1 章节开篇提到的内容进一步加深对其代理的理解。下图中的内容节选自 7.1 做别人的嫁衣!

image-20230529160906626

image-20230529160921329

image-20230529160939041

image-20230529160957414

哈哈,故事似乎有点。。。。回到设计模式的概念上来,那么如何设计该模式呢?

image-20230529161800674

这种的设计理念,其实就相当于追求者与被追最求者是有直接联系的,如果按照上面的案例,就表示追求者与被追求者之间是相互认识的,但实际的情况是他们并没有直接的联系。所以需要对上面的 UML 图进行修改。

image-20230529162842186

伪代码

interface GiveGift(){
    void giveDolls();
    void giveFlowers();
    void giveChocolate();
}
//追求者
class Pursuit interface GiveGift {
    String name;
    void giveDolls(){
        System.out.println("送礼物");
    }
    void giveFlowers(){
        System.out.println("送鲜花");        
    }
    void giveChocolate(){
        System.out.println("送巧克力");        
    }
}
//代理者
class Proxy interface GiveGift{
    Pursuit gg;
    Proxy(String name){
        this.gg.setName(name);
    }
    void giveDolls(){
        gg.giveDolls();
    }
    void giveFlowers(){
         gg.giveFlowers();  
    }
    void giveChocolate(){
         gg.giveChocolate();  
    }
}
    //客户端
    class Client{
        String name="娇娇";
        
        Proxy proxy=new Proxy(name);
        
        proxy.giveDolls();
        proxy.giveFlowers();  
        proxy.giveChocolate();
    }
    

从上面的案例中可以看到,客户端中定义了代理者,而代理者去完成相应的事件。

2.3 代理人提升执行效率

其实在一个Proxy系统当中,Proxy 的角色往往扮演者肩负使命的重要责任。怎么理解这句话呢?我们看一段关于日本的一位IT大佬 结城浩《图解设计模式》中的一段案例。

如下图所示是节选自 21-1 章的 UML 结构图,从图中可以看到,代理模式当中有三大重要的角色。分别是

  • Subject(主体)

    定义了使用 Proxy角色和 RealSubject 角色之间关联具有一致性的接口。所以对于 Client 就无需关注自己使用的角色,是 Proxy 角色还是 RealSubject 角色,在下图中 printable 扮演此角色。

  • Proxy(代理人)

    Proxy 会尽量的去处理来自 Client 角色的请求。如果出现无法处理的,才会交付于 RealSubject 角色。Proxy 只会在必要的时才会生成 RealSubject 角色。由于 Proxy 角色是实现于 Subject 角色,所以对于 Subject 的方法都会给予实现。在下图当中 PrintableProxy 充当此角色。

  • RealSubject(实际主体)

    该角色也就是被代理者“本人”,也就是当代理者出现 Proxy 角色无法胜任的工作时,RealSubject 角色就会出场。该角色于 Proxy 角色是一样的,同样的继承于 Subject 角色,实现了 Subject 角色中的所有的方法。在下图中 Printer 充当该角色。

  • Client(请求者)

    使用 Proxy 的角色,也就是最终的调用执行者。而 Client 客户端并不属于 Proxy 模式的,只是充当了一个程序的入口而已,在下图中有 Main 充当。

这四个角色分别对应下图中的 priintable

image-20230529180504027

image-20230529180635521

接下来就会上面 UML 图中的各个角色的类展开详述,大家感兴趣的可以购买此书( 【日】结城浩《图解设计模式》)阅读。

image-20230529182652237

image-20230529182711210

image-20230529182734948

image-20230529182751665

image-20230529182836069

image-20230529182905329

image-20230529182923436

image-20230529182934783

附录:

    package org.peggy.example01;
    ​
    /**
     * @Projectname: designPatterns
     * @Filename: Printable
     * @Author: peggy
     * @Data:2023/5/29 18:31
     * @Description: 功能主体
     */
    public interface Printable {
        void setPrinterName(String name); //设计名字
        String getPrinterName(); //显示名字
        void print(String string); //显示文字 (打印输出)
    }
    ​
<!---->

    package org.peggy.example01;
    ​
    /**
     * @Projectname: designPatterns
     * @Filename: PrinterProxy
     * @Author: peggy
     * @Data:2023/5/29 18:34
     * @Description: RealSubject 实际主体
     */public class Printer implements Printable {
    ​
        String name;
    ​
        public Printer(String name) {
            heavyJob("正在生成 Printer 的实例,(" + name + ")");
        }
    ​
        @Override
        public void setPrinterName(String name) {
            this.name = name;
        }
    ​
        @Override
        public String getPrinterName() {
            return this.name;
        }
    ​
        @Override
        public void print(String string) {
            System.out.println("====" + name + "====");
            System.out.println(string);
        }
    ​
        private void heavyJob(String msg) {        //重活
            System.out.println(msg);
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(5 * 1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.print(". ");
            }
            System.out.println("结束");
        }
    }
<!---->

    package org.peggy.example01;
    ​
    /**
     * @Projectname: designPatterns
     * @Filename: PrinterProxy
     * @Author: peggy
     * @Data:2023/5/29 18:41
     * @Description: 代理者
     */public class PrinterProxy implements Printable {
        private String name;    //名字
        private Printer real;   //"本人" 被代理的对象
    ​
        @Override
        public void setPrinterName(String name) {
            if (real != null) {
                real.setPrinterName(name);
            }
            this.name = name;
        }
    ​
        @Override
        public String getPrinterName() {
            realized();
            return real.name;
        }
    ​
        @Override
        public void print(String string) {
            realized();
            real.print(string);
        }
    ​
        private synchronized void realized() {
            if (real == null) {
                real = new Printer(name);
            }
        }
    }
<!---->

    package org.peggy.example01;
    ​
    /**
     * @Projectname: designPatterns
     * @Filename: Main
     * @Author: peggy
     * @Data:2023/5/29 18:46
     * @Description: 客户端类
     */public class Main {
        public static void main(String[] args) {
            Printable p = new PrinterProxy();
            System.out.println("现在的名字是:" + p.getPrinterName());
            p.setPrinterName("Bob");
            System.out.println("现在的名字是:" + p.getPrinterName());
            p.print("Hello World");
        }
    }

image-20230529185312866

其实在上面的整个案例当中。Proxy角色在整个代理模式系统中充当者十分重要的角色,通过 Proxy 角色在上述的案例当中,将 print 打印输出的方法提前到了其生成实例之前执行。

案例当中的生成实例的过程耗时并不是特别的长,所以这里如果自己体验可能感受并不会特别的明显。如果将其放到一个大型系统的初始化当中,存在大量的消耗时间的处理,那么这个过程就会整体拉低系统的执行效率,程序启动的时间将会变得非常的缓慢,而我们如果只在需要某一个功能的时候,通过代理类去实现初始化,就可以解决这样的一类问题。

2.4 其他的代理

JDK动态代理

静态代理与动态代理的区别

  • 静态代理

    静态代理是指再编译时就已经实现,编译完成后代理类是一个 class 文件

  • 动态代理

    动态代理是指在程序运行的时候动态生成,既就是再编译完成之后不会生产动态代理的类的 class 文件,而是在运行时动态类生成字节码文件加载到 JVM 当中

image-20230522200229950

开启事务 保存数据 提交事务

类是如何生成的

Java 虚拟机当中类的加载主要分为以下五个阶段:

加载、验证、准备、解析、初始化

其中对于加载阶段需要完成三件事情:

  1. 通过类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流代表的静态存储结构转化为方法区运行时的数据结构
  3. 在内存当中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类所有的数据库访问入口

image-20230522201742783

运行时计算生成: 这种的场景多用于动态代理技术,在 java.lang.refelct.Proxy 类中,就是用了 ProxyGenerator.generate

ProxyClass 来为特定接口生成形式为 *$Proxy 的代理类二进字节流

image-20230522203027010

所以动态加载其实就是,根据接口和目标对象,计算出代理类的字节码然后加载到 JVM 当中使用

使用阿里巴巴下的 arthas 工具查看中间代理的类生成信息

image-20230522212525464转存失败,建议直接上传图片文件

/*
     * Decompiled with CFR.
     *
     * Could not load the following classes:
     *  com.peggy.service.IUserDao
     */
    package com.sun.proxy;
    ​
    import com.peggy.service.IUserDao;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    ​
    public final class $Proxy0
    extends Proxy
    implements IUserDao {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    ​
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    ​
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    ​
        public final void save() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    ​
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    ​
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    ​
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("com.peggy.service.IUserDao").getMethod("save", new Class[0]);
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    }
  • 动态代理类对象 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
  • 代理类的构造函数,参数是 InvocationHandler 实例,Proxy.newInstance 方法就是通过这个构造函数来创建代理实例的
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 this.h.invoke(this, m3, null)); 实际上h.invoke就是在调用ProxyFactory中我们重写的invoke方法
<!---->

    @Override
    public Object invoke(Object proxy, Method method, Object[]args) throws Throwable {
        System.out.println("开启事务");
        //执行目标对象方法
        method.invoke(target, args);
        System.out.println("提交事务");
        return null;
    }

cglib动态代理

动态代理执行架构图

image-20230523151228651

Cglib 的动态代理的执行大致流程与 JDK 动态代理的基本一致。

三种代理模式实现的方式:

  • Jdk代理和Cglib代理

    使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理与静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

代理模式使用的场景

  • 功能增强:

    当需要对一个对象访问提供一些额外操作的时候,可以使用代理模式

  • 远程代理

    实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

  • 防火墙代理

    当你浏览器配置设置为代理模式的时候,防火墙就将你的浏览器的请求转发给互联网,当互联网返回响应的时候,代理服务器再将响应的信息返回给你的浏览器

  • 保护代理

    当一个用户访问对象的时候,可以根据用户不同类型的用户提供不同级别的使用权限。

以上就是关于 Proxy 模式的学习记录了,关于博文中的案例请移至到此处查阅。

设计模式-designMode

图解设计模式