十三、代理模式

文章目录

  • [1 基本介绍](#1 基本介绍)
  • [2 案例](#2 案例)
    • [2.1 Sortable 接口](#2.1 Sortable 接口)
    • [2.2 BubbleSort 类](#2.2 BubbleSort 类)
    • [2.3 SortTimer 类](#2.3 SortTimer 类)
    • [2.4 Client 类](#2.4 Client 类)
    • [2.5 Client 类的运行结果](#2.5 Client 类的运行结果)
    • [2.6 总结](#2.6 总结)
  • [3 各角色之间的关系](#3 各角色之间的关系)
    • [3.1 角色](#3.1 角色)
      • [3.1.1 Subject ( 主体 )](#3.1.1 Subject ( 主体 ))
      • [3.1.2 RealObject ( 目标对象 )](#3.1.2 RealObject ( 目标对象 ))
      • [3.1.3 Proxy ( 代理 )](#3.1.3 Proxy ( 代理 ))
      • [3.1.4 Client ( 客户端 )](#3.1.4 Client ( 客户端 ))
    • [3.2 类图](#3.2 类图)
  • [4 动态代理的使用](#4 动态代理的使用)
    • [4.1 JDK 动态代理](#4.1 JDK 动态代理)
      • [4.1.1 实现动态代理的步骤](#4.1.1 实现动态代理的步骤)
      • [4.1.2 代码演示](#4.1.2 代码演示)
    • [4.2 CGLIB 动态代理](#4.2 CGLIB 动态代理)
      • [4.2.1 实现动态代理的步骤](#4.2.1 实现动态代理的步骤)
      • [4.2.2 代码演示](#4.2.2 代码演示)
  • [5 注意事项](#5 注意事项)
  • [6 优缺点](#6 优缺点)
  • [7 适用场景](#7 适用场景)
  • [8 总结](#8 总结)

1 基本介绍

代理模式 (Proxy Pattern)是一种 结构型 设计模式,它在 不改变原有对象结构 的基础上,通过为其提供代理,来 增强对象的功能

代理模式分为 静态代理动态代理 ,本文着重讲解 静态代理 ,对于 动态代理 ,由于其实现非常复杂,所以本文只介绍如何使用 动态代理

2 案例

本案例使用 静态代理 实现了对冒泡排序进行计时的功能。

2.1 Sortable 接口

java 复制代码
public interface Sortable { // 排序接口,实现它之后就能对 int 数组进行排序
    void sort(int[] nums); // 对 int 数组进行升序排序
}

2.2 BubbleSort 类

java 复制代码
public class BubbleSort implements Sortable { // 冒泡排序
    @Override
    public void sort(int[] nums) {
        for (int i = nums.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (nums[j] > nums[j + 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                }
            }
        }
    }
}

2.3 SortTimer 类

java 复制代码
public class SortTimer implements Sortable { // 排序计时器
    private Sortable sortable;

    public SortTimer(Sortable sortable) {
        this.sortable = sortable;
    }

    @Override
    public void sort(int[] nums) {
        long start = System.currentTimeMillis(); // 记录排序的起始时间

        sortable.sort(nums);

        long end = System.currentTimeMillis(); // 记录排序的终止时间
        System.out.println("排序花费的时间为 " + (end - start) + " ms");
    }
}

2.4 Client 类

java 复制代码
import java.util.Random;

public class Client { // 客户端,测试了冒泡排序的计时功能
    public static void main(String[] args) {
        Sortable sortable = new SortTimer(new BubbleSort());
        sortable.sort(generateRandomArray(10000));
    }

    // 生成随机的 int 数组,长度为 length,用于测试排序
    private static int[] generateRandomArray(int length) {
        int[] arr = new int[length];
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            arr[i] = random.nextInt(100);
        }
        return arr;
    }
}

2.5 Client 类的运行结果

运行结果大概为 47 毫秒,这与电脑的性能也有一定的关系。

排序花费的时间为 47 ms

2.6 总结

在没有修改 BubbleSort 类的 sort() 方法的代码的前提下,通过代理类 SortTimer,实现了对 sort() 的功能增强------增加计时功能。唯一不好的一点是需要自己实现代理类,比较麻烦。

3 各角色之间的关系

3.1 角色

3.1.1 Subject ( 主体 )

该角色负责 定义 RealObject 应该具有的 方法 ,Proxy 也实现 Subject 中定义的方法,从而 使 RealObject 和 Proxy 具有一致性 。本案例中,Sortable 接口扮演该角色。

3.1.2 RealObject ( 目标对象 )

该角色负责 实现 Subject 定义的 方法 ,具备基础的功能。本案例中,BubbleSort 类扮演该角色。

3.1.3 Proxy ( 代理 )

该角色负责 使用内部聚合的 RealObject 对象实现 Subject 定义的 方法 ,并 对 RealObject 对的功能作一定的增强 。本案例中,SortTimer 类扮演该角色。

3.1.4 Client ( 客户端 )

该角色负责 调用 Subject 定义的方法完成业务 。本案例中,Client 类扮演该角色。

3.2 类图

说明:虽然图中没有指出 Client 使用了 Proxy 和 RealObject,但实际上 Client 只在创建对象时使用了它们。

4 动态代理的使用

在 Java 中,动态代理主要有两种实现方式:

  • JDK 动态代理 :通过 实现 目标对象实现的 接口 来生成代理类,当目标对象没有实现的接口时,这种方法无法使用。
  • CGLIB 动态代理 :通过 继承 目标对象的类来生成代理类,不需要目标对象实现接口。

这两种方式都是通过 反射 的机制在 运行时 创建具体的代理类,不需要像静态代理那样硬编码,只不过会延缓应用程序的启动。

4.1 JDK 动态代理

JDK 动态代理是 Java 原生支持 的代理方式,要求目标类必须实现至少一个接口 ,其核心是 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。

4.1.1 实现动态代理的步骤

  1. 定义一个(或多个)接口 和 其实现类(目标对象的类)。
  2. 创建一个实现了 InvocationHandler 接口的 处理器类,用于处理代理对象上的方法调用。
  3. 使用 Proxy.newProxyInstance() 方法 创建代理对象 ,该方法需要三个参数:类加载器实现的接口列表InvocationHandler 对象实例

4.1.2 代码演示

以上面为冒泡排序计时为例,Sortable 接口、BubbleSort 类不变,只需要修改 SortTimer 类、Client 类的代码,测试的结果没有明显变化。

SortTimer 类:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 排序计时器,实现了 InvocationHandler 接口的处理器类
public class SortTimer implements InvocationHandler {
    private Sortable sortable;

    public SortTimer(Sortable sortable) {
        this.sortable = sortable;
    }

    // 获取 Sortable 的代理对象
    public static Sortable getProxy(Sortable sortable) {
        return (Sortable) Proxy.newProxyInstance(
                sortable.getClass().getClassLoader(), // 获取 sortable 的类加载器
                sortable.getClass().getInterfaces(), // 获取 sortable 实现的接口
                // 创建一个新的 SortTimer 对象,要求这个对象的类实现 InvocationHandler 接口
                new SortTimer(sortable)
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis(); // 记录排序的起始时间

        Object res = method.invoke(sortable, args);

        long end = System.currentTimeMillis(); // 记录排序的终止时间
        System.out.println("排序花费的时间为 " + (end - start) + " ms");

        return res;
    }
}

Client 类:

java 复制代码
import java.util.Random;

public class Client { // 客户端,测试了冒泡排序的计时功能
    public static void main(String[] args) {
        Sortable sortable = SortTimer.getProxy(new BubbleSort());
        sortable.sort(generateRandomArray(10000));
    }

    // 生成随机的 int 数组,长度为 length,用于测试排序
    private static int[] generateRandomArray(int length) {
        int[] arr = new int[length];
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            arr[i] = random.nextInt(100);
        }
        return arr;
    }
}

4.2 CGLIB 动态代理

CGLIB 动态代理是 Java 中另一种 强大 的代理技术,允许在不实现接口的情况下对类进行代理 。CGLIB 通过 继承目标对象的类 来创建代理对象,也就是说,通过这种方式创建的代理对象的类 是 目标对象的类 的 子类。它适用于那些没有实现接口的类。

4.2.1 实现动态代理的步骤

  1. 添加 CGLIB 依赖

    xml 复制代码
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
  2. 创建一个实现了 MethodInterceptor 接口的 拦截器类

  3. 使用 Enhancer 来生成代理对象,需要设置 要代理类拦截器对象实例

4.2.2 代码演示

以上面为冒泡排序计时为例,BubbleSort 类可以不实现 Sortable 接口,删除 Sortable 接口,修改 SortTimer 类、Client 类的代码,测试的结果没有明显变化。

注意:最好不要在 JDK 17 的环境下运行如下的代码,会报错,推荐使用 JDK 8。

BubbleSort 类:

java 复制代码
public class BubbleSort { // 冒泡排序
    public void sort(int[] nums) {
        for (int i = nums.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (nums[j] > nums[j + 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                }
            }
        }
    }
}

SortTimer 类:

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 排序计时器,实现了 MethodInterceptor 接口的拦截器类
public class SortTimer implements MethodInterceptor {
    private BubbleSort bubbleSort;

    public SortTimer(BubbleSort bubbleSort) {
        this.bubbleSort = bubbleSort;
    }

    // 获取 BubbleSort 的代理对象
    public static BubbleSort getProxy(BubbleSort bubbleSort) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(bubbleSort.getClass()); // 设置要代理的类
        enhancer.setCallback(new SortTimer(bubbleSort)); // 设置拦截器对象实例
        return (BubbleSort) enhancer.create(); // 创建代理对象
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long start = System.currentTimeMillis(); // 记录排序的起始时间

        Object res = method.invoke(bubbleSort, args);

        long end = System.currentTimeMillis(); // 记录排序的终止时间
        System.out.println("排序花费的时间为 " + (end - start) + " ms");

        return res;
    }
}

Client 类:

java 复制代码
public class Client { // 客户端,测试了冒泡排序的计时功能
    public static void main(String[] args) {
        BubbleSort bubbleSort = SortTimer.getProxy(new BubbleSort());
        bubbleSort.sort(generateRandomArray(10000));
    }

    // 生成随机的 int 数组,长度为 length,用于测试排序
    private static int[] generateRandomArray(int length) {
        int[] arr = new int[length];
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            arr[i] = random.nextInt(100);
        }
        return arr;
    }
}

5 注意事项

  • 代理对象与目标对象的接口一致性 :代理对象应该与目标对象 实现相同的接口(仅限 静态代理 和 Java 代理),以便在代理对象中可以透明地替换目标对象,客户端代码无需修改即可使用代理对象。
  • 代理行为的合理性 :在创建代理对象之前,需要明确代理的目的,例如是为了 延迟加载安全控制日志记录 ,还是其他目的,代理对象中的代理逻辑应该 合理且高效,避免不必要的复杂性和性能开销。
  • 代理对象的管理:需要妥善管理代理对象的生命周期,确保代理对象在适当的时候被创建和销毁。
  • 静态代理与动态代理的选择
    • 静态代理 :如果代理类 在编译时就已经确定 ,且 不需要频繁更换代理逻辑,可以选择静态代理。
    • 动态代理 :如果代理类 在运行时才能确定 ,或者 需要频繁更换代理逻辑,可以选择动态代理。
  • 避免过度使用 :虽然代理模式可以带来很多好处,但也需要避免过度使用。过度使用代理模式可能会增加系统的 复杂性 和维护成本。

6 优缺点

优点

  • 降低系统耦合度 :代理模式可以在 客户端目标对象 之间起到一个 中介 的作用,客户端 不直接访问 目标对象,而是通过代理对象来 间接访问,这样可以降低系统的耦合度。
  • 增强系统扩展性 :当需要在目标对象上添加新的功能时,可以通过 修改代理类 来实现,而不需要修改目标对象,这提高了系统的可扩展性。
  • 保护目标对象 :代理对象可以 控制对目标对象的访问 ,比如 限制访问权限实现延迟加载(只在需要使用目标对象时创建,其他情况只使用代理对象自身的功能)等,从而保护目标对象不被过度使用或错误使用。
  • 实现远程代理 :在 分布式系统 中,远程代理可以 隐藏远程对象的存在细节,使得客户端可以像调用本地对象一样调用远程对象,简化了分布式系统的复杂性。

缺点

  • 增加系统复杂度:引入代理模式后,系统中会增加代理类,这可能会增加系统的复杂度,特别是当系统中存在大量代理类时,会使得系统难以理解和维护。
  • 请求处理速度可能变慢 :由于客户端的请求需要 先经过代理对象才能到达目标对象 ,因此代理模式可能会 降低请求的处理速度,特别是当代理对象需要执行额外的处理逻辑时。
  • 过度使用代理:如果过度使用代理模式,可能会使得系统变得复杂且难以理解。在设计时需要根据实际需求权衡是否使用代理模式。
  • 代理类的编写和维护:编写和维护代理类需要一定的时间和精力,特别是在目标对象接口频繁变化的情况下,代理类也需要进行相应的修改。

7 适用场景

  • 远程代理 :当 对象位于远程服务器上 时,可以使用代理模式来进行 远程访问。代理对象可以隐藏实际对象的细节,客户端通过代理对象来访问远程对象,而无需了解远程对象的实现细节。
  • 虚拟代理 :当 创建一个对象 需要很长时间 或 消耗大量资源 时,可以使用代理模式来 延迟对象的创建。代理对象会尽量满足各种方法调用,当需要时才真正创建真正的对象。
  • 安全代理 :当 需要控制对对象的访问权限 时,可以使用代理模式。代理对象可以控制客户端对真实对象的访问权限。
  • 缓存代理 :当 需要缓存对象 时,可以使用代理模式。代理对象可以在获取真实对象之前首先检查缓存中是否存在对象,如果存在则直接返回缓存对象,降低数据库的访问压力,提高系统的响应速度。
  • 日志记录代理 :当 需要对对象的方法调用进行日志记录 时,可以使用代理模式。代理对象可以在调用真实对象的方法之前或之后记录日志。

8 总结

代理模式 是一种 结构型 设计模式,在 不改变原有对象结构 的基础上,通过为其 提供代理 ,来 增强对象的功能 ,如虚拟代理、安全代理、日志记录代理等功能。使用代理模式可以 降低系统耦合度增强系统扩展性 。但是在使用 代理模式 时需要注意不要过度使用,如果过度使用,则会降低系统的响应速度。

代理模式 共有两种:

  • 静态代理 :提前在源文件中写代理类(硬编码),实现起来比较简单。
  • 动态代理 :不需要提前写代理类,而是在运行程序时通过 反射 动态生成代理类,实现起来比较复杂,但可以使用现有的技术:
    • JDK 动态代理 :使用位于 java.lang.reflect 包中的 Proxy 类 和 InvocationHandler 接口。
    • CGLIB 动态代理 :添加 CGLIB 的依赖,使用其内部的 MethodInterceptor 接口 和 Enhancer 类。
相关推荐
向宇it15 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行17 分钟前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate
星河梦瑾1 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富1 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想1 小时前
JMeter 使用详解
java·jmeter
言、雲1 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
TT哇1 小时前
【数据结构练习题】链表与LinkedList
java·数据结构·链表
Yvemil72 小时前
《开启微服务之旅:Spring Boot 从入门到实践》(三)
java
Anna。。2 小时前
Java入门2-idea 第五章:IO流(java.io包中)
java·开发语言·intellij-idea
思忖小下2 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit