本文主要对设计模式中的代理模式进行讲解,包括静态代理举例,动态代理中的jdk动态代理、cglib动态代理原理分析等几个方面。
一、概念
定义:代理模式(Proxy Pattern)代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的访问。代理对象在客户端了和目标对象之间起到中介作用。
二、结构
UML:
主要角色:
真实主题类:客户端真正想调用的的主题类。
代理类:保存一个真实主题类的引用,使得代理对象可以访问真实主体对象的实体。真实主题类和代理对象都会继承相同的接口:在用到真实主题类的地方都可以使用代理类来完成。
抽象主体:定义真实主题类和代理类的接口。
三、静态代理
虚拟代理
虚拟代理作为创建开销大的对象的代表。直到我们真正使用对象时才会创建它,当对象在创建前和创建中,由虚拟代理来扮演对象的替身。对象创建后,代理会将请求直接委托给对象。
抽象主题接口
1 | public interface LargeObject { |
具体主题角色,实现了抽象主题接口
1 | public class RealLargeObject implements LargeObject { |
现在我们要对类进行访问控制,对”巨型对象”进行延迟创建。
1 | public class ProxyLargeObject implements LargeObject { |
当时使用代理类时,只有当客户端调用doBigThing方法的时候才会创建LargeObject对象。
当我们需要对开销大的对象进行延迟创建或隐藏其创建过程时可以使用虚拟代理模式。
远程代理
概述
RMI
远程代理RMI允许一个jvm上的对象调用另一个jvm上的对象,流程类似于上面这个图。
客户对象直接调用辅助对象 stub的方法,stub打包调用信息,通过网络把他运送给服务端辅助对象 skeleton,服务端辅助对象进行解包,调用真正服务对象的真正方法。
然后服务对象将结果返回给服务辅助对象,服务辅助对象将结果打包,然后客户服务对象将返回结果解包交给真正客户对象。
由registry来作为注册中心,服务端将对象注册到其中,客户端通过相关方法调用。
源代码
下面来介绍RMI步骤
1.制作远程接口
1 | public interface MyRemote extends Remote { |
2.制作远程实现
1 | public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote { |
3.产生Stub和Skeleton
对远程实现类执行,到classes目录下找到远程实现类,然后使用如下命令rmic com.rmidemo.MyRemoteImpl
产生stub和skeleton文件。
4.启动registry
依然是在classes 目录下运行 remiregistry 命令启动registry
5.启动服务
运行远程服务实现的 main() 方法 —> MyRemoteImpl
1 | public class MyRemoteClient { |
这样就完成了RMI的调用,这种方法其实已经过时了,但是这是一个标准的远程代理模式,客户端调用的是注册中心中的远程实现类的代理,但就像是调用本地的方法一样,正常使用。
四、动态代理
动态代理有两种,一种是jdk动态代理,另一种是cglib动态代理,前者是根据继承当前类的接口,而cglib是继承当前类。所以jdk动态代理无法对没有实现接口的类进行代理。
jdk动态代理
UML
使用方法
主题对象
1 | // 定义一个人的接口,用来创建代理~ |
真正主题对象,就是我Cderam,移动只能靠走路
1 | public class Cdream implements Person { |
现在我要开始通过动态代理来改装自己了!!
1 | // 搞一个handler |
1.可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())
2.可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
测试类
1 | public class Test { |
看,我就这样穿上了钢铁战衣,化身钢铁侠,能飞上天与太阳肩并肩了!
这就是动态代理的功能,其实是把我加强了啊~~~~本来代理模式的目的是用来控制访问的,结果让我弄成加强自己了……不过大家对付看吧,用法基本就是这个样子的。
代理模式与装饰者模式
看到这里一定有人要提问题了:“等等等等,我好像在哪里见过这个模式啊,哇!!!!这不是装饰者模式吗?小样,别以为你穿上马甲我就不认识你了!“
唉,其实我也觉得这两个模式太像了!然后我就谷歌啊百度,努力找到了这两个模式的不同,开始做笔记吧!
原理分析
然后来看看代理类的具体信息,对jdk动态代理进行深入研究!
1 | public class Test { |
以上得出结论,创建出来的类是$Proxy0(动态生成的!),父类是Proxy,实现了接口Person,有四个看不懂的属性,方法除了我Person接口里的move、getName还多了几个方法。然后我们一头扎进newProxyInstance
看看到地方发生了什么事。
1 | public static Object newProxyInstance(ClassLoader loader, |
这个方法干了两件事:
- 根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy0类 实现了interfaces的接口,并继承了Proxy类。
- 实例化$Proxy0并在构造方法中把Handler传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值。
然后看看生成代理类$Proxy0
1 | public final class Proxy0 extends Proxy implements Person { |
我们把这个$Proxy转成了Person,然后Person就调用这里面的move方法,move方法触发了CdreamInvocationHandler 中的invoke方法,所以我这次才有幸穿上钢铁战衣化身钢铁侠!!!
真是不禁会想到,我看个原理都看的这么费劲,写这东西的大佬得多可怕啊!!!
jdk动态代理的缺陷,大家也知道,就是这个只能对有接口的类进行代理,并且只有接口里的方法能被增强,如果木有接口的类我们怎么处理啊?emmmmmm……下面我们介绍cglib动态代理。
cglib动态代理
UML
使用方法
沿用上面的Cdream类
1 | public class Cdream { |
创建一个方法拦截器,类似于jdk中的handler
1 | public class CdreamInterceptor implements MethodInterceptor { |
测试类
1 | public class test2 { |
原理分析
这个是cglib动态代理的基本流程,然后我们对cglib进行深入了解一下
1 | public class test2 { |
我竟后悔把方法和属性都打印出来了,这谁受的了啊!不管了,看重点,cglib的代理类确实继承了被增强类,这样就不必在意是否有接口了,但带来的问题就是final方法无法增强,然后代理类又实现了Factory,用来创建实例使用。然后代理了好多好多方法~
然后我们来看看代理类
在调用方法创建代理类前把这个段代码加上去,后面是输出的位置
1 | System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/cdream/IdeaProjects/Project1/out"); |
下面是代理类里的move方法
1 | final void CGLIB$move$2() { |
代理类为每个方法都生成了一个CGLIB$move$2这样的代理方法
下面这个move方法是被我们强转后,会调用,如果CallBack为空的情况下,会调用父类中的方法,如果设置后就会调用方法拦截器中的方法(CdreamInterceptor.intercept)。
然后我们来说一下method和proxyMethod,下面这个是源代码中的注释
1 | public interface MethodInterceptor |
method是被拦截的方法,如果进行调用,则需要使用method.invoke(cdream,args)
,利用反射机制进行调用,而methodProxy则是利用fastClass机制,调用时利用一个hash算法快速定位代理类中的方法,调用时使用如下的方式methodProxy.invokeSuper(o,args)
,methodProxy.invoke(cdream,args)
前者是调用代理类中的CGLIB$move$2()方法,后者是调用代理类中的move()方法。注意invokeSuper的第一个参数。
五、优缺点
优点:
代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;
代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了保护目标对象的作用。
缺点:
- 由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢;
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂;
六、总结
本文对代理模式简要进行了介绍,在日常开发中,代理模式经常会被用到。
静态代理模式与装饰者模式类似,但目的是不同的,装饰者模式目的是对类进行包装,而代理模式则是为了控制对象的方法访问权限。
动态代理是静态代理的增强,避免创建过多的代理类。动态代理的代理类是在运行中生成的,而静态代理的代理类需要我们事先编写好。我们经常会用到的动态代理包括jdk动态代理和cglib动态代理,jdk动态代理需要被代理类的接口,cglib则是继承被代理类,由于cglib的fastClass机制,cglib的效率会高于利用反射的jdk动态代理。
Spring中的AOP,微服务的RPC都是代理模式的一种形式。
本文首发于cdream个人博客
欢迎转载,转载请注明出处!
参考资料: