代理模式(结构型模式)

目录

1、概述

2、结构

2.1、角色分类

2.2、类图

3、静态代理

3.1、案例类图

3.2、案例代码

4、JDK 动态代理

4.1、案例代码

4.2、底层原理

4.3、执行流程说明

5、CGLib 动态代理

5.1、案例代码

6、三种代理的对比

6.1、JDK代理和CGLib代理

6.2、动态代理和静态代理

7、优缺点

7.1、优点

7.2、缺点

8、使用场景

8.1、远程(Remote)代理

8.2、防火墙(Firewall)代理

8.3、保护(Project or Access)代理

1、概述

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

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。

Java 中的代理按照生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则在 Java 运行时动态生成,程序结束时内存会自动释放。动态代理又分为 JDK 动态代理和 CGLib 动态代理。

2、结构

2.1、角色分类

代理(Proxy)模式分为三种角色,分别如下:

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

(2)具体主题(Real Subject)角色:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,也叫做被委托角色、被代理角色。

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

2.2、类图

3、静态代理

静态代理是由程序创建或者特定工具自动生成源代码,在程序运行前,代理类的.class文件已经存在。

案例:

房东出租房屋,将出租和收回房子的事情都交给中介来完成,中介出租房屋后可以赚取到中介费。

3.1、案例类图

3.2、案例代码
/*** @ClassName IHouseOwner* @Description 出租房屋的接口-抽象主题角色* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public interface IHouseOwner {​/*** @Description: 出租房屋* @Author: chengjunyu*/void rentHouse();​/*** @Description: 收回房屋* @Author: chengjunyu*/void takeBackHouse();}​​/*** @ClassName HouseOwner* @Description 房东-具体主题角色* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class HouseOwner implements IHouseOwner {​/*** @Description: 出租房屋* @Author: chengjunyu*/@Overridepublic void rentHouse() {System.out.println("房屋闲置,房东要出租房屋");}​/*** @Description: 收回房屋* @Author: chengjunyu*/@Overridepublic void takeBackHouse() {System.out.println("房屋到期,房东要收回房屋");}}​​/*** @ClassName HouseOwnerProxy* @Description 代理角色* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class HouseOwnerProxy implements IHouseOwner {​private IHouseOwner houseOwner;​public HouseOwnerProxy(IHouseOwner houseOwner) {this.houseOwner = houseOwner;}/*** @Description: 出租房屋* @Author: chengjunyu*/@Overridepublic void rentHouse() {this.houseOwner.rentHouse();System.out.println("帮房东出租了房子,挣取佣金2000元");}​/*** @Description: 收回房屋* @Author: chengjunyu*/@Overridepublic void takeBackHouse() {this.houseOwner.takeBackHouse();System.out.println("帮房东收回了房子");}}​​/*** @ClassName Client* @Description 业务场景* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class Client {public static void main(String[] args) {//创建抽象主题角色IHouseOwner houseOwner = new HouseOwner();//创建代理类,通过代理类来完成具体主题角色需要做的事情HouseOwnerProxy proxy = new HouseOwnerProxy(houseOwner);proxy.rentHouse();proxy.takeBackHouse();}}

执行结果:

房屋闲置,房东要出租房屋

帮房东出租了房子,挣取佣金2000元

房屋到期,房东要收回房屋

帮房东收回了房子

从上面代码中可以看出业务场景直接访问的是 IHouseOwner 类对象,也就是说 IHouseOwner 作为访问对象和目标对象的中介,同时也对 rentHouse() 和 takeBackHouse() 方法做出了增强。

4、JDK 动态代理

JDK 动态代理要求目标对象实现一个接口。

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

4.1、案例代码

在JDK动态代理中,不再需要去手动创建一个代理类来完成目标对象的执行方法。

/*** @ClassName IHouseOwner* @Description 抽象主题角色* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public interface IHouseOwner {/*** @Description: 出租房屋* @Author: chengjunyu*/void rentHouse();​/*** @Description: 收回房屋* @Author: chengjunyu*/void takeBackHouse();}​/*** @ClassName HouseOwner* @Description 房东-具体主题角色* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class HouseOwner implements IHouseOwner {/*** @Description: 出租房屋* @Author: chengjunyu*/@Overridepublic void rentHouse() {System.out.println("房屋闲置,房东要出租房屋");}​/*** @Description: 收回房屋* @Author: chengjunyu*/@Overridepublic void takeBackHouse() {System.out.println("房屋到期,房东要收回房屋");}}​/*** @ClassName ProxyFactory* @Description  获取代理对象的工厂类* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class ProxyFactory {//1、声明目标对象private HouseOwner houseOwner = new HouseOwner();​/*** @Description: 获取代理对象的方法* @Author: chengjunyu*/public IHouseOwner getHouseOwner() {//2、返回代理对象/** ClassLoader loader: 类加载器,用于加载代理类(程序运行中动态的在内存中生成的类),通过目标对象获取* Class<?>[] interfaces:代理类实现的接口的字节码对象* InvocationHandler h:代理对象的调用处理程序*/IHouseOwner proxyObject = (IHouseOwner) Proxy.newProxyInstance(houseOwner.getClass().getClassLoader(),houseOwner.getClass().getInterfaces(),//匿名内部类,重写 invoke 方法new InvocationHandler() {/** Object proxy:代理对象,和 proxyObject 是同一个对象,在 invoke 方法中基本上不使用* Method method:对接口中的方法进行封装的 method 对象* Objects[] args:调用方法的实际参数*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object object =  method.invoke(houseOwner, args);if ("rentHouse".equals(method.getName()))  {System.out.println("帮房东出租了房子,挣取佣金2000元");}if ("takeBackHouse".equals(method.getName())) {System.out.println("帮房东收回了房子");}return object;}});return proxyObject;}}​/*** @ClassName Client* @Description 业务场景* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class Client {public static void main(String[] args) {//获取代理对象//1、创建代理对象工厂ProxyFactory proxyFactory = new ProxyFactory();//2、使用factory对象的方法获取代理对象IHouseOwner houseOwner = proxyFactory.getHouseOwner();//调用租房方法houseOwner.rentHouse();//调用收回房屋方法houseOwner.takeBackHouse();}}

执行结果:

房屋闲置,房东要出租房屋

帮房东出租了房子,挣取佣金2000元

房屋到期,房东要收回房屋

帮房东收回了房子

4.2、底层原理

在学习JDK动态代理的底层原理之前,可以先考虑一个问题:ProxyFactory是代理类吗?

ProxyFactory 不是代理模式中所说的代理类,代理类是程序在运行过程中动态的在内存中生成的类。

我么可以通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构,操作步骤如下:

1、运行指令 java -jar arhtas-boot.jar 启动 Arthas;

2、运行指令 jad com.sun.proxy.$Proxy0 获取该类源码;

3、优化源码,优化后的源码如下:

 
package com.sun.proxy;​import com.design.pattern.proxy.jdkProxy.IHouseOwner;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 IHouseOwner {private static Method m1;private static Method m2;​//构造方法提供了有参构造,参数为 InvocationHandler 对象,赋值给了父类 Proxypublic $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}​static {//获取IHouseOwner字节码对象后获取方法m1 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("rentHouse", new Class[0]);m2 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("takeBackHouse", new Class[0]);}​//调用了 InvocationHandler 的子实现类对象的 invoke 方法public final void rentHouse() {//此处h即为InvocationHandler对象,this表示本类,m1为rentHouse方法this.h.invoke(this, m1, null);}​public final void takeBackHouse() {this.h.invoke(this, m2, null);}}​//父类Proxypublic class Proxy {protected InvocationHandler invocationHandler;}

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

1、代理类($Proxy0)实现了 IHouseOwner ,这也就印证了我们之前说的真实类和代理类实现同样的接口;

2、代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

4.3、执行流程说明

根据JDK动态代理示例代码和底层原理代码,我们可以分析出JDK动态代理的执行流程如下:

1、在业务场景中通过代理对象调用 rentHouse() 和 takeBackHouse();

2、根据多态的特性,执行的是代理类($Proxy0)中的 rentHouse() 和 takeBackHouse();

3、代理类($Proxy0)中的 rentHouse() 方法和 takeBackHouse() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke() 方法;

4、invoke 方法通过反射执行了真实对象所属类(HouseOwner)中的rentHouse() 和 takeBackHouse()。

5、CGLib 动态代理

有时候目标对象只是一个单独的对象,并没有实现接口,这个时候就可以使用CGLIB代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

CGLIB动态代理基于继承来实现代理,所以无法对 final 类、private 和 static 方法实现代理。

5.1、案例代码

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

如果没有定义IHouseOwner接口,只定义了HouseOwner(房屋所有者类),很显然JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

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

CGLIB是第三方提供的包,所以在Spring框架下需要引入jar包的maven坐标。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>3.3.0</version>

</dependency>

代码如下:

 
/*** @ClassName HouseOwner* @Description 房东-具体主题角色* @Author chengjunyu* @Date 2022/12/8* @Version V1.0*/public class HouseOwner {​/*** @Description: 出租房屋* @Author: chengjunyu*/public void rentHouse() {System.out.println("房屋闲置,房东要出租房屋");}​/*** @Description: 收回房屋* @Author: chengjunyu*/public void takeBackHouse() {System.out.println("房屋到期,房东要收回房屋");}}​​/*** @ClassName ProxyFactory* @Description 代理工厂类* @Author chengjunyu* @Date 2022/12/10* @Version V1.0*/public class ProxyFactory implements MethodInterceptor {​private HouseOwner houseOwner = new HouseOwner();​public HouseOwner getProxyObject() {//1、创建Enhancer对象,类似于JDK动态代理中的Proxy类Enhancer enhancer = new Enhancer();//2、设置父类的字节码对象,指定父类enhancer.setSuperclass(HouseOwner.class);//3、设置回调函数enhancer.setCallback(this);//4、创建代理对象,即目标类对象的子对象HouseOwner proxyObject = (HouseOwner) enhancer.create();return proxyObject;}​/*** @Description: 方法所属类的回调函数* @Author: chengjunyu*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("调用了回调函数");Object object = method.invoke(this.houseOwner, objects);return object;}}​​/*** @ClassName Client* @Description 业务场景类* @Author chengjunyu* @Date 2022/12/10* @Version V1.0*/public class Client {public static void main(String[] args) {//创建代理工厂对象ProxyFactory factory = new ProxyFactory();//获取代理对象HouseOwner proxyObject = factory.getProxyObject();//调用代理对象中的方法proxyObject.rentHouse();proxyObject.takeBackHouse();}}

6、三种代理的对比

6.1、JDK代理和CGLib代理

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行处理,因为CGLib原理是动态生成被代理的子类。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib的代理效率低一些,但是到JDK1.8的时候,JDK代理效率高于CGLib代理,所以如果有接口使用JDK动态代理,如果没有接口则使用CGLib代理。

6.2、动态代理和静态代理

动态代理和静态代理相比较,最大的好处就是接口中声明的所有方法都被转移到调用处理器一个集中的方法处理(InvocationHandler.invoke)。这样,在接口中方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

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

7、优缺点

7.1、优点

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

(2)代理对象可以扩展目标对象的功能;

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

7.2、缺点

(1)增加了系统的复杂度。

8、使用场景

8.1、远程(Remote)代理

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

8.2、防火墙(Firewall)代理

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

8.3、保护(Project or Access)代理

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

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

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

相关文章

matlab学习(三)(4.9-4.15)

一、空域里LSB算法的原理 1.原理&#xff1a; LSB算法通过替换图像像素的最低位来嵌入信息。这些被替换的LSB序列可以是需要加入的水印信息、水印的数字摘要或者由水印生成的伪随机序列。 2.实现步骤&#xff1a; &#xff08;1&#xff09;将图像文件中的所有像素点以RGB形…

【C语言】每日一题,快速提升(1)!

调整数组使奇数全部都位于偶数前面 题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分 所有偶数位于数组的后半部分 解题思路&#xff1a; 给定两个下标left和right&#xff0c;left放在数组的起始…

Web端Excel的导入导出Demo

&#x1f4da;目录 &#x1f4da;简介:✨代码的构建&#xff1a;&#x1f4ad;Web端接口Excel操作&#x1f680;下载接口&#x1f680;导入读取数据接口 &#x1f3e1;本地Excel文件操作⚡导出数据&#x1f308;导入读取数据 &#x1f4da;简介: 使用阿里巴巴开源组件Easy Exce…

软考中级工程师网络技术第二节网络体系结构

OSPF将路由器连接的物理网络划分为以下4种类型&#xff0c;以太网属于&#xff08;25&#xff09;&#xff0c;X.25分组交换网属于&#xff08;非广播多址网络NBMA&#xff09;。 A 点对点网络 B 广播多址网络 C 点到多点网络 D 非广播多址网络 试题答案 正确答案&#xff1a; …

【鸿蒙开发】第二十一章 Media媒体服务(二)--- 音频播放和录制

1 AVPlayer音频播放 使用AVPlayer可以实现端到端播放原始媒体资源&#xff0c;本开发指导将以完整地播放一首音乐作为示例&#xff0c;向开发者讲解AVPlayer音频播放相关功能。 以下指导仅介绍如何实现媒体资源播放&#xff0c;如果要实现后台播放或熄屏播放&#xff0c;需要…

Java使用OpenOffice将office文件转换为PDF

Java使用OpenOffice将office文件转换为PDF 1. 先行工作1.1 OpenOffice官网下载1.2 JODConverter官网下载1.3 下载内容 2.介绍3. 安装OpenOffice服务3.1.Windows环境3.2 Linux环境 4. maven依赖5. 转换代码 1. 先行工作 请注意&#xff0c;无论是windows还是liunx环境都需要安装…

Flutter - iOS 开发者速成篇

首先 安装FLutter开发环境&#xff1a;M1 Flutter SDK的安装和环境配置 然后了解Flutter和Dart 开源电子书&#xff1a;Flutter实战 将第一章初略看一下&#xff0c;你就大概了解一下Flutter和Dart这门语言 开始学习Dart语言 作为有iOS经验的兄弟们&#xff0c;学习Dart最快…

C# dynamic 数据类型

在C#中&#xff0c;dynamic是一种数据类型&#xff0c;它允许在运行时推迟类型检查和绑定。使用dynamic类型&#xff0c;可以编写更具灵活性的代码&#xff0c;因为它允许在编译时不指定变量的类型&#xff0c;而是在运行时根据实际情况进行解析。 dynamic类型的变量可以存储任…

【脚本】多功能Ubuntu临时授予用户sudo权限管理工具

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 设计原理和初衷可以看这里&#xff1a;【技巧】Ubuntu临时授予用户sudo权限&#xff0c;并在一定时间后自动撤销_ubuntu jianshao sudo-CSDN博客文章浏览阅读404次。非常实用_ubuntu jianshao sudohttps://blog.c…

SpringBoot+FreeMaker

目录 1.FreeMarker说明2.SpringBootFreeMarker快速搭建Pom文件application.properties文件Controller文件目录结构 3.FreeMarker数据类型3.1.布尔类型3.2.数值类型3.3.字符串类型3.4.日期类型3.5.空值类型3.6.sequence类型3.7.hash类型 4.FreeMarker指令assign自定义变量指令if…

C++版【AVL树的模拟实现】

前言 在学习AVL树的底层之前&#xff0c;先回顾一下二叉搜索树&#xff0c;我们知道二叉搜索树在极端场景是会形成单支树的&#xff0c;如下图&#xff1a; 在退化成单支树后&#xff0c;查找的效率就会降到O(n)&#xff0c;所以为了解决退化成单支树的情况&#xff0c;AVL树就…

stm32移植嵌入式数据库FlashDB

本次实验的程序链接stm32f103FlashDB嵌入式数据库程序资源-CSDN文库 一、介绍 FlashDB 是一款超轻量级的嵌入式数据库&#xff0c;专注于提供嵌入式产品的数据存储方案。与传统的基于文件系统的数据库不同&#xff0c;FlashDB 结合了 Flash 的特性&#xff0c;具有较强的性能…

Ubuntu20.04安装FloodLight最新版本

Ubuntu20.04安装FloodLight最新版本 网上的很多教程尝试了一下都不对&#xff0c;并且很多都是基于Ubuntu14的旧版本系统&#xff0c;其中的Python环境大多是基于2.0的&#xff0c;由于本人所使用的系统是Ubuntu20.04&#xff0c;后再油管澳大利亚某个学校的网络教学视频的帮助…

【Vue】面试题

vue的组建通信方式 父子关系&#xff1a;props & $emit 、 $parent / $children 、 ref / $refs 、 插槽跨层级关系&#xff1a; provide & inject通用方案&#xff1a;Vuex 或 eventbus 插播&#xff1a;兄弟组建怎么通信&#xff1f; eventbusVuex通过中间件&…

架构师系列-搜索引擎ElasticSearch(六)- 映射

映射配置 在创建索引时&#xff0c;可以预先定义字段的类型&#xff08;映射类型&#xff09;及相关属性。 数据库建表的时候&#xff0c;我们DDL依据一般都会指定每个字段的存储类型&#xff0c;例如&#xff1a;varchar、int、datetime等&#xff0c;目的很明确&#xff0c;就…

STM32之DHT11温湿度传感器

目录 一 DHT11温湿度传感器简介 1.1 传感器特点 1.2 传感器特性 1.3 传感器引脚说明 二 测量原理及方法 2.1 典型应用电路 2.2 单线制串行简介 2.2.1 串行接口 (单线双向) 2.2.2 数据示例 2.3 通信时序 三 单片机简介 3.1 STM32F103C8T6最小系统板 四 接线说明 …

011、Python+fastapi,第一个后台管理项目走向第11步:建立python+fastapi项目,简单测试一下

一、说明 本文章就是记录自己的学习过程&#xff0c;如果有用您可以参考&#xff0c;没用你就略过&#xff0c;没有好与不好之分&#xff0c;今天主要是参考了gitee上的一些项目&#xff0c;一步一步的往后i建立 对于学习来说&#xff0c;如果您有java c等经验&#xff0c;py…

wpf下RTSP|RTMP播放器两种渲染模式实现

技术背景 在这篇blog之前&#xff0c;我提到了wpf下播放RTMP和RTSP渲染的两种方式&#xff0c;一种是通过控件模式&#xff0c;另外一种是直接原生RTSP、RTMP播放模块&#xff0c;回调rgb&#xff0c;然后在wpf下渲染&#xff0c;本文就两种方式做个说明。 技术实现 以大牛直…

RT-thread信号量与互斥量

1,信号量 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。理解资源计数适合于线程间工作处理速度不匹配的场合;信号量在大于0时才能获取,在中断、线程中均可释放信号量。 为了体现使用信号量来达到线程间的同步,…

qemu源码解析一

基于qemu9.0.0 简介 QEMU是一个开源的虚拟化软件&#xff0c;它能够模拟各种硬件设备&#xff0c;支持多种虚拟化技术&#xff0c;如TCG、Xen、KVM等 TCG 是 QEMU 中的一个组件&#xff0c;它可以将高级语言编写的代码&#xff08;例如 C 代码&#xff09;转换为可在虚拟机中…