代理模式(结构型模式)

目录

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.hqwc.cn/news/619186.html

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

相关文章

系统架构最佳实践 -- 统一身份认证系统

目录 1.系统架构设计&#xff1a; 2.用户认证与授权&#xff1a; 3.用户身份管理&#xff1a; 4.安全性保障&#xff1a; 5.日志记录与审计&#xff1a; 6.高可用性与容错性&#xff1a; 7.用户体验优化&#xff1a; 随着互联网的快速发展和应用的普及&#xff0c;人们在…

SpringCloud实用篇(四)——Nacos

Nacos nacos官方网站&#xff1a;https://nacos.io/ nacos是阿里巴巴的产品&#xff0c;现在是springcloud的一个组件&#xff0c;相比于eureka的功能更加丰富&#xff0c;在国内备受欢迎 nacos的安装 下载地址&#xff1a;https://github.com/alibaba/nacos/releases/ 启动…

Hadoop 3.1.3

第1章 Hadoop概述 1.1 Hadoop是什么 1.2 Hadoop发展历史&#xff08;了解&#xff09; 1.3 Hadoop三大发行版本&#xff08;了解&#xff09; Hadoop三大发行版本&#xff1a;Apache、Cloudera、Hortonworks。 Apache版本最原始&#xff08;最基础&#xff09;的版本&#x…

IOS 短信拦截插件

在使⽤iOS设备的时候, 我们经常会收到1069、1065开头的垃圾短信, 如果开了iMessage会更严重, 各种乱七⼋糟的垃圾信息会时不时地收到。 从iOS11开始, ⼿机可以⽀持恶短信拦截插件了. 我们可以通过该插件添加⼀些规则通过滤这些不需要的信息. ⼀. 使⽤xcode新建⼀个项⽬ 【1】…

终端工具命令行颜色配置(解决终端工具连上服务器之后,无颜色问题)

本期主题&#xff1a; 讲解使用mobaxterm等终端工具连上服务器&#xff0c;但是命令行没有颜色的问题 目录 1. 问题描述2. 原因解释3.测试 1. 问题描述 使用终端工具&#xff08;Mobaxterm等&#xff09;连上服务器之后&#xff0c;发现终端工具没有颜色&#xff0c;如下图&am…

中国移动传关停8元保号套餐?或是5G成本带来的压力所致

日前有网友发现希望使用中国移动的保号套餐&#xff0c;却发现已无法办理&#xff0c;媒体对此多有报道&#xff0c;这意味着中国移动的套餐业务发生了重大变动&#xff0c;如此做或许在于5G成本上涨带来的压力促使它不得不提高套餐的门槛。 中国移动已建成最多的5G基站&#x…

HiveQL练习(hive3.x)

零、准备工作 1. Hive环境安装 参见搭建Hive 3.x环境&#xff08;CentOS 9 Hadoop3.x&#xff09; 2. 准备数据 在虚拟机HOME目录创建如下文件内容&#xff1a; cd /root vi emp.csv内容如下&#xff1a; 7369,SMITH,CLERK,7902,1980/12/17,800,,20 7499,ALLEN,SALESMAN…

简单的中医药网站开发(《Web前端开发技术》网页设计)

距离制作这个网站已经过去好久了 但是还是想记录一下 给刚开始学习web的新人提供一个小小的例子 这是我第一次接触计算机有关的代码知识 也算是自己的计算机启蒙了 大一第一学期的制成品 虽然现在看来略为简单 但是当时确确实实实实在在地码了一个月 边学边做 这个过程对于我来…

Ubuntu 22.04 开机自动挂载webdav - 设置开机自启脚本 - 解决坚果云webdav无写入权限

效果图&#xff1a; 前言&#xff1a; 1&#xff09;亲测/etc/fstab的办法没有成功自动挂载&#xff0c;换成传统的rc.local可以解决&#xff1b; 2&#xff09;rc-local.service是系统自带的一个开机自启服务&#xff0c;但是在 ubuntu 20.04 上&#xff0c;该服务默认没有开…

从零开始学习Linux(1)---基本命令(1)

1.学习准备 我学习Linux是使用xshell远程登录自己的云服务器来进行。 xshell是一个远程终端管理软件&#xff0c;下载官网&#xff1a; https://www.netsarang.com/products/xsh_overview.htm 下载安装的时候选择 "home/school"…

prompt 工程整理(未完、持续更新)

工作期间会将阅读的论文、一些个人的理解整理到个人的文档中&#xff0c;久而久之就积累了不少“个人”能够看懂的脉络和提纲&#xff0c;于是近几日准备将这部分略显杂乱的内容重新进行梳理。论文部分以我个人的理解对其做了一些分类&#xff0c;并附上一些简短的理解&#xf…

CentOS 7安装Zookeeper

说明&#xff1a;本文介绍如何在CentOS 7操作系统下使用Zookeeper 下载安装 首先&#xff0c;去官网下载所需要安装的版本&#xff0c;我这里下载3.4.9版本&#xff1b; 上传到云服务器上&#xff0c;解压 tar -xvf zookeeper-3.4.9.tar.gz修改配置 进入Zookeeper目录下的co…