设计模式——结构型模式——代理模式(静态代理、动态代理:JDK、CGLIB)

news2024/11/28 2:37:14

目录

代理模式

代理模式简介

代理模式的分类

代理模式组成

代理模式的优缺点

静态代理

背景前置

编写代码

JDK动态代理

编写代码

使用Arthas分析JDK动态代理底层原理

CGLIB动态代理

编写代码

三种代理的对比

 代理模式使用场景

代理模式

代理模式简介

        代理模式属于结构型模式。指一个对象本身不做实际的操作,而是通过其他对象来获取自己想要的结果。       

        产生背景:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

        意义:目标对象只需要关注自己的实现细节,通过代理来实现功能的增强,可以扩展目标对象的功能。同时体现了非常重要的变成模式,不能随便修改目标对象的源码,如果需要修改目标对象的源码对已有功能进行增强,此时可以通过修改代理的方式实现功能的扩展。

        例子:如果某人需要租房,此时可以借助于房屋中介或租赁公司,此时房屋中介或租赁公司相当于代理,可以让租房人找到适合自己的房屋。

代理模式的分类

        静态代理:静态代理就是在程序运行之前,代理类字节码.class就已编译好,通常一个静态代理类也只代理一个目标类,代理类和目标类都实现相同的接口。

        动态代理:动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。

代理模式组成

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

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的优缺点

        优点:保护、扩展、降低耦合度。

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

  • 代理对象可以扩展目标对象的功能;

  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

        缺点:增加系统的复杂性。

静态代理

背景前置

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

编写代码

        如上面背景所示,我们需要定义①抽象主题类:买票接口类。②真实主题类:代理的目标类(火车站)。③代理类:代售点,以实现对真实主题类功能的增强。

/**
 * 声明抽象类、定义公共方法
 */
public interface SallTicket {
    void sale();
}


/**
* 火车站类,实现买票接口
*/
public class TrainStation implements SallTicket{
    @Override
    public void sale() {
        System.out.println("火车站正在卖票...");
    }
}


/**
* 代理类:与目标类实现相同的接口,以达到对目标类方法的增强
*/

public class SallProxy implements SallTicket{
    private TrainStation trainStation;
    public SallProxy(TrainStation trainStation){
        this.trainStation = trainStation;
    }
    @Override
    public void sale() {
        System.out.println("代售点收取少量服务费...");
        trainStation.sale();
    }
}

/**
 * 编写客户端测试类
 */
public class Client {
    public static void main(String[] args) {
        SallProxy sallProxy = new SallProxy(new TrainStation());
        sallProxy.sale();
    }
}

接口、目标类、代理类之间的关系图:

  • 接口类SallTicket:定义了卖票的抽象公共方法sale()。
  • 目标类TrainStation:实现了接口类SallTicket,并重写方法sale()。
  • 代理类SallProxy:实现接口类SallTicket,且在方法内部传递了接口类的实现类也即目标类TrainStation。在代理类内部实现了原有卖票方法sale()的功能的增强,并且调用目标类TrainStation中的sale()方法达到了卖票的功能。
  • 测试类Client:构建代理类,并实现功能的验证。

JDK动态代理

编写代码

        JDK动态代理和静态代理不同的地方在于代理类的编写不同,因此接口类、目标类仍然沿用静态代理中创建的,编写JDK动态代理程序结构如下:

        其中代理类(ProxyFactory)和测试类(Client)代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * 获取代理对象的工厂类
 * 代理类也实现了对应的接口
 */
public class ProxyFactory {

    // 声明目标对象
    private TrainStation station = new TrainStation();

    // 获取代理对象的方法
    public SallTicket getProxyObject(){
        // 返回代理对象、代理对象也实现了目标接口
        /**
         * ClassLoader loader, 类加载器,用于加载代理类,可以通过目标对象获取类加载器
         * Class<?>[] interfaces, 代理类实现的接口的字节码对象
         * InvocationHandler h, 代理对象的调用处理程序
         */
        SallTicket proxyObject = (SallTicket) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(), // 目标对象和代理类实现了相同的接口,可以通过目标对象获取代理类实现的接口的字节码对象
                new InvocationHandler() {   // 匿名内部类
                    /**
                     * @param proxy 代理对象,就是proxyObject
                     * @param method 对接口中的方法进行封装的method对象
                     * @param args 调用方法的实际参数
                     * @return  返回值就是方法的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 对目标对象进行方法的增强
                        System.out.println("代售点收取一定的服务费用(JDK)...");
                        // 执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}


/**
 * 客户端,测试JDK动态代理对方法的增强
 */
public class Client {
    public static void main(String[] args) {
        // 创建代理对象工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 使用proxyFactory对象的方法获取代理对象
        SallTicket proxyObject = proxyFactory.getProxyObject();
        // 调用目标对象的方法
        proxyObject.sale();
    }
}

       接口、目标类、代理类之间的关系图: 

        上图中的ProxyFactory类并不是真正的JDK动态代理类,其本质上是一个代理工厂,通过Proxy类的静态方法newProxyInstance(...),传入目标类的字节码,代理类实现的接口,invocationHandler接口的实现内部类等参数在程序运行的时候动态的创建代理类,并且借助反射获取代理对象的方法,并且实现对目标方法的增强。

使用Arthas分析JDK动态代理底层原理

        在测试类Client中添加以下代码用于获取代理类的Class对象。

        随后借助于阿里开源工具Arthas,使用反编译命令jad com.sun.proxy.$Proxy0获取已加载到 JVM 中的类的源代码如下:

package com.sun.proxy;

import com.itheima.proxy.dynamic.jdk.SellTickets;
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 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)将我们提供了的匿名内部类对象传递给了父类。

        可以分析代理类执行流程如下:

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

CGLIB动态代理

编写代码

        CGLIB动态代理和静态代理不同的地方在于代理类的编写不同,并且不需要接口类,因此目标类仍然沿用静态代理中创建的,编写CGLIB动态代理程序结构如下:

        CGLIB是第三方提供的包,编写代码之前需要先引入jar包。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

/**
 * 代理工厂
 */
public class ProxyFactory implements MethodInterceptor {
    private TrainStation station = new TrainStation();

    // 获取代理对象
    public TrainStation getProxyObject(){
        // 创建Enhancer对象,类似于JDK动态代理的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperclass(station.getClass());
        // 设置回调函数,传递的对象,是MethodInterceptor的子实现类对象
        enhancer.setCallback(this);
        // 创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

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


/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        // 创建代理对象工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 获取代理对象
        TrainStation proxyObject = proxyFactory.getProxyObject();
        proxyObject.sale();
    }
}

         接口、目标类、代理类之间的关系图:

        简单原理:通过自定义实现拦截器接口(MethodInterceptor)的类【也就是目标类】,并重写intercept()用于拦截增强被代理类的方法【类似于JDK动态代理中的invoke()方法】。通过Enhancer 类的 create()创建简单的代理类。

        CGLIB采用非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术的拦截所有父类的方法调用,顺势织入横切逻辑。(CGLIB在字节码的基础上,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

三种代理的对比

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

  • JDK动态代理只能代理实现了接口的类,而CGLIB可以代理未实现任何接口的类。
  • JDK动态代理是实现了被代理对象所实现的接口CGLIB是继承了被代理对象。
  • JDK和CGLIB都是在运行期生成字节码,JDK是直接写Class字节码,CGLIB是使用ASM框架写Class字节码。
  • CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  • 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

 代理模式使用场景

        此部分内容暂时不是很熟悉,等以后学习到再慢慢补充。

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1668600.html

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

相关文章

测试萌新三天速通python基础(二)列表,字符串,元组,字典,遍历,容器,集合,函数

python基础 字符串下标(索引)切片字符串的替换 replace()字符串拆分 split()字符串的连接 join列表 list列表的增删改查列表的反转 reverse()排序列表嵌套元组 tuple 排序 升序降序交换变量字典 dict查询遍历容器集合函数参数函数的嵌套调⽤函数的返回值模块导⼊的⽅法____name…

重发布和路由策略实验(课堂练习)

需求&#xff1a; 将1.1.1.0/24网段&#xff08;不在OSPF中&#xff09;重发布到网络中&#xff0c;不允许出现次优路径&#xff0c;实现全网可达。 需求分析&#xff1a; 1、在R1上重发布1.1.1.0/24网段&#xff0c;但是需要过滤192.168.12.0/24和192.168.13.0/24 2、在R2和R3…

【论文阅读笔记】MapReduce: Simplified Data Processing on Large Clusters

文章目录 1 概念2 编程模型3 实现3.1 MapReduce执行流程3.2 master数据结构3.3 容错机制3.3.1 worker故障3.3.2 master故障3.3.3 出现故障时的语义 3.4 存储位置3.5 任务粒度3.6 备用任务 4 扩展技巧4.1 分区函数4.2 顺序保证4.3 Combiner函数4.4 输入和输出的类型4.5 副作用4.…

【超详细】跑通YOLOv8之深度学习环境配置2

环境配置2下载安装内容如下&#xff1a; CUDA&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive cudnn&#xff1a;https://developer.nvidia.com/rdp/cudnn-archive 版本&#xff1a;CUDA11.3 cudnn8.9.7 CUDA安装 简介 CUDA&#xff08;Compute Unified De…

【superset】基于MySQL的BI数据分析可视化实战案例(已更新)

1.熟悉、梳理、总结下superset可视化分析实战案例知识体系,一直想探索有效可用的可视化分析方案,大多收费或不好用,这里,借此机会总结、更新下。 2.复杂度高,遇到并解决的问题较多,尝试了很多次。 3.欢迎批评指正,跪谢一键三连! 基于MySQL的BI数据分析可视化实战案例文…

免费剪辑的素材资源网站,超高清、可商用、不限速、无版权,迅速有效的解决您的视频剪辑难题!

在数字媒体时代&#xff0c;高质量的剪辑素材已成为视频制作的核心资源。下面为您推荐的优质视频剪辑素材网站&#xff0c;都提供超高清、无限速、无版权、可商用的素材&#xff0c;这些网站将大大提升您的视频制作效率和质量 01. 蛙学府 实用性&#xff1a;★★★★☆ 丰富性&…

容器化Jenkins远程发布java应用(方式一:pipline+ssh)

1.创建pipline工程 2.准备工程Jenkinsfile文件&#xff08;java目录&#xff09; 1.文件脚本内容 env.fileName "planetflix-app.jar" env.configName "planetflix_prod" env.remoteDirectory "/data/project/java" env.sourceFile "/…

百度云防护如何开启CC攻击防护

百度云防护的最重要的功能是可以CC攻击防护&#xff0c;针对CC攻击&#xff0c;百度云防护有被动的CC攻击拦截规则&#xff0c;也有主动自定义访问策略拦截。 今天百度云来教大家如何开启百度云防护的CC攻击防御功能。 1.进入防护模板功能-创建模板 2.开启CC攻击防御功能&…

Windows单机部署RocketMQ5.X并与SpringBoot集成

RocketMQ 5.X下载 《RocketMQ 5.X下载》 下载后&#xff0c;解压缩 修改初始内存 修改runserver.cmd&#xff08;Linux则为runserver.sh&#xff09; 将下面的-Xms2g -Xmx2g -Xmn1g调小 if %JAVA_MAJOR_VERSION% lss 17 (set "JAVA_OPT%JAVA_OPT% -server -Xms2g -X…

Apache反代理Tomcat项目,分离应用服务器和WEB服务器

项目的原理是使用单独的机器做应用服务器&#xff0c;再用单独的机器做WEB服务器&#xff0c;从网络需要访问我们的应用的话&#xff0c;就会先经过我们的WEB服务器&#xff0c;再到达应用程序&#xff0c;这样子的好处是我们可以保护应用程序的机器位置&#xff0c;同时还可以…

长安汽车:基于云器 Lakehouse 的车联网大数据平台建设

近年来随着智能汽车行业的迅速发展&#xff0c;数据也在呈爆炸式增长。长安大数据平台承接了长安在生产上大部分流量应用及日常生产业务应用。本文将分享长安汽车在车联网场景下大数据平台建设面临的一些挑战和具体落地的实践。 主要内容如下&#xff1a; 1. 背景介绍 2. 长…

渲染农场多少钱一个小时?

​很多第一次准备使用渲染农场的小伙伴不知道渲染农场多少钱一个小时&#xff0c;今天就给大家介绍一下渲染农场多少钱一小时。 现在渲染农场基本都有CPU渲染和GPU渲染&#xff0c;各渲染农场的服务器配置不同&#xff0c;收费也各不相同&#xff0c;不过都是按渲染时长收费&…

C语言学习(八)typedef 虚拟内存 malloc/free

目录 一、typedef 类型重定义&#xff08;一&#xff09;使用&#xff08;二&#xff09;define和typedef的区别1. 编译处理的阶段不同2. 功能不同 二、虚拟内存&#xff08;一&#xff09;虚拟内存分布&#xff08;二&#xff09;内存分布1. 静态分配2. 动态分配 三、malloc/f…

如何使用canvas在图片上进行标注,以下代码不起作用,着实被坑到了(文末附完整代码)

今天发现一个有意思的问题&#xff1a; 如何使用canvas在图片上进行如下的标注&#xff0c;以下代码不起作用,如何修改 原始代码如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name&quo…

理解进程的基本概念

1. 什么是进程 进程&#xff0c;即执行中的程序 进程 程序执行 在计算机中&#xff0c;每一个运行的exe程序&#xff0c;就是一个进程 2.为什么会有进程 早期&#xff0c;操作系统只有一个程序&#xff0c;这样效率是很低的。为了提高CPU的利用率&#xff0c;人们将多个程…

时间复杂度的简单讲解

小伙伴们大家好&#xff0c;我们又见面了&#xff0c;这次我们直接进入正题 时间复杂度的概念 时间复杂度的定义&#xff1a;在计算机科学中&#xff0c; 算法的时间复杂度是一个函数 &#xff0c;它定量描述了该算法的运行时间。一 个算法执行所耗费的时间&#xff0c;从理论…

Hadoop-未授权访问-内置配合命令执行RCE

一、Hadoop常见端口及配置文件 Hadoop中有多种端口&#xff0c;这些端口用于不同的服务和通信。以下是Hadoop中常见的端口以及它们的用途&#xff1a; NameNode Web界面端口 (默认: 9870)NameNode 对客户端服务端口 (默认: 8020)Secondary NameNode Web界面端口 (默认: 9868)…

每日OJ题_贪心算法四⑤_力扣354. 俄罗斯套娃信封问题

目录 力扣354. 俄罗斯套娃信封问题 解析代码1_动态规划&#xff08;超时&#xff09; 解析代码2_重写排序贪心二分 力扣354. 俄罗斯套娃信封问题 354. 俄罗斯套娃信封问题 难度 困难 给你一个二维整数数组 envelopes &#xff0c;其中 envelopes[i] [wi, hi] &#xff0…

物联网杀虫灯—新型的环保杀虫设备

型号推荐&#xff1a;云境天合TH-FD2S】物联网杀虫灯是一种新型环保杀虫设备&#xff0c;其中风吸式太阳能杀虫灯作为其一种特殊类型&#xff0c;展现了独特的工作原理和优势。 风吸式太阳能杀虫灯以太阳能电池板为电源&#xff0c;白天储存电源&#xff0c;晚上为杀虫灯提供电…

资产公物仓管理系统|实现国有资产智能化管理

1、项目背景 资产公物仓管理系统&#xff08;智仓库DW-S201&#xff09;是一套成熟系统&#xff0c;依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 项目设计原则 方案对公物仓资…