设计模式(六):结构型之代理模式

news/2024/7/6 6:33:40 标签: 代理模式, 设计模式, java

设计模式系列文章

设计模式(一):创建型之单例模式

设计模式(二、三):创建型之工厂方法和抽象工厂模式

设计模式(四):创建型之原型模式

设计模式(五):创建型之建造者模式

设计模式(六):结构型之代理模式


目录

  • 一、设计模式分类
  • 二、代理模式
    • 1、概述
    • 2、结构
  • 三、静态代理
  • 四、JDK动态代理
  • 五、CGLIB动态代理
  • 六、三种代理的对比


一、设计模式分类

  • 创建型模式
    • 用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”
    • 提供了单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式
  • 结构型模式
    • 用于描述如何将类或对象按某种布局组成更大的结构
    • 提供了代理、适配器、桥接、装饰、外观、享元、组合 7 种结构型模式
  • 行为型模式
    • 用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责
    • 提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式

二、代理模式

1、概述

  • Java中的代理按照代理类生成时机不同又分为静态代理动态代理
    • 静态代理代理类在编译期就生成
    • 而动态代理代理类则是在Java运行时动态生成
  • 动态代理又有JDK代理CGLib代理两种

2、结构

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能

三、静态代理

  • 如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦
  • 而火车站在多个地方都有代售点,我们去代售点买票就方便很多了
  • 这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象

类图如下:

在这里插入图片描述

代码如下:

java">//卖票接口
public interface SellTickets {
    void sell();
}

//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代售点
public class ProxyPoint implements SellTickets {

    private TrainStation station = new TrainStation();

    public void sell() {
        System.out.println("代理点收取一些服务费用");
        station.sell();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        ProxyPoint pp = new ProxyPoint();
        pp.sell();
    }
}
  • 从上面代码中可以看出测试类直接访问的是ProxyPoint类对象
  • 也就是说ProxyPoint作为访问对象和目标对象的中介
  • 同时也对sell方法进行了增强(代理点收取一些服务费用)

四、JDK动态代理

  • Java中提供了一个动态代理类Proxy
  • Proxy并不是我们上述所说的代理对象的类
  • 而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象

代码如下:

java">//卖票接口
public interface SellTickets {
    void sell();
}

//火车站  火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代理工厂,用来创建代理对象
public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject() {
        //使用Proxy获取代理对象
        /*
          newProxyInstance()方法参数说明:
              ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
              Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
              InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                     InvocationHandler中invoke方法参数说明:
                        proxy : 代理对象
                        method : 对应于在代理对象上调用的接口方法的 Method 实例
                        args : 代理对象调用接口方法时传递的实际参数
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                        //执行真实对象
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();
        
        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

使用了动态代理,我们思考下面问题:

  • ProxyFactory是代理类吗?
    • ProxyFactory不是代理模式中所说的代理类
    • 而代理类是程序在运行过程中动态的在内存中生成的类
    • 通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:
java">public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", 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());
        }
    }

    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 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);
        }
    }

    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}
  • 从上面的类中,我们可以看到以下几个信息:
    • 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口
    • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类

下面是摘取的重点代码:

  • new Class[0]经常用来告诉反射的getMethod函数找到一个无参数的函数
java">//程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
    }

    public final void sell() {
        this.h.invoke(this, m3, null);
    }
}

//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
	protected InvocationHandler h;
	 
	protected Proxy(InvocationHandler h) {
        this.h = h;
    }
}

//代理工厂类
public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject() {
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}


//测试访问类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();
        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

执行流程如下:

  • 在测试类中通过代理对象调用sell()方法
  • 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
  • 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
  • invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

五、CGLIB动态代理

  • 如果没有定义SellTickets接口,只定义了TrainStation(火车站类)
  • 很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理
  • CGLIB是一个功能强大,高性能的代码生成包
  • 它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充
  • CGLIB是第三方提供的包,所以需要引入jar包的坐标:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

代码如下:

java">//火车站
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

//代理工厂
public class ProxyFactory implements MethodInterceptor {

    private TrainStation target = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer =new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;
    }

    /*
        intercept方法参数说明:
            o : 代理对象
            method : 真实对象中的方法的Method实例
            args : 实际参数
            methodProxy :代理对象中的方法的method实例
     */
    @Override
    public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
        TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
        return result;
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();

        proxyObject.sell();
    }
}

