ZPY博客

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

代理模式是一个很经典的设计模式,代理模式为另一个对象提供一个替身或点位符以控制对这个对象的访问。它的概念不难理解,但要深入理解几种代理模式还是要花些时间的。

一般来说,代理模式为分下面三种。

远程代理(Remote Proxy)

虚拟代理(Virtual Proxy)

保护代理(Protection Proxy)

下面来分别说说这三种代理

首先,远程代理(Remote Proxy)其实比较好理解,就是我在我的机器上想访问别的机器上的方法,我肯定也是通过获得对象,调用对象的方法,这个对象就是一个远程代理,对我们而言,不管这个对象是代理还是真实的,对我们来说是透明的,我们想要的只是调用这个对象的方法能返回我们想要的结果即可。那么为什么我调用代理的方法可以实际调用到远程机器上真实对象的方法呢?这一切都是通java的rmi技术来实现的。rmi是Rmote Method Invocation的缩写,也就是远程方法调用。使用rmi,首先得将真实对象注册为一个服务,并提供服务名。然后通过ip地址和服务名,就可以调用它的方法。

虚拟代理(Virtual Proxy)作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才会创建它。当对象还没有创建好的时候,由虚拟代理来扮演对象的替身,对象创建好后,代理会将请求直接委托给对象。引用《Head First设计模式》中的一个例子,我们需要请求网络url来显示一张图片,但加载过程中,整个程序会挂起,我们干不了别的任何操作,直到图片显示完成为止。现在我们新建一个ImageProxy作为代理,当真正的ImageIcon加载成功之前,返回ImageProxy的宽和高,比如默认为800*600,显示“图片加载中,请稍候”。然后用一个新的线程来创建ImageIcon,当创建完成时重新调用paint方法将图片显示出来。

保护代理(Protection Proxy)用来对真实对象做一些控制。比如在执行方法的前后打印日志等等。一般比较常用的是java自带的动态代理。Java的动态代理不需要我们自己新建代理类,只需要新建一个InvocationHandle,然后用Proxy.newProxyInstance()方法来动态创建代理。这个InvocationHandle里有一个方法名叫invoke,所有请求都会走到这个invoke方法里,我们可以在这个方法里做我们想做的任何控制。用method.invoke()方法可以调用真实对象的方法。动态代理的具体使用方法不难,真实的难点是理解这个动态代理“动态”在哪?

回答这个问题之前,我们先想下,为什么要用动态代理?要弄清楚这个,必须先从静态代理说起。静态代理很简单,比如有一个类A,实现了一个接口的方法叫hello(),我现在想在调用hello这个方法时打印日志。那么就可以新建一个代理类B,实现同样的接口,并持有A的引用,在B的hello方法里调用A的hello方法,并打印日志。这就是静态代理。但如果我想在调用A的别的方法时也打印日志呢?显然,我在B的对应方法里也必须加上打印的代码,这样会有很多重复的代码。这时候动态代理就能很好的解决上面这个问题。因为动态代理所有的请求都会走到InvocationHandle的invoke方法里,所以你不管是调用A的hello方法还是别的方法,日志的打印代码都只用写在InvocationHandle里即可。

现在,来回答动态代理“动态”在哪的问题。说它动态,是因为我们不是像静态代理那样自己新建代理类,而是交给Java来动态帮我们创建。那么Java是如何帮我们动态创建的呢?我想,大部分人首先想到是利用反射。但是,很遗憾反射实现不了,反射是知道类的包名和类名,利用反射就可以实例化这个对象,但是现在我们并不知道代理类的包名和类名,因为这个对象根本就不并在。。Java的做法是,让我们定义InvocationHandle,然后在Proxy.newProxyInstance()里根据我们定义的InvocationHandle帮我们生成代理类的代码,并编译成字节码,当需要用时,再用ClassLoader来加载到内存。这就是所谓的动态创建。

代理模式也经常和装饰者模式来作比较。它们确实比较相似,但他们的目的不同,装饰者模式是增加对象的行为,而代理模式则是作为对象的一个代理,可以起到控制对象的访问等作用。还有一个区别是,装饰模式只会装饰对象,不会实例化任何东西。虚拟代理会实例化对象。