代理模式 PROXY Surrogate 结构型 设计模式(十四)
程序员文章站
2022-05-18 18:20:38
代理模式是一种很常用的模式,JDK也内置了对于代理的支持,动态代理,本文对代理模式进行了介绍,意图,结构,java实现,对静态代理和动态代理进行了分析,并且给出了代码示例,并且介绍了CGLIB的使用。 ......
代理模式 proxy 别名surrogate
意图
为其他的对象提供一种代理以控制对这个对象的访问。
代理模式含义比较清晰,就是中间人,中介公司,经纪人...
在计算机程序中,代理就表示一个客户端不想或者不能够直接引用一个对象
而代理对象可以在客户端和目标对象之间起到中介的作用
结构
代理模式的根本在于隔离,如下图所示,间接访问
代理对象如何能够真的代理真实对象?
在java语言中,看起来像的一个方式就是实现同一接口
代理角色和真实对象角色拥有共同的抽象类型,他们拥有相同的对外接口request()方法
proxysubject内部拥有一个realsubject
你应该能感觉到组合模式的思想-----他们都是subject,属于同一个component
对外有一致的接口
抽象主题角色subject
声明了真实主题和代理主题的共同接口,任何使用真实主题的地方,都可以使用代理主题
代理主题角色proxysubject
代理主题角色内部含有对真实对象的引用,从而可以在任何时候操作真实主题
代理主题提供与真实主题的相同的接口,以便任何时刻,都可以替代真实主题
而且,代理主题还可以在真实主题执行前后增加额外的处理,比如:经纪人要先收下费~
真实主题角色realsubject
被代理的真实主题对象,真正工作的是他,比如经纪人总不会站在舞台上去~
示例代码
subject 抽象角色 定义了真正的处理请求 的request()方法
package proxy; public interface subject { void request(); }
realsubject真实主题角色,实现了处理请求的方法
package proxy; public class realsubject implements subject { @override public void request() { system.out.println("realsubject process request...."); } }
proxy代理角色
实现了request()方法,用于替代真实主题,内部调用真实主题完成请求
并且额外的提供了pre和after操作
package proxy; public class proxy implements subject{ private subject realsubject; @override public void request() { prerequest(); realsubject.request(); afterrequest(); } public proxy(subject realsubject){ this.realsubject = realsubject; } public void prerequest(){ system.out.println("pre request do sth...."); } public void afterrequest(){ system.out.println("after request do sth...."); } }
测试类
package proxy; public class test { /**请求subject执行请求 * @param subject */ public static void askforsth(subject subject){ subject.request(); system.out.println("################"); } public static void main(string[] args){ subject real = new realsubject(); subject proxy = new proxy(real); askforsth(proxy); askforsth(real); } }
定义了真实对象,也定义了一个代理对象
查看他们分别处理请求的结果
从下面的时序图中,能更好的感受到“间接”的感觉
在真正调用真实对象方法前,需要先执行prerequest方法
真实对象方法调用后,在执行afterrequest方法
代理实现
代理的实现分类有两种,静态代理和动态代理
前面形式描述的代理,就是静态代理
在编译时期,就已经编写生成好了代理类的源代码,程序运行之前class文件就已经生成了
这种按照我们上面模式编写了代理类和真实类的形式就是 静态代理
静态代理经常被用来对原有逻辑代码进行扩展,原有的逻辑不需要变更,但是可以增加更多的处理逻辑
但是,但是如果有很多的对象需要被代理怎么办?
如果按照静态代理的形式,那么将会出现很多的代理类,势必导致代码的臃肿。
所以后来出现了动态代理
jdk代理机制
所谓动态代理,按照字面意思就是动态的进行代理,动态相对于静态的含义是不需要事先主动的创建代理类,可以在运行时需要的时候,动态的创建一个代理类。
动态代理的动态关键在于代理类的动态生成,不需要我们实现创建,从class文件的角度来看的话,是与静态代理一样的,仍旧有一个代理类的class文件
在java中提供了内置的动态代理的支持。
java在java.lang.reflect包中提供了三个核心 proxy,invocationhandler, method 可以用于动态代理的使用
java动态代理简单示例
package proxy.mydynamicproxy; public interface subject { void dosth(); }
package proxy.mydynamicproxy; public class realsubject implements subject { @override public void dosth() { system.out.println("real object do something..."); } }
package proxy.mydynamicproxy; import java.lang.reflect.invocationhandler; import java.lang.reflect.method; public class dynamicproxyhandler implements invocationhandler { private object realsubject; public dynamicproxyhandler(object realsubject) { this.realsubject = realsubject; } @override public object invoke(object proxy, method method, object[] args) throws throwable { system.out.println("proxy do something...."); return method.invoke(realsubject, args); } }
package proxy.mydynamicproxy; import java.lang.reflect.proxy; public class test { public static void main(string[] args){ realsubject realsubject = new realsubject(); subject proxy = (subject) proxy .newproxyinstance(test.class.getclassloader(), new class[]{subject.class}, new dynamicproxyhandler(realsubject)); proxy.dosth(); } }
测试结果为:
动态代理到底都做了什么?
对于静态代理,我们有一个realsubject,以及他的超接口subject
subject定义了方法,realsubject实现了方法。
然后我们创建了代理类,这个代理类实现了subject接口,并且将新增的逻辑添加进来,然后通过代理类进行方法调用。
在上面的例子中,realsubject,以及他的超接口subject含义不变,与静态代理中的逻辑一样。
然后我们创建了一个调用处理器dynamicproxyhandler 实现了 invocationhandler接口
该接口只有一个方法invoke
public object invoke(object proxy, method method, object[] args) throws throwable
他有三个参数
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 method 实例。method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.integer 或 java.lang.boolean)的实例中。
最后通过java提供的代理机制创建了一个代理
subject proxy = (subject) proxy
.newproxyinstance(test.class.getclassloader(), new class[]{subject.class}, new dynamicproxyhandler(realsubject));
核心就是newproxyinstance方法,他创建了一个实现了subject接口的代理类
public static object newproxyinstance(classloader loader,
class<?>[] interfaces,
invocationhandler h)
throws illegalargumentexception
这个方法也有三个参数
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
为什么需要这三个参数呢?
首先,proxy.newproxyinstance帮你动态的创建方法,肯定要有一个类加载器,上面的示例中我们直接使用的测试类的类加载,这个一般是应用程序 类加载器
再者,动态代理与静态代理一样,需要实现同样的接口,那你实现了哪些接口呢?所以你得把接口列表告诉我
最后,你希望有哪些处理呢?你要把处理器给我
proxy.dosth();执行时,会将当前代理实例,以及当前方法,以及当前方法的参数传递给invoke方法,所以就完成代理的功能。
再来重头理一下:
- 如同静态代理,需要被代理的对象realsubject,以及他的超接口subject
- 需要实现invocationhandler接口创建一个处理器,新增加的方法逻辑封装在invoke方法中
- proxy.newproxyinstance创建代理实例
- 使用创建的代理实例执行方法
简言之,动态代理与静态代理一模一样,差别就在于不用你事先去自己主动地创建一个代理类
静态的时候编写了代理类,然后编译为class然后需要时被加载到jvm,然后调用
动态是运行时在需要的时候,直接生成class文件
依照上面的步骤流程,你就可以借助于java的机制实现动态代理
但是你会发现,proxy.newproxyinstance方法的参数需要一个 class<?>[] interfaces,这意味着什么?这意味着被代理的对象必须实现一个接口
如果被代理的对象不曾实现任何接口怎么办?
给每个被代理的对象增加一个标记接口(形式接口)?如果只是为了使用jdk的动态代理实现,而添加了无意义的接口这是否妥当?
cglib
还有另外一种形式的动态代理cglib
需要两个jar
package proxy.cglib; public class realsubject{ public void dosth() { system.out.println("realsubject process request...."); } }
package proxy.cglib; import java.lang.reflect.method; import net.sf.cglib.proxy.methodinterceptor; import net.sf.cglib.proxy.methodproxy; public class myhandler implements methodinterceptor { @override public object intercept(object o, method method, object[] objects, methodproxy methodproxy) throws throwable { system.out.println("before do something..."); object object = methodproxy.invokesuper(o,objects); system.out.println("after do something..."); return object; } }
package proxy.cglib; import net.sf.cglib.proxy.enhancer; public class test { public static void main(string[] args){ enhancer enhancer = new enhancer(); enhancer.setsuperclass(realsubject.class); enhancer.setcallback(new myhandler()); realsubject subject = (realsubject)enhancer.create(); subject.dosth(); } }
在这个示例中,不再需要接口,仅仅只有一个真是对象realsubject
实现了一个处理器 myhandler 继承自 methodinterceptor,实现了intercept方法
在测试客户端中,通过四个步骤创建了代理对象,然后借助于代理对象执行
从 enhancer.setsuperclass(realsubject.class);这一句或许猜得到,cglib不依赖于接口,而是代理类继承了真实主题类
流程
真实主题对象realsubject是必不可少的,否则代理模式就没有意义了
类似jdk的代理模式,处理器也是解耦的,在cglib中借助于methodinterceptor接口约定,这一步要做的事情的本质与invocationhandler并没有什么太多不同---封装附加的处理逻辑
借助于enhancer用来组装处理创建逻辑,并且创建代理类
setsuperclass设置需要继承的类(也就是被代理的类)
setcallback设置回调函数
create创建真正的代理对象。
cglib采用继承的机制,如果一个类是final的怎么办?那就歇菜了
jdk代理机制与cglib对比
目前到jdk8 据说性能已经优于cglib了
jdk机制不需要第三方jar,jdk默认集成,cglib需要引入第三方jar包
jdk需要依赖真实主题对象实现接口,cglib则不需要,cglib继承了真实主题
cglib虽然不依赖真实主题实现接口,但是被代理的类不能为final,那样的类是无法继承的
通常的做法是如果实现了接口,那么使用jdk机制,如果没有实现接口,使用cglib
代理用途分类
代理模式的根本在于隔离,“间接”,只要隔离,间接,那么就可以隐藏真实对象,并且增加额外的服务,优化,管理等
比如
隐藏了真实的对象,比如你通过中介租房子,可能到期也没见过房东
提供了代理层,可以提供更多服务
比如买卖房屋通过中介可以节省你合同的审校工作,很多人不懂合同中暗藏的猫腻
隐藏真实对象,自然能够起到一定的保护作用,避免了直接接触
比如去学校见孩子,需要先经过老师同意
通过代理,也相当于有一个管家,可以管理外界对真实对象的接触访问
比如,真实对象是电脑,管家类软件相当于代理,可以限制小孩子对电脑的使用时长
围绕着代理带来的特点“隐藏真实对象,并且增加额外的服务,优化,限制”
在多种场景下,延伸出来一些分类
远程代理 remote
为一个位于不同的地址空间的对象提供一个局域代表对象,这个不同的地址空间可以是本机器的,也可以是另一台机器的
为一个位于不同的地址空间的对象提供一个局域代表对象,这个不同的地址空间可以是本机器的,也可以是另一台机器的
虚拟代理 virtual
根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建
保护代理 protect or access
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限
cache代理
为一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
防火墙代理 firewall
保护目标,防止恶意行为
同步代理 synchronization
使几个用户能够同时使用一个对象而没有冲突
使几个用户能够同时使用一个对象而没有冲突
智能引用 smart reference
当一个对象被引用时,提供一些额外的操作,比如将对象调用次数记录下来
很显然,这些分类其实只是代理的不同应用场景,以后可能还会有更多的分类出来
但是永远也脱离不了代理的“隔离”“间接”的根本核心。
总结
代理角色虽然是真实角色的“代理人”,虽然代理角色内部依赖真实角色
但是真实角色可以完全脱离代理人,单独出现
比如上面示例中的
askforsth(proxy);
askforsth(real);
只不过,通过代理角色会有不同的效果
代理人只是会“帮助”解决他能解决的问题,它能提供的服务,他做不了的事情
比如经纪人不会出唱片,对于出唱片的任务还是会委托给真实角色
现实世界中,我们通常说真实角色委托代理角色,比如,房东找中介
在程序世界中,通常却说代理角色将任务委托给真实角色,委托与被委托都是相对的
要看你到底是站在什么视角看待问题,无所谓~
再次强调,代理模式的重点在于增加对真实受访对象的控制,也可以增加额外的服务。
上一篇: ps将人物单眼皮变成双眼皮效果
下一篇: VSCode瞎折腾记