设计模式_结构型模式 -《代理模式》

news2024/11/20 9:39:19

设计模式_结构型模式 -《代理模式》

笔记整理自 黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下 7 种:

  • 代理模式
  • 适配器模式
  • 装饰器模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

概述

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

Java 中的代理按照代理类生成时机不同又分为静态代理和动态代理。

  • 静态代理代理类在编译期就生成
  • 而动态代理代理类则是在 Java 运行时动态生成。动态代理又有:
    • JDK 代理
    • CGLib 代理

结构

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

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

静态代理

我们通过案例来感受一下静态代理。

【例】火车站卖票

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

代码如下:

  • 抽象主题类-卖票接口

    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方法参数说明:
                         * Object proxy:代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用
                         * Method method:对应于在代理对象上调用的接口方法的 Method 实例
                         * Object[] 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) {
            // 获取代理对象
            // 1.创建代理工厂对象
            ProxyFactory factory = new ProxyFactory();
            // 2.使用factory对象的方法获取代理对象
            SellTickets proxyObject = factory.getProxyObject();
            // 3.调用卖票的方法
            proxyObject.sell();
        }
    }
    

    输出

    代理点收取一定的服务费用(JDK动态代理方式)
    火车站卖票
    

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

  • 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;
    
    // jdk动态生成的类,实现了SellTickets接口
    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) {
            // 构造方法传入的InvocationHandler(也就是我们自己new的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是我们需要注意的
                // 通过全类名加载了接口 再去拿到接口中的同名方法 将这个Method对象赋值给m3
                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());
            }
        }
    
        // 省略了equals、toString、hashCode方法...
    
        // 卖票方法
        public final void sell() {
            try {
                // 调用h.invoke 其实就是调用我们自己实现的InvocationHandler中的invoke方法
                /*
                 * this:当前对象(代理类$Proxy0)
                 * m3:要执行的方法
                 * null:方法没有参数,所以为null
                 */
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    
    // 父类Proxy
    public class Proxy implements java.io.Serializable {
        
        // InvocationHandler属性 声明为h
        protected InvocationHandler h;
    }
    

    从上面的类中,我们可以看到以下几个信息:

    • 代理类($Proxy0)实现了 SellTickets 接口。这也就印证了我们之前说的真实类和代理类实现同样的接口。
    • 代理类($Proxy0)将我们提供了的匿名内部类对象 InvocationHandler 传递给了父类。
  • 动态代理的执行流程是什么样?

    下面是摘取的重点代码:

    // 程序运行过程中动态生成的代理类
    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();
            // 设置父类的字节码对象(CGLIB的代理类属于目标类的子类)所以这里直接用TrainStation的字节码对象
            enhancer.setSuperclass(target.getClass());
            // 设置回调函数,参数是MethodInterceptor子实现类的对象,使代理工厂实现这个接口,
            // 所以这里传入this 该接口的子实现类对象即可。
            enhancer.setCallback(this);
            // 创建代理对象
            TrainStation obj = (TrainStation) enhancer.create();
            return obj;
        }
    
        /*
         * intercept方法参数说明:
         * o:代理对象
         * method:真实对象中的方法的Method实例
         * args:实际参数
         * methodProxy:代理对象中的方法的method实例
         */
        // 通过代理对象调用需要执行的方法,就会调用intercept方法,这个就是上面的回调函数,设置的是方法所属类的对象this。
        public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
            // 调用目标对象的方法
            //Object obj = method.invoke(station, objects);
            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();
        }
    }
    

    输出

    代理点收取一些的服务费用(CGLIB动态代理方式)
    火车站卖票
    

    cglib动态代理拦截器中使用MethodProxy#invokeSuper和invoke的区别

三种代理的对比

  • 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/163038.html

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

相关文章

Docker 架构和安装

Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。 容器&#xff08;Con…

<Linux> Linux项目自动化构建工具—makemakefile的使用

< Linux> Linux 项目自动化构建工具—make/makefile的使用 文章目录< Linux> Linux 项目自动化构建工具—make/makefile的使用一、make/makefile的背景二、如何编写 makefile1.依赖关系和依赖方法2.makefile的使用3.clean的使用4.多文件编译5.伪目标 .PHONY6.三个时…

无约束优化——线性搜索法

无约束优化——线性搜索法&#xff08;line search&#xff09;前言概述构建关于步长的函数Wolfe条件Strong Wolfe条件Zoutendijk条件后记前言 该系列为学习笔记系列&#xff0c;所有内容可以在 Numerical Optimization (2nd Edition) 中找到&#xff0c;该书十分有用经典建议…

组件封装 - Tabs组件

首先我们先来看一下 Element UI 里面的 Tabs 是怎么样的 <template><el-tabs v-model"activeName" class"demo-tabs" tab-click"handleClick"><el-tab-pane label"User" name"first">User</el-tab-pa…

基于matlab的汽车牌照识别程序详细教程

设计一个基于matlab的汽车牌照识别程序&#xff0c;能够实现车牌图像预处理&#xff0c;车牌定位&#xff0c;字符分割&#xff0c;然后通过神经网络对车牌进行字符识别&#xff0c;最终从一幅图像中提取车牌中的字母和数字&#xff0c;给出文本形式的车牌号码。关键词&#xf…

C语言 一个特殊的数组【柔性数组】

文章目录前言柔性数组的特点柔性数组的使用柔性数组的优势写在最后前言 也许你从来就没有听过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但他是真的存在&#xff1b;柔性数组的概念存在于C99标准当中&#xff0c;C99标准表示&#xff1a;结构体的最后…

Linux-进程概念

目录 进程状态&#xff1a; 操作系统简图 调用进程bin.exe的详细过程 cpu运行队列的结构 R进程和阻塞进程 进程状态&#xff1a;挂起&#xff1a; Linux操作&#xff1a; ​编辑 R运行状态 S休眠状态 T暂停状态&#xff1a; kill kill -18 表示继续 kill -9 杀死进程…

肿瘤内微生物群在癌症转移中的新作用

谷禾健康 癌症是一种复杂的疾病&#xff0c;归因于多因素变化&#xff0c;导致治疗策略困难。 90%的癌症患者死于复发或转移。癌症转移是恶性肿瘤进展的关键步骤&#xff0c;由癌细胞内在特性和外在环境因素决定。 一些微生物组通过诱导癌性上皮细胞和慢性炎症促进癌发生、癌症…

光耦合器:类型及其应用

光耦合器&#xff1a;类型及其应用 介绍 光耦合器&#xff08;也称为光隔离器&#xff09;是一种在两个隔离电路之间传输电信号的半导体器件。光耦合器由两部分组成&#xff1a;发射红外光的LED和检测LED光的光敏器件。这两个部件都装在一个带有连接销的黑匣子中。输入电路接收…

数据可视化笔记记录(二)pandas

笔记内容是根据B站上面的一位老师的视频而记录了&#xff0c;因为我也还在学习&#xff0c;所以个人理解可能会出现错误&#xff0c;若有错误请指出。另外这篇文章会很长 B站视频连接、 numpy,matplotlib的学习记录 pandas 学习记录 SerIes结构&#xff0c;一图胜千言 Seri…

实用工具:FastDoc 文档提取工具

实用工具&#xff1a;FastDoc 文档提取工具 简单、实用的 HTTP API 文档生成工具&#xff0c;支持 Spring MVC/Spring Boot 下的控制器信息提取&#xff0c;类似 Swagger 但稍有不同的机制。 在线演示地址在 https://framework.ajaxjs.com/demo/fast-doc/。 关于研发者这工具…

二三层转发原理

二层转发原理 数据帧 二层即数据链路层的转发是以数据帧的格式进行转发&#xff0c;数据帧的格式如下&#xff1a; 目的地址(Destination Address,DA) &#xff1a;可以是单独的地址,或者是广播或组播MAC地址。 源地址(Source Address,SA) &#xff1a;用来识别发送没备,在S…

听说你想用开发者工具调试我的网站?挺可以的啊。25

本篇博客重点为大家介绍&#xff0c;如何禁止用户在浏览器中查看源码&#xff0c;禁用开发者工具调试等前端需求 案例已更新到 爬虫训练场 文章目录禁用右键&#xff0c;禁用 F12禁用 ctrl U 查看源代码&#xff0c;禁用 ctrl shift i 打开开发者工具实现开发者工具无限 deb…

arcgis使用Python脚本进行批量截图

arcgis使用Python脚本进行批量截图 介绍 最近公司数据部那边有个需求&#xff0c;需要结合矢量数据和影像数据&#xff0c;进行批量截图&#xff0c;并且截图中只能有一个图斑&#xff0c;还要添加上相应的水印。 思路 一开始我是准备使用QGIS直接进行批量出图&#xff0c;…

Ant Design在处理业务表单中的一些实践

文章目录前言表单处理实践Modal 清空旧数据使用Form.create和getFieldDecorator对Form进行包装表单控件是switch自定义表单控件Table列表SelectshowSearch 用于在选择框中显示搜索框其他需要注意的组件Tabs其它前言 目前&#xff0c;很多中小企业前端实现采用了react&#xff…

二、总线控制

文章目录一、引子二、总线判优控制1.基本概念2.方式&#xff08;1&#xff09;集中式①链式查询方式②计数器定时查询方式③独立请求方式&#xff08;2&#xff09;分布式三、总线通信控制1.基本概念2.方式&#xff08;1&#xff09;同步通信①介绍②同步式数据输入③同步数据输…

数据结构与算法学习推荐

推荐&#xff1a;官网地址&#xff1a;http://localhost:8000/algorithm/github&#xff1a;https://github.com/paiDaXing-web/You-Dont-Know-Algorithm 点star 大话搜索搜索一般指在有限的状态空间中进行枚举&#xff0c;通过穷尽所有的可能来找到符合条件的解或者解的个数。…

佳能C5235彩色激光复印机复印有底灰

故障描述&#xff1a; 佳能C5235彩色激光复印机&#xff0c;复印的时候有不同颜色的底灰问题&#xff1b; 检测分析&#xff1a; 打印不同纯色的纸张进行测试发现每张纸的上面都有不同颜色的底灰&#xff1b; 拆机进行检测吧&#xff0c;先打开前盖&#xff0c;拧下左右两边的螺…

CMake基础教程

CMake最大优势是跨平台&#xff0c;可以根据不同的平台生成Makefile文件&#xff0c;看下CMake的基础使用。 cmake&#xff08;单目录单文件&#xff09; 第一个最简单的cmake构建。 demo1.cpp代码&#xff1a; #include <iostream>using namespace std;int main(int…

【基于机械臂触觉伺服的物体操控研究】UR5e动力学建模及代码实现

我的毕设题目定为《基于机械臂触觉伺服的物体操控研究》&#xff0c;这个系列主要用于记录做毕设的过程。 前言&#xff1a;UR系列是优傲公司的代表产品&#xff0c;也是目前比较通用的产品级机械臂。所以我打算用该机械臂进行毕设的仿真实现。关于其动力学建模&#xff0c;网…