《Head First设计模式》 读书笔记12 代理模式
The Proxy Pattern
问题引入
为上一章的糖果机建立一个糖果监视器,在远程监控糖果机。
远程代理就好比“远程对象的本地代表”。
何谓“远程对象”?这是一种对象,活在不同的Java虚拟机(JVM)堆中,更一般的说法为,在不同的地址空间运行的远程对象。
何谓“本地代表”?这是一种可以由本地方法调用的对象,其行为会转发到远程对象中。
你的客户对象所做的事情就像是在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再由代理处理所有网络通信的底层细节。
Java已经内置远程调用的功能了,我们只需要修改一下代码,让它符合RMI的要求就行了。
远程方法
我们想要设计一个系统,能够调用本地对象,然后将每个请求转发到远程对象上进行。
我们需要一些辅助对象,帮我们真正进行沟通。
客户辅助对象(Client helper)并不真正拥有客户所期望的方法逻辑,它会联系服务器,传送方法调用信息,然后等待服务器返回。
在服务器端,服务辅助对象(Service helper)从客户辅助对象中接收请求(通过Socket连接),将调用的信息解包,然后调用真正服务对象上的方法。
对于服务对象(Service object)来说,调用是本地的,来自服务辅助对象,而不是远程客户。
服务辅助对象得到返回值,将它打包,然后运回到客户辅助对象(通过网络Socket的输出流),客户辅助对象将信息解包,最后将返回值交给客户对象。
RMI: Remote Method Invocation 远程方法调用
RMI: Remote Method Invocation 远程方法调用。
RMI提供了客户辅助对象和服务辅助对象,为客户辅助对象创建和服务对象相同的方法。
RMI的好处在于你不必亲自写任何网络或I/O代码。
客户程序调用远程方法就和在运行在客户自己的本地JVM上对对象进行正常方法调用一样。
RMI将客户辅助对象(client helper)称为桩(stub),服务辅助对象(service helper)称为骨架(skeleton)。
制作远程服务
这里有用来制作远程服务的五个步骤,这些步骤将一个普通的对象变成可以被远程客户调用的远程对象。
1.制作远程接口。
远程接口定义出可以让客户远程调用的方法。
客户将用它作为服务的类类型。Stub和实际的服务都实现此接口。
2.制作远程的实现。
这是做实际工作的类,为远程接口中定义的远程方法提供了真正的实现。
这就是客户真正想要调用方法的对象。
3.利用rmic产生stub和skeleton。
这是客户和服务的辅助类。
你不需要自己创建这些类,当你运行rmic工具时,会自动处理。rmic可以在JDK中找到。
4.启动RMI registry(rmiregistry)。
rmiregistry就像是电话簿,客户可以从中查到代理的位置(也就是client helper/stub 对象)。
5.开始远程服务。
你必须让服务对象开始运行。
你的服务实现类会去实例化一个服务的实例,并将这个服务注册到RMI registry。
注册之后,这个服务就可以供客户调用了。
步骤细节
步骤一:制作远程接口
接口要扩展(extends)Remote接口(java.rmi.Remote)。这个接口没有方法,只是一个标记接口。
声明所有的方法都会抛出RemoteException。
确定变量和返回值是属于原语(primitive)类型或者可序列化(Serializable)类型。
步骤二:制作远程实现
服务必须实现远程接口。
为了要成为远程服务对象,你的对象需要某些“远程的”功能。
最简单的方法是扩展java.rmi.server.UnicastRemoteObject,让超类帮你做这些工作。
设计一个不带变量的构造器,并声明RemoteException。
利用RMI Registry注册此服务。
注册服务使用了java.rmi.Naming类的静态rebind()方法。
步骤三:产生Stub和Skeleton
在远程实现类上执行rmic。
rmic是JDK内的一个工具,用来为一个服务类产生stub和skeleton。命名习惯是在远程实现的名字后面加上_Stub和_Skel。
步骤四:执行remiregistry
开启一个终端,启动rmiregistry。
步骤五:启动服务
开启另一个终端,启动服务。
代理模式定义
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
代理模式有许多变体,而这些变体几乎都和“控制访问”的做法有关。
使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象(远程代理)、创建开销大的对象(虚拟代理)或需要安全控制的对象(保护代理)。
代理模式的类图
虚拟代理
前面的例子中讲了远程代理,代理模式可以以很多形式显现。
虚拟代理和远程代理的比较:
远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。
虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。
例子:想要显示CD封面,得从在线服务中取得图像,由于网络限制,下载需要一些时间,在等待图像加载的时候,应该显示一些东西。虚拟代理可以代理Icon,管理背景的加载,并在加载未完成时显示“CD封面加载中,请稍候……”,当加载完成,代理显示图像。注意这里要启动一个新的线程异步加载图像。
Java API 动态代理
Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类。
因为实际的代理类是在运行时创建的,我们称这个Java技术为:动态代理。
动态代理的类图
你可以把InvocationHandler想成是代理收到方法调用后,请求做实际工作的对象。
其中只有一个invoke()方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。
保护代理
例子:约会服务系统,有个人信息的访问相关,希望顾客可以设置自己的信息,同时又防止他人更改这些信息;
另外,评分则相反,他人可以设置你的评分,但自己不能更改自己的评分;
还有一些get方法是所有人都可以调用的。
保护代理就是,需要根据访问权限,决定客户可否访问对象的代理。
关于动态代理
动态代理之所以被称为动态,是因为运行时才将它的类创建出来。
代码开始执行时,还没有proxy类,它是根据需要从你传入的接口集创建的。
Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。
代理类有一个静态方法isProxyClass(),此方法的返回值如果为true,表示这是 一个动态代理类。
Java 5开始,RMI和动态代理搭配使用,不再需要使用rmic,客户和远程对象沟通的一切都在幕后处理掉了。
更多代理的变体
防火墙代理(Firewall Proxy):
控制网络资源的访问,保护主题免于“坏客户”的侵害。
智能引用代理(Smart Reference Proxy):
当主题被引用时,进行额外的动作,例如计算一个对象被引用的次数。
缓存代理(Caching Proxy):
为开销大的运算结果提供暂时的存储:它也允许多个客户共享结果,以减少计算或网络延迟。
同步代理(Synchronization Proxy):
在多线程的情况下为主题提供安全的访问。
复杂隐藏代理(Complexity Hiding Proxy):
用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Façade Proxy),这不难理解。
复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。
写入时复制代理(Copy-On-Write Proxy):
用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。这是虚拟代理的变体。