六、三种代理的对比

  • 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)
    • 这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转
    • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法

http://www.niftyadmin.cn/n/385450.html

相关文章

如何设计一份优秀的 PPT 文档?

设计一份优秀的 PPT 需要考虑以下几个方面&#xff1a; 目标和受众&#xff1a;明确你的 PPT 的目标是什么&#xff0c;以及你的受众是谁。这将帮助你确定内容的重点和风格。结构和流程&#xff1a;确定清晰的结构和流程&#xff0c;使你的 PPT 有逻辑性。使用标题、副标题和分…

【反转再反转】186. 反转字符串中的单词 II

文章目录 一、题目1、题目描述2、基础框架3、原题链接 二、解题报告1、思路分析2、时间复杂度3、代码详解 三、本题小知识 一、题目 1、题目描述 给你一个字符数组 s &#xff0c;反转其中 单词 的顺序。 单词 的定义为&#xff1a;单词是一个由非空格字符组成的序列。s 中的…

6.S081——陷阱部分(内核陷阱)——xv6源码完全解析系列(6)

0.briefly speaking 点此跳转到上一篇博客 在上一篇博客中&#xff0c;我们通过系统调用这个重要的机制了解了Xv6操作系统中用户态陷阱的处理全流程。这篇博客则准备研究一下内核陷阱的处理流程&#xff0c;在研究内核陷阱流程中一个麻烦的家伙是定时器中断&#xff0c;首先它…

Redis中的整数集合(IntSet)

Redis节省内存的两个优秀设计思想&#xff1a;一个是使用连续的内存空间&#xff0c;避免内存碎片开销&#xff1b;二个是针对不同长度的数据&#xff0c;采用不同大小的元数据&#xff0c;以避免使用统一大小的元数据&#xff0c;造成内存空间的浪费。IntSet便具备以上两个设计…

架构师成长的关键能力

从架构师的角色定义开始&#xff0c;而后引出架构师最核心的价值创造&#xff0c;也就是设计和实施结构化软件系统的能力。 在维持同质这个核心价值上&#xff0c;具有不同能力维度的不同架构师角色的领地是什么。 首先是程序员 / 代码的结构性&#xff0c;这是软件结构性的最…

GLTF/GLB模型轻量化简明教程

GLB 文件格式很方便&#xff0c;因为它包含渲染所需的所有文件&#xff0c;包括纹理。 但是&#xff0c;根据用途&#xff0c;你可能希望简化文件&#xff0c;因为它有时非常详细。 在本文中&#xff0c;我将使用 gltf-transform 来执行简化&#xff0c;并且假设你使用的是 Wi…

固态硬盘购买记录

众所周知SSD和U盘作为都是我们最为常用的非易失性存储器&#xff08;non-volatile memory&#xff0c;即NVM&#xff09;&#xff0c;并非数据绝对不会丢失&#xff0c;只是不易丢失。 而且相对于传统的机械硬盘数据保存时间也是低了很多&#xff0c;不过对于网传的固态硬盘一年…

动态规划.背包问题--填满背包的最大价格(java)

背包问题--填满背包的最大价格 题目描述暴力递归解题思路代码演示 动态规划解题思路代码演示 动态规划专题 题目描述 背包问题 给定两个长度都为N的数组weights和values&#xff0c;weights[i]和values[i]分别代表 i号物品的重量和价值 给定一个正数bag&#xff0c;表示一个载重…