「设计模式」代理模式

news2024/9/22 17:31:32

「设计模式」代理模式

文章目录

  • 「设计模式」代理模式
    • 一、概述
    • 二、结构
    • 三、静态代理
    • 四、 JDK动态代理
    • 五、CGLIB动态代理
    • 六、三种代理的对比
      • 优缺点
      • 使用场景

一、概述

代理模式是一种应用很广泛的结构型设计模式,而且变化很多。在代理模式中引入了一个新的代理对象,代理对象可以在客户端对象和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。

代理模式定义如下:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式是一种对象结构型模式。

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


二、结构

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。代理模式结构如图所示:

image-20230111213010139

从图中可以看出,在代理模式结构图中包含以下 3 个角色:

  • **Subject(抽象主题角色):**它声明了真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
  • **Poxy(代理主题角色):**代理主题角色内部包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
  • **RealSubject(真实主题角色):**它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

三、静态代理

【例】火车站卖票

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

image-20230111213833266

代码如下:

//卖票接口
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动态代理

接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

代码如下:

//卖票接口
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【阿尔萨斯】)查看代理类的结构:

    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)将我们提供了的匿名内部类对象传递给了父类。
  • 动态代理的执行流程是什么样?

    下面是摘取的重点代码:

    //程序运行过程中动态生成的代理类
    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();
        }
    }
    

执行流程如下:

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

五、CGLIB动态代理

同样是上面的案例,我们再次使用CGLIB代理实现。

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

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

代码如下:

//火车站
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实例
     */
    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)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题


优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度

缺点:

  • 增加了系统的复杂度;

使用场景

  • 远程(Remote)代理

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

  • 防火墙(Firewall)代理

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

  • 保护(Protect or Access)代理

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

参考

设计模式之代理模式

黑马程序员

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

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

相关文章

面试攻略,Java 基础面试 100 问(十一)

抽象类&#xff08;abstract class&#xff09;和接口&#xff08;interface&#xff09;有什么异同? 抽象类和接口都不能够实例化&#xff0c;但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现&#xff…

JavaEE-HTTP协议完结认识HTTPS协议

目录回顾构造HTTP请求(客户端)基于 HTML和JS基于 form表单基于 ajax基于 Java基于socket认识HTTPS加密对称加密非对称加密回顾 让我们回顾一下HTTP协议中的请求和响应 构造HTTP请求(客户端) 基于 HTML和JS 基于 form表单 核心HTML标签&#xff0c;就是form标签 form ( 表单…

RocketMQ5.0.0消息存储<五>_文件过期删除机制

目录 一、概览 二、过期文件删除机制 三、参考资料 一、概览 RocketMQ操作CommitLog、ConsumeQueue文件是基于内存映射机制并在启动时会加载commitlog、consumequeue目录下的所有文件&#xff0c;为了避免内存与磁盘的浪费&#xff0c;不可能将消息永久存储Broker服务器上&a…

2023/02/18 ES6数组的解读

1 扩展运算符 扩展运算符&#xff08;spread&#xff09;是三个点&#xff08;…&#xff09;. 它好比 rest 参数的逆运算&#xff0c;将一个数组转为用逗号分隔的参数序列. console.log(...[1, 2, 3]) // 1 2 3console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5该运算符主要用于…

比较全面的HTTP和TCP网络传输的单工、全双工和半双工

文章目录单工、全双工、半双工1. 单工2. 半双工3. 全双工HTTP协议的工作模式TCP协议的工作模式本文参考&#xff1a; 图解网络传输单工、半双工、全双工 - 知乎 (zhihu.com) 问&#xff1a;HTTP是单工的还是双工的还是半双工的 - 简书 (jianshu.com) 关于TCP全双工模式的解释_忙…

大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——InputFormat数据输入

3.1.1切片与MapTask并行度决定机制 1、问题引出 MapTask的并行度决定Map阶段的任务处理并发度&#xff0c;进而影响到整个Job的处理速度。 思考&#xff1a;1G的数据&#xff0c;启动8个MapTask&#xff0c;可以提高集群的并发处理能力。那么1K的数据&#xff0c;也启动8个M…

华为OD机试 - 机器人走迷宫(JS)

机器人走迷宫 题目 房间有X*Y的方格组成&#xff0c;例如下图为6*4的大小。每一个放个以坐标(x,y)描述。 机器人固定从方格(0,0)出发&#xff0c;只能向东或者向北前进&#xff0c; 出口固定为房间的最东北角&#xff0c;如下图的方格(5,3)。 用例保证机器人可以从入口走到出…

算法:(十四)动态规划

文章目录14.1 单序列问题面试题88&#xff1a;爬楼梯的最少成本面试题89&#xff1a;偷盗房屋面试题90:环形偷盗房屋面试题91&#xff1a;粉刷房子面试题92&#xff1a;反转字符面试题93&#xff1a;最长斐波那契数列面试题94&#xff1a;最少回文分割14.2 双序列问题面试题95&…

2023美国大学生数学建模竞赛A题详细公式和代码分享

目录 2023美赛A题翻译 1.1 建立一个数学模型&#xff0c;预测一个植物群落在各种不规则的天气周期中如何随时间变化。包括本该降水充足的干旱时期。该模型应考虑到干旱周期中不同物种之间的相互作用。 1.2就植物群落与大环境的长期相互作用&#xff0c;探讨你能从你的模型中…

【Python从入门到进阶】8、Python的输入输出

接上篇《7、运算符》 上一篇我们学习了Python的运算符部分&#xff0c;包括算数运算符、赋值运算符、比较运算符、逻辑运算符等。本篇我们来学习Python的输入和输出相关内容。 一、输出 其实输出的含义就是在控制台里打印一些东西&#xff0c;我们在之前已经做过很多的“prin…

社招前端必会手写面试题集锦

查找字符串中出现最多的字符和个数 例: abbcccddddd -> 字符最多的是d&#xff0c;出现了5次 let str "abcabcabcbbccccc"; let num 0; let char ;// 使其按照一定的次序排列 str str.split().sort().join(); // "aaabbbbbcccccccc"// 定义正则表达…

IDEA插件安装慢、超时、不成功问题如何解决?

目录 一、打开国内插件的节点IP地址 二、修改本地hosts文件 三、刷新DNS缓存 一、打开国内插件的节点IP地址 国内插件的节点IP地址查询: http://tool.chinaz.com/speedtest/plugins.jetbrains.com 在下方的检测结果中&#xff0c;找到一个解析时间最短的IP地址&#xff0c;解…

流程引擎之Activiti简介

背景Activiti 是一个开源架构的工作流引擎&#xff0c;基于 bpmn2.0 标准进行流程定义&#xff0c;其前身是 jBPM&#xff0c;Activiti 相对于 jBPM 更轻量&#xff0c;更易上手&#xff0c;且天然集成了 Spring。2010年 jBPM 创始人 Tom Baeyens 离开 JBoss&#xff0c;随之加…

大数据之-Nifi-监控nifi处理数据的状态信息_处理数据的状态栏_组件统计_公告BUG信息---大数据之Nifi工作笔记0010

首先可以看到状态栏,可以提供活动现场的数量,排队统计信息,等等 可以看到在面板的最上面有一行状态栏,就是 就是具体的状态信息 然后组件统计就是具体的处理器的统计信息 可以看到这里pickup这个getfile处理器,可以看到in这里,说了文件的输入个数,以及大小 out是输出个数以及大…

Linux进程学习【二】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Perseverance is not a long race; it is many short races one after another…

java黑马头条 day5自媒体文章审核 敏感词过滤算法DFA 集成RabbitMQ实现自动审核

自动审核流程介绍 做为内容类产品&#xff0c;内容安全非常重要&#xff0c;所以需要进行对自媒体用户发布的文章进行审核以后才能到app端展示给用户。2 WmNews 中status 代表自媒体文章的状态 status字段&#xff1a;0 草稿 1 待审核 2 审核失败 3 人工审核 4 人工审核通过 …

Mac上动态切换JDK

起因&#xff1a; 因为甲骨文的JDK8并没有适配Mac M1芯片&#xff0c;新版的17倒是有适配的&#xff0c;11这些不清楚。往常开发可以使用Zulu这些厂商提供的JDK8。 但是在本地起一些服务&#xff0c;例如Nacos时候&#xff0c;还是会出现不兼容导致起不来&#xff01;虽然Nac…

【网络原理6】数据链路层协议——以太网

数据链路层负责的是相邻两个网络节点之间的数据以帧为单位进行传输。 具体关于数据链路层的介绍&#xff0c;已经在这一篇文章当中提到了。 初识网络&#xff1a;IP、端口、网络协议、TCP-IP五层模型_革凡成圣211的博客-CSDN博客TCP/IP五层协议详解https://blog.csdn.net/weix…

Python每日一练(20230219)

目录 1. 循环随机取数组直到得出指定数字&#xff1f; 2. 旋转链表 3. 区间和的个数 1. 循环随机取数组直到得出指定数字&#xff1f; 举个例子&#xff1a; 随机数字范围&#xff1a;0~100 每组数字量&#xff1a;6&#xff08;s1,s2,s3,s4,s5,s6&#xff09; 第二轮开始随…

高级前端一面面试题集锦

详细说明 Event loop 众所周知 JS 是门非阻塞单线程语言&#xff0c;因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话&#xff0c;我们在多个线程中处理 DOM 就可能会发生问题&#xff08;一个线程中新加节点&#xff0c;另一个线程中删除节点&#…