设计模式之代理模式(Proxy Pattern)

article/2025/7/20 22:18:45

1.概念

为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能或者增加额外功能。

2.角色

抽象主题角色(Subject):声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
具体主题角色(RealSubject):也称为委托角色或者被代理角色。定义了代理对象所代表的目标对象。
代理主题角色(Proxy):也叫委托类、代理类。代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。

3.代码实现

代理模式分为动态代理和静态代理两种模式。静态代理是程序员手动实现代理类,在编码之前,就已经产生了静态代理类的class文件。动态代理是在程序运行时,动态生成代理类的编码,无需程序员手动创建代理类。
3.1静态代理
静态代理是程序员手动书写静态代理类,首先,我们要定义产品接口,我们以明星和替身为例,如下代码:

/*** 产品类,明星演员,规范方法是表演*/
public interface Star {void proferm();
}

然后,我们定义产品实现,即具体明星演员,我们以杨幂为例,如下代码:

public class YMStar implements Star {@Overridepublic void proferm() {System.out.println("杨幂出演节目");}
}

现在,在杨幂出演的某个影片中,一些危险的动作,需要替身来做,而大部分情节,还需要杨幂本人出演。对于这种场景,我们就可以使用代理模式,替身就是杨幂的代理对象,代理杨幂去做危险的动作。代码如下:

/*** 静态代理,手动创建代理对象,代理真实对象执行接口方法*/
public class ProxyYMStar implements Star {private YMStar ym;public ProxyYMStar(YMStar star){this.ym=star;}/*** 代理方法,替身表演危险动作,杨幂表现普通情节*/@Overridepublic void proferm() {System.out.println("替身出演危险情节");ym.proferm();//杨幂出演普通情节}
}

这样,我们调用代理对象,就可以完成表演,代码如下:

public class Main {public static void main(String[] args) {Star star=new ProxyYMStar(new YMStar());star.proferm();}
}

运行结果如下图所示:
在这里插入图片描述
由此可见,静态代理是程序员提前写好的,来进行调用的,这里我们可以感觉到,编写一个和真实对象几乎一样的代理类来实现代理模式,很浪费效率,下面,我们来看动态代理的实现方式。

注意:
在上面的静态代理实现中,我们在ProxyYMStar 代理类中,定义的成员属性是YMStar类型,这也就固定了我们代理的范围,只能是YMStar,而不能是其他明星的代理。这就是静态代理的特点,只针对一个对象做代理。那有同学会想,成员属性定义成Star类型,那不是就可以对很多明星做代理了吗?是的,这么做可以为多个明星做代理,但是,这种实现方式,就不叫静态代理了,而是我们前面讲过的装饰者模式的实现方式了。这也是静态代理和装饰者模式的区别所在。

3.2JDK动态代理
JDK动态代理是基于接口的。这句话的意思是,JDK生成的代理对象,是生成的接口的代理对象。我们接收这个代理对象时,需要是一个接口类型。如果是一个Class类型的对象接收JDK代理对象,会报错。JDK代理模式的工作流程如下:
1.获取被代理对象上的所有接口列表
2.确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
3.根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;
4.将对应的字节码转换为对应的class对象;
5.创建InvocationHandler实例handler,用来处理Proxy所有方法的调用;
6.Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象;
这里,我们主要讲解代理模式的实现方式,JDK代理模式的源码,我会在单独一篇文章中进行讲解。我们这里讲解一下JDK动态代理的API。
 Jdk通过java.lang.reflect.Proxy包来支持动态代理,在Java中要创建一个代理对象,必须调用Proxy类的静态方法newProxyInstance,该方法的原型如下:
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
这几个参数的意思分别是:
loader,表示类加载器,来加载下一个参数interfaces里的接口。
interfaces,表示接口或对象的数组,它就是前述代理对象和真实对象都必须共有的父类或者接口;
handler,表示调用处理器,它必须是实现了InvocationHandler接口的对象,其作用是定义代理对象中需要执行的具体操作。
 我们在调用代理对象的接口方法时,代理对象都会执行handler对象的invoke方法,我们可以在invoke里面,执行方法的加强等操作。invoke方法API如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 其中:
proxy,表示执行这个方法的代理对象;
method,表示真实对象实际需要执行的方法(关于Method类参见Java的反射机制);
args,表示真实对象实际执行方法时所需的参数。
 在实际的编程中,需要优先定义一个实现InvocationHandler接口的调用处理器对象,然后将它作为创建代理类实例的参数。(抑或在调用newProxyInstance方法时使用匿名内部类。)这样就得到了代理对象。
 真实对象本身的实例化在调用处理器对象内部完成,实例化时需要的参数也应该及时传入调用处理器对象中。这样一来就完成了代理对象对真实对象的包装,而代理对象需要执行的额外操作也在invoke方法中处理。
 其后,在客户端中,如果需要使用真实对象时,就可以用代理对象来替代它了(有时需要类型强制转化)。
下面,我们来实现代码:例子还是上面的明星演出,产品规范和产品实现无需改动,我们需要创建接口的代理对象,代码如下:

/*** 动态代理类,首先实现InvocationHandler接口,实现invoke方法,* 在JDK生成Proxy对象的方法里,传入的handler就是这个类本身,因为它实现了invoke方法。* 这样生成的代理类,执行接口方法时,就走handler里的invoke方法了。*/
public class ProxyStar implements InvocationHandler {private Object obj;public  Object getProxyObj(Object obj){this.obj=obj;return  Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("替身演出");method.invoke(obj,args);//本身出演return null;}
}

我们在使用时,用这个代理类就可以,

public class Main {public static void main(String[] args) {Star star=(Star)new ProxyStar().getProxyObj(new YMStar());star.proferm();}
}

运行结果如下:
在这里插入图片描述
这就是JDK的动态代理实现。我们可以看到,这里的核心就是实现InvocationHandler接口,实现其invoke方法,在这个方法里,我们来做方法的增强。然后调用JDK里Proxy类的静态生成代理对象方法,获得代理对象。
在静态代理中,我们手动定义了杨幂的代理类。在JDK动态代理中,我们手动定义了代理类,这似乎看起来都需要定义代理类,那为什么还说动态代理更灵活呢?
因为在静态代理中,我们只是定义了杨幂的代理类,找到了杨幂的替身。那么其他明星需要替身,怎么办呢?我们需要手动定义其他明星的代理类。但是在动态代理中,我们定义的代理类,可以生成所有明星的代理对象,我们无需每个明星都实现一个代理类,这就是两者的区别。

3.3CGLIB动态代理
在JDK动态代理中,生成的代理对象,是面向接口生成的,我们只能为一个接口的实现类,生成代理对象。如果一个对象,没有实现任何接口,而我们要用其代理对象时,应该怎么办呢?这时,我们就应该采用第三方类库中的动态代理方式了。CGLIB是针对类来实现的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLIB代理类的代码如下:

public class ProxyCglib implements MethodInterceptor
{private Object target;public Object getInstance(Object target){this.target = target;//Cglib中的加强器,用来创建动态代理Enhancer enhancer = new Enhancer();//设置要创建动态代理的类enhancer.setSuperclass(this.target.getClass());//设置回调,这里相当于是对于代理类上所有方法的调用,都会调用Callback,而Callback则需要实现intercept()方法进行拦截enhancer.setCallback(this);Object obj = enhancer.create();return obj;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable{System.out.print("I'm Proxy, I'm invoking...");Object object = proxy.invokeSuper(obj, args);System.out.println(object);return object;}
}

通过上面的代码,我们可以生成一个类的代理对象,然后执行代理对象的方法时,都会执行intercept方法。

4.代理模式的应用

在java领域,代理模式的实现方式,就是以上介绍的几种模式。遵循这些实现方式,我们可以在很多情况下使用代理模式,如:
1.远程代理:即为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实;在java中RMI就是远程代理模式。java的RMI模式我会在另一篇文章中进行详解,我们这里不做过多讲解。
2.虚拟代理:即根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象;例如:网页中的无图模式,就是代理对象产生的无图效果,当用户需要图时,才产生真正的图片。这里的实现方式,也是在Handler的invoke方法里进行实现。
3.安全代理:用来控制真实对象访问时的权限;这种应用的实现,也是遵循上面的代理方式实现方式,在invoke方法里,进行业务判断,如果有权限执行某方法,则调用真实对象的方法,如果没有权限执行方法,则不调用真实对象的方法,以此达到权限的控制。
4.智能指引:即当调用真实对象时,代理处理另外一些事。

5.方法增强的方式总结

方法增强的意思是增强某个对象的原始方法,使其在原有方法的基础上,有额外的功能。下面我们总结一下有哪些方式,可以增强对象的原始方法。
1.继承:继承是最简单的方法加强方式,我们想加强谁的方法,继承它,并在子类里覆盖要增强的方法即可。
2.装饰者模式:装饰者模式是面向接口增强方法的。装饰者实现接口,并将接口定义成成员变量,以此来接收要加强的对象,并通过实现接口的方法,调用成员变量,进行方法加强。不熟悉装饰者模式的,可以阅读《设计模式之装饰者模式(Decorator Pattern)》
3.代理模式:我们本篇讲的代理模式,也是用于加强方法的方式和手段


http://chatgpt.dhexx.cn/article/bLQu4xub.shtml

相关文章

树莓派4B配置Selenium库+设置无图无头属性

写一个树莓派4B配置selenium库&#xff0c;并成功调用浏览器进行自动化爬虫 本文会手把手教你配置环境&#xff0c;并带你写第一个selenium爬虫程序 使用版本说明 系统&#xff1a;官网2020-02-13-raspbian-buster-full Python&#xff1a;树莓派自带的3.7.3 以下所有的pip操…

屏蔽图片的chrome插件

有时候觉得CSDN博客的广告好烦啊&#xff0c;还有有些看小说的网站&#xff0c;于是找了个屏蔽图片的插件。 效果如图&#xff1a; 之前博客的广告 之后&#xff1a; CSDN的广告有字&#xff0c;所以不会完全不显示&#xff0c;但是有很多小说网站都可以直接全部屏蔽掉…

python selenium playwright库使用教程 破解网页防止开发者模式 截取数据请求

安装chromedriver 下载 chromedriver的版本一定要与Chrome的版本一致&#xff0c;不然就不起作用。 有两个下载地址&#xff1a; 1、http://chromedriver.storage.googleapis.com/index.html 2、CNPM Binaries Mirror 当然&#xff0c;你首先需要查看你的Chrome版本&…

360浏览器,怎么开启无图片模式,在开热点的时候,省流量

如果某些网站&#xff0c;我需要加载图片&#xff0c;如csdn上传图片的时候。 这时候我们需要进行放权&#xff1a;

【Playwright】关于无痕模式与无头模式

Playwright的无头模式和无痕模式 无头模式 无头模式指的是自动测试框架在执行过程中不打开浏览器窗口的功能。在Playwright中&#xff0c;关闭无头模式会在测试代码执行过程中显示浏览器窗口&#xff0c;执行的过程会显示在浏览器的上。 # 以下代码以Playwright的同步API为例…

爬虫之selenium开启无界面模式

绝大多数服务器是没有界面的&#xff0c;selenium控制谷歌浏览器也是存在无界面模式的&#xff08;又称之为无头模式&#xff09; 开启无界面模式的方法 实例化配置对象 options webdriver.ChromeOptions()配置对象添加开启无界面模式的命令 options.add_argument("--he…

locust入门 -6 无图模式和分布式执行

无图模式 无图模式即不通过UI界面进行执行locust测试用例。locust提供了命令参数&#xff0c;我们只需要在执行时添加一个--headless标签即可 “Disable the web interface, and start the test immediately. Use -u and -t to control user count and run time ” 以下内容为…

Python爬虫配置Selenium库+设置无图无头属性

出一个最新windows下配置selenium全环境的教程~ 本文会手把手教你配置环境&#xff0c;并带你写第一个selenium爬虫程序 环境配置 1.安装Selenium库 Python和Anaconda-Python中并不包含Selenium包&#xff0c;这里我们要安装一下Selenium这个包 以下三种方式认选一种即可&am…

chrome如何进入无图模式?

chrom只显示文字不显示图片 隐私和安全-图片 可以设置所有网站都不显示图片 自定义指定网站不允许显示图片

locust入门 —— 无图模式和分布式执行

无图模式 无图模式即不通过UI界面进行执行locust测试用例。locust提供了命令参数&#xff0c;我们只需要在执行时添加一个--headless标签即可 “ Disable the web interface, and start the test immediately. Use -u and -t to control user count and run time ” 以下内容为…

PC端浏览器如何设置无图模式

以谷歌浏览器为例&#xff0c;注意有些浏览器并不支持该功能。 1&#xff09;打开自定义与控制 2&#xff09;选择设置 3&#xff09;查看左边状态栏&#xff0c;选择高级设置--》隐私设置和安全性 4&#xff09;选择内容设置 5&#xff09;图片 6&#xff09;选择不显示任何图…

火狐浏览器设置无图模式

步骤一 打开火狐浏览器&#xff0c;在地址栏输入about:config&#xff0c;然后回车 步骤二 在出来的页面中搜索&#xff1a;permissions.default.image 步骤三 把 permissions.default.image 设成 2 保存。 效果

万向锁的简单数学解释

我们知道用欧拉角表示空间的旋转&#xff0c;容易产生万向锁(Gimbal Lock)问题&#xff0c;这常常不太容易理解。下面给出一个直观的数学解释。 欧拉角表示的空间旋转&#xff0c;可以用绕三个坐标轴的旋转矩阵的乘积表示 万向锁问题就是出现在这种表示方法中。 假如我们令 β…

万向锁的理解

万向锁 万向锁这个概念其实还是不大好理解的&#xff0c;看了很多的博客&#xff0c;虽然看起来他们讲的很有道理&#xff0c;可还是想不通。 希望我这篇文章能讲清楚。。。 万向锁产生的根本原因是绕三个轴的旋转不是同时进行的&#xff0c;想象一下我们旋转矩阵的推导是不是…

万向锁问题详解,以Unity为例

转载自&#xff1a;https://blog.csdn.net/fengya1/article/details/50721768 根据上面的说明两个旋转面&#xff08;圆圈&#xff09;怎么会共面&#xff0c;让我迷糊。假设共面&#xff0c;那这两个旋转面的法线应该是旋转轴&#xff0c;要想两个面共面&#xff0c;那旋转轴肯…

UE4解决万向锁问题

万向锁 目录万向锁的来源Unity中最简单的万向锁UE4最简单万向锁解决方案四元数与欧拉角之间的转换参考链接 目录 万向锁的来源 简单而言&#xff0c;万向锁就是由于物体在进行旋转时&#xff08;前提是通过欧拉角进行旋转&#xff09;&#xff0c;当旋转到某个特定角度会导致…

万向锁(Gimbal lock)问题的理解及解决

万向锁(Gimbal lock)问题的理解及解决 万向锁&#xff08;Gimbal lock&#xff09;&#xff1a; 一旦选择90作为pitch角&#xff0c;就会导致第一次旋转和第三次旋转等价&#xff0c;整个旋转表示系统被限制在只能绕竖直轴旋转&#xff0c;丢失了一个表示维度。 对于万向锁的…

欧拉角表示旋转会出现的问题——万向锁(Gimbal Lock)

本文用来总结万向锁问题。尽量写得非常简单&#xff0c;方便自己复习和后人理解&#xff0c;水平有限若有错误请指教。 一、旋转的表示 本文中矩阵计算的结果是在世界坐标系&#xff08;称之为North East Down Frame NED Frame)中的坐标&#xff1b; 参考文章中最后矩阵计算的…

旋转矩阵、欧拉角,万向锁的危害

1. 万向锁会带来什么危害&#xff1f; 最近看了很多万向锁的文章&#xff0c;大家都集中于讲述万向锁的成因&#xff0c;最后都会加上句“导致缺失一个方向的自由度”&#xff0c;但是万向锁现象到底会给实际工作带来什么问题&#xff1f;具体点&#xff0c;万向锁会给姿态解算…

资料分享:一文搞懂万向锁

大家好&#xff0c;我是被深圳台风吹飞的小鱼。台风老哥太暴躁&#xff0c;早上上班裤子都湿了 今天给大家分享一篇文章&#xff0c;主要讲的是欧拉角的万向锁现象&#xff0c;大家知道表示旋转的方式有很多&#xff0c;其中欧拉角就属于比较常见的一种方式&#xff0c;但是欧…