摘要:RMI是Sun设计的优秀的基于Java的分布计算框架,像CORBA、DCOM等架构一样采用了经典的Stub/Skeleton设计。本文针对基于RMI企业级分布计算的实际需要,利用Java的特性,对RMI进行改进,设计出一个优秀的Intercepto(拦截器)-RMI框架。
关键字:Java;RMI;Stub/Skelton;Serialization;DynamicProxy;DynamicClassLoading
Abstract:
KeyWord:
1 引言
1.1Java概述
严格来说,Java指JavaPlatform,包括JVM(JavaVirtualMachine)、Java语言、Java核心类库和扩展类库及基于此平台的所有应用,它是由SunMicroSystems公司创造并领导发展的。凭借JVM承诺的“Writeonce,RunEverywhere”和垃圾回收、Java语言本身设计的先进、Java类库的强大和高效、SunMicroSystems适当的开放和约束规范,Java无疑已经成为当今最为流行的软件技术,在芯片制造、智能家电、手持智能商务设备、无线通信、个人电脑应用、企业应用系统架构,甚至是搜索引擎开发、军用系统开发、航天控制系统开发等领域都可见其活跃的身影。没有特别说明,下文中的Java特指Java语言。遵循”Networkiscomputer”和”Wemakethenetwork”理念,Sun将Java定位于”网络时代的语言“,因此Java中处处可见针对network的设计。譬如严格的数据类型定义、对象序列化、代码移动性、强大和方便的网络API、完整的安全体系结构等等,这些特性完全颠覆了传统的网络编程模型,一切变得简单而强大。
1.2 经典的Stub/Skeleton计算架构和RMI(RemoteMethodInvocation)
至今为止,几乎所有的分布式计算框架都采用了Stub/Skelton设计,如OMG的CORBA、
MicroSoft的DCOM、SunMicroSystems的RMI,以“通明化“远程调用。动作序列一般如下:客户端的远程调用”代理“给Stub,Stub与客户端分布计算引擎交互,客户端的引擎与服务器端的引擎通信,传递调用信息,服务器端的分布计算引擎与Skeleton交互,Skeleon将远程调用转发给远程对象实施。然后,再沿相反的路径传回结果。(图1)
RMI(RemoteMethodInvocation)是Sun设计的基于Java的轻量级分布对象计算解决方案。
RMI不但在本身的体系结构设计上非常优秀,它还利用和继承了Java平台的若干得天独厚的特性,譬如平台无关性、(分布)垃圾回收、安全性、代码移动性、类动态加载等等。它是EJB和JINI的分布特性支持技术。
2 RMI的编程模型和本质
2.1RMI的编程模型
2.1.1定义远程接口
publicinterfaceHelloWorldextendsRemote{
publicStringreturnGreeting(Stringparam)throwsRemoteException;
}
这个接口定义了远程对象行为,RMI规范规定远程接口需要直接或间接扩展Remote接口
并且方法签名需要抛出RemoteException异常。
2.1.2实现远程接口
publicclassHelloWorldImplextendsUnicastRemoteObjectimplementsHelloWorld{
publicHelloWorldImpl()throwsRemoteException{super();}
publicStringreturnGreeting(Stringparam){
return“Hello”+param;
}
}
2.1.3服务器端(Main.java)生成远程对象,并利用registry将之与一定的名字绑定。
/*…………*/
HelloWorldImplhwi=newHelloWorldImpl();
Naming.bind(“HelloWorld”,hwi);
/*…………*/
2.1.4创建客户端(Client.java)
/*…………*/
System.setSecurityManager(newRMISecurityManager());
HelloWorldhw=(HelloWorld)Naming.lookup(“rmi://localhost/HelloWorld”);
System.out.println(hw.returnGreeting(“World”));
/*…………*/
2.1.5编译源代码
javacHelloWorld.javaHelloWorldImpl.javaMain.javaClient.java
产生文件HelloWorld.class、HelloWorldImpl.class、Main.class、Client.class、
HelloWorldImpl_Stub.class、HelloWorldImpl_Skel.class。
2.1.6运行系统
服务器端首先配置文件HelloWorld.class、HelloWorldImpl.class、Main.class、
HelloWorldImpl_Stub.class、HelloWorldImpl_Skel.class文件,然后执行
javarimregistry
javaMain
客户端首先配置文件HelloWorld.class、HelloWorldImpl_Stub.class然后执行
javaClient
2.2RMI的本质
这里我们以上面叙述编程模型时使用的实例来描述追踪RMI的运作,从而探出本质。
2.2.3服务器(Main.java)中,
HelloWorldImplhwi=newHelloWorldImpl();
Naming.bind(“HelloWorld”,hwi);
上面两句,实现了远程对象的导出(export),并将之与名字HelloWorld绑定。
这里我们需要深入探讨,直入本质,因为我们的改进的一部分就是在这里着手的。因为HelloWorldImpl继承了UnicastRemoteObject,而UnicastRemoteObject在构造函数中通过
exportOjbet将对象导出到特定端口,以使得对象可被远程引用。ExportObject将远程对象置入一个ObjectTable中,并且加载HelloWorldImpl_Stub,并且将Stub与名字绑定。而不是一般认为的远程对象。这样作的目的主要是考虑到远程对象作为参数或返回值的时候,只能传入Stub,而不能移动远程对象实体,满足分布式环境计算的需要。
2.2.2客户端(Client.java)中,HelloWorldhw=
(HelloWorld)Naming.lookup(“rmi://localhost/HelloWorld”)解析。自然,在使用远程对象之
前,需要获得远程对象引用。这一步是通过registry来实现的,registry事实上,是rmi专
用的名字服务工具。它也是远程对象,默认运作在2009端口,也可以使用
LocateRegistry.createRegistry()在特定端口导出它。客户端的lookup操作会首先取得服
务器端registry的远程引用,然后使用远程引用再查询HelloWorldImpl,从服务器端获得的远程引用事实上是一个Stub,所以我们上面的hw事实上是代表Stub的RemoteObject。到此为止,我们已经取得了远程对象的远程引用。
2.2.3客户端(Client.java)中hw.returnGreeting(“World”)解析。客户端使用远程引用调用方法,作为Stub/Skeleton的经典步骤,客户端的调用“代理“给Stub,可以在HelloWorldImp_Stub.java中看到ref.invoke(this,_fld$method_hit_0,null,
0x9339f0d6bc98fe87L),然后Stub与JRMP引擎交互,由JRMP引擎来负责编组(返回时自然就是解列)远程引用,调用方法,方法参数等,并与服务器端JRMP通信,传递调用相关信息。服务器端JRMP引擎将获得的信息递交给Skeleton,Skeleton负责解列,并且从ObjectTable中获得远程对象,将调用“分发“给它执行
,HelloWorldImp_Skel.java中可见publicvoiddispatch(Remoteremote,RemoteCallremotecall,inti,longl),然后编组返回结果,沿相反的途径回传到客户端。
上面已经基本将核心步骤讲述了。可以参看图2。
3 几种Java语言特性
3.1 Java的Serialization特性
Serialization特性是在JDK1.1中增添的,一个对象只要implementsSerializable,
就可以实现”持久化”。持久化能力表示对象可以写入介质,然后再从介质中读出并在内存中恢复原状态的能力。对象的这种能力对于网络应用非常的重要。因为它可以支持对象在网络传输过程中,屏蔽系统的差异。我们关注的是接口中的ANY-ACCESS-MODIFIERObjectreadResolve()throwsObjectStreamException方法,它是用于在对象反序列化时,如果你希望用其它的对象来替代原先的对象,你就可以在这里实施。
3.2 Java的DynamicClassLoading特性
这就是所谓的“代码移动”基础。它是Java最大的特色之一。它允许代码从网络下
载执行。经典的应用是著名的Applet。Java类库中相关类的继承树为:java.lang.Object->
java.lang.ClassLoader->java.security.SecureClassLoader->java.net.UrlClassLoader和
java.lang.Object->java.rmi.server.RmiClassLoader。ClassLoader提供多种形式的类加载功能,特别是基于网络获得字节流,在本地从新映射类定义并加载的能力。UrlClassLoader提供基于网络url集的类加载。RmiClassLoader提供了一个statci功能函数集,一般由RmiRuntime使用以在Rmi编组解列时实现注释类路径信息、基于网络的类加载等等特性。
3.3 Java的DynamicProxy特性
DynamicProxy特性是在JDK1.3中添加的,它遵循Proxy模式的思想。一个Dynamci
Proxy可“代理”一组interface的实现,并且将实际方法调用代理给一个
InvocationHandler实施。通过这种特性,我们可以方便的实现很多经典的应用,如为一
组对象提供一个入口,从而实现在不改变类设计的情况下增加新的功能或者实现拦截
器等等。
4 利用上述Java特性改进RMI模型实施
就目前来说,RMI解决方案一般作为企业分布架构的基础。当然,企业级应用中,安全性、事务性、系统日记等性征都是很必要的,那么我们就设想是否可以在RMI层实施这些特性,从而建立设计更优秀、功能更强大、扩展性更强、维护更方便的企业级系统。经过探索,利用上述Java特性我们实现了这一构想,设计出Interceptor(拦截器)-RMI框架。图3
需要解决的核心问题和相应的解决方案如下:
1. 为了客户端从服务器端下载Stub并能够安装拦截器,我们需要替换掉服务器端默认的
类加载器,用DynamicClassLoader替代。
publicclassDynamicClassLoaderextendsURLClassLoader{
/*…………*/
protectedClassfindClass(Stringname)throwsClassNotFoundException{
if(name.endsWith("_Stub")){
name=name.substring(0,name.length()-5);
Classcl=loadClass(name);
currentClass.set(cl.getInterfaces()[0]);
returnDynamicRemoteStub.class;
}else{returnsuper.findClass(name);}
}
这样,我们在加载远程对象的时候,可以使用自己的ClassLoader,来动态替换Stub。
2.那么DynamicRemoteStub是什么呢?
publicclassDynamicRemoteStubextendsRemoteStub{
/*…………*/
ObjectreadResolve()throwsObjectStreamException
{
if(cl==null)returnthis;
DynamicStubHandlerstubHandler=newDynamicStubHandler();
Objectproxy=Proxy.newProxyInstance(cl.getClassLoader(),
newClass[]{cl},
stubHandler);
stubHandler.setProxy(this);
cl=null;
returnproxy;
}
}
这里我们需要注意的是readResolve,它是在对象序列化的时候调用,实施一定的方法,用其它的对象来代替原来解序列的对象。我们在这里实施这一行为返回代理,在于为客户端添加拦截器。
3.那么代理的处理者InvocationHandler是什么?就是DynamicStubHandler:
publicclassDynamicStubHandlerimplements
InvocationHandler,java.io.Serializable{
/*…………*/
//InvocationHandlerimplementation
publicObjectinvoke(Objectproxy,
Methodmethod,
Object[]args)throwsThrowable{
returnstub.getRef().invoke(stub,method,args,getHash(method));
}
}
/*…………*/
我们可以看到,returnstub.getRef().invoke(stub,method,args,
getHash(method)),通过它,客户端在经过任意个拦击器调用后,最后实施将远程对象的调用。
4.服务器端的拦截器实施。
HelloWorldhw=newHelloWorldImpl();
UnicastRemoteObject.exportObject(hw);
hw=(HelloWorld)Proxy.newProxyInstance(HelloWorld.class.getClassLoader(),
newClass[]{HelloWorld.class},
newLogProxy(hw));
UnicastRemoteObject.exportObject(hw);
/*…………*/
hw=(HelloWorld)Proxy.newProxyInstance(hw.getClass().getClassLoader(),
newClass[]{HelloWorld.class},
newReconnectProxy(server,HelloWorld.NAME));
hw=(HelloWorld)Proxy.newProxyInstance(HelloWorld.getClass().getClassLoader(),
newClass[]{HelloWorld.class},
newPerformanceProxy(hw));
Naming.bind(HellotWorld.NAME,server);
上面仅为示例,我们在打算在服务器端添加一个LogProxy,在客户端添加
ReconnectProxy和PerformanceProxy。决定到底是否是客户端拦截器的是是否使用UnicastRemoteObject.exportObject导出远程对象,因为RMI规范规范没有将Stub与名字绑定的对象,在远程引用的时候,直接将远程对象自身序列化到客户端,而不是它的Stub,这里实施了一个Trick。
5. 我们可以看到上面使用了大量的拦截器。事实上,拦截器主要的功能就是实施特殊应
需求,并且传递调用。最基本的要求,它们需要继承InvocationHandler,并且
implementsSerializable。
LogProxy示例:
publicclassLogProxyimplementsInvocationHandler,java.io.Serializable,LinkedProxy
{
/*…………*/
publicObjectinvoke(Objectproxy,
Methodmethod,
Object[]args)throwsThrowable{
/**
省略:具体的LogProxy拦截器行为在这里实施
**/
try{
returnmethod.invoke(next,args);
}catch(InvocationTargetExceptione){
throwe.getTargetException();
/**省略:next相关的操作**/
}}}
5 结语:改进的意义和应用
RMI作为目前最为优秀的分布计算框架之一,应用非常广泛,特别是企业级的系统基础架构。
而企业级架构中事务特性、日记、性能评价、设计上优秀的扩展性、网络范围管理的便利等等都是很必要的。Interceptor-RMI框架正是为了适应了这一需求而设计的。它为企业分布计算基础架构提供了一个优秀的参考。
参考文献
[1]SunMicroSystems,inc.RMISpecification[R].SunMicroSystems,2003.
[2]W.KeithEdwards.JINI核心技术.北京:机械工业出版社,2000.
[3]EdRoman,ScottAmbler.精通EJB[M].北京:电子工业出版社,2002.
[4]MarkoBoger.Java与分布式系统[M].北京:机械工业出版社,2003.
[5]Rickard?berg.精通RMI-Java与EJB企业级应用开发.北京:机械工业出版社,2003.
[6]朱刚等.Linux网络编程.北京:科学出版社,2000.
[7]DonBox.COM本质论.北京:中国电力出版社,2002.
[8]何炎祥,陈莘萌.Agent和多Agent系统的设计和应用.武汉:武汉大学出版社,2001.