1997 年,IBM 和 Sun Microsystems 启动了一项旨在促进 Java
作为企业开发技术的发展的合作计划。两家公司特别着力于如何将 Java
用作服务器端语言,生成可以结合进现有体系结构的企业级代码。所需要的就是一种远程传输技术,它兼有 Java 的 RMI(Remote
Method Invocation,远程方法调用)较少的资源占用量和更成熟的 CORBA(Common Object Request
Broker Architecture,公共对象请求代理体系结构)技术的健壮性。出于这一需要,RMI-IIOP 问世了,它帮助将 Java
语言推向了目前服务器端企业开发的主流语言的领先地位。
在本文中,我将简要介绍 RMI-IIOP,目标是使您能开始在企业开发解决方案中使用这一技术。要解释
RMI-IIOP 究竟是什么,我认为提供一些关于 CORBA 和 RMI 的信息是重要的,这些信息您在各个技术的典型介绍中可能找不到。如果您对
CORBA 或 RMI 的基础知识不熟悉,我建议您在往下读之前先阅读一些介绍性信息。请参阅参考资料,那里挑选了一些文章和教程。
在我具体讨论 RMI-IIOP 之前,我们将先看一下 CORBA 和 RMI
用来对请求进行数据编入的机制。CORBA 将是我们的主要示例,因为 RMI-IIOP 数据编入是建立在 CORBA
传输协议(IIOP)的基础上的。我们将回顾一下该传输协议和 ORB(object request
broker,对象请求代理)在网络上发送请求、定位远程对象和传输对象方面的基本功能。
远程对象传输
对
CORBA 请求进行数据编入是通过使用 IIOP 协议做到的。简言之,IIOP 将以标准化格式构造的任何 IDL(Interface
Definition Language,接口定义语言)的元素表示为一系列字节。那就假设有一个 Java 客户机正在将一个 CORBA
请求分派到 C++ 服务器吧。客户机应用程序以 Java
接口的形式拥有远程对象的引用,并调用该接口的一个操作。本质上是,接口调用它对该操作的相应实现,这个实现将位于存根(stub)(存根是您将已经用
idlj
从 IDL 生成了的)。
存根把方法调用分派到 ORB 中,ORB 由两部分组成:客户机 ORB 和服务器 ORB。客户机
ORB 的职责是对请求进行数据编入,放到网络上,传往特定位置。服务器 ORB
的职责是侦听从网络上传下来的请求,并将这些请求转换成语言实现能够理解的方法调用。要了解对 CORBA ORB 的角色的更深入讨论,请参阅参考资料部分。
存根分派了方法调用之后,客户机 ORB 将请求和所有参数转换成标准化字节格式,在这种情况中是
IIOP。接着,请求通过导线被发送到服务器 ORB,服务器 ORB 应该正在侦听传入请求。服务器端 ORB 将读进数据的字节并将请求转换成对
C++ 服务器实现有意义的东西。C++ 服务器方法将执行它的功能(即调用所请求的方法)并使用相同的机制通过 IIOP 将结果返回给客户机。
RMI 以类似的方式处理请求,但是它使用 JRMP(Java Remote Messaging Protocol,Java 远程消息传递协议)作为其传输协议。当然,RMI 传输还涉及 Java 对象的序列化。
- CORBA 运行在 IIOP 协议之上;RMI 使用 JRMP。
- CORBA 是独立于语言的;RMI 是纯粹 Java 到 Java 的。
- RMI 使用 JNDI 定位远程对象;CORBA 使用 CosNaming。
- RMI 会将对象序列化;CORBA 则不然。
|
远程对象定位
CORBA
使用 CosNaming 命名服务定位远程对象。CosNaming 为名称服务器保存对 CORBA
服务器进程的绑定(或引用)提供了一个框架。当 CORBA 客户机向名称服务发送 CosNaming
请求,请求给定名称的服务器进程时,名称服务返回该进程的可互操作对象引用(interoperable object reference(IOR))。接着,客户机使用该 IOR 直接与服务器进程通信。
IOR 包含关于服务器进程的信息,例如服务器进程的位置。CosNaming 服务的缺点之一是,IOR 对人类而言是难以看懂的 — 至少对我们这些没有电子大脑的人来说是这样。相反地,RMI 对用户则要友好一些。它使用运行在 JNDI 之上的注册中心(与命名服务极为相似)来定位远程对象。RMI 注册中心使用 Java Reference
对象(它由若干个 RefAddr
对象组成)来识别和定位远程对象。这些 Java 对象比 IOR 对用户更加友好。
不久前,COBRA 将可互操作命名服务(Interoperable Naming
Service(INS))结合进了它的对象-定位(object-location)模式。INS 在 CosNaming
上运行,使用人类可以阅读的 URL 作它的对象位置。INS 不使用命名服务;相反地,它将调用直接发送到指定的 URL。请参阅参考资料了解关于 INS 的更多信息。
RMI 对 CORBA
那
么,哪一个更好呢:是 CORBA 还是 RMI?答案取决于您想做什么。CORBA
是一个运行在业界标准的第三或第四代协议上的、经过试验和测试的大体系结构。如果考虑到 CORBA
提供的所有附件(例如:事务处理、安全拦截器、事件通道,还有更多)的话,则 CORBA 看来是企业应用程序的解决方案。CORBA
的最大缺点是它很复杂。要熟练使用 CORBA,开发者通常要经历陡峭的培训曲线。
相反地,RMI 相当容易学习。创建一个客户机/服务器实现,绑定到注册中心和远程对象,使用 RMI
调用和/或接收请求都相当简单。RMI 的资源占用量也比 CORBA 小得多,因为 JRMP 是开销比 IIOP 小得多的协议。但是,RMI
缺乏 CORBA 的工业级的附件,而且是纯基于 Java 的机制。那么,我们真正需要的就是 RMI 的灵活性和易用性以及 CORBA
的企业就绪性,对吗?那就开始讨论 RMI-IIOP 吧。
- RMI-IIOP 兼有 CORBA 的强度和 RMI 的灵活性。
- 开发者很容易就可以使用 RMI-IIOP,RMI-IIOP 也易于集成到多数企业基础架构中。
|
RMI-IIOP 概览
RMI-IIOP 让您仅需极少修改就可以在 IIOP 上运行 RMI 调用。借助于 RMI-IIOP,您可以编写简单易懂的 Java 代码,同时使用 CORBA 提供的丰富的企业功能套件。而且,代码的灵活性足够大,可以运行在 RMI 或 IIOP 上。这意味着,您的代码可以在纯 Java 环境中运行(当小的资源占用量和灵活性很关键时),或者对代码作少量修改后集成到现有的 CORBA 基础架构中。
RMI-IIOP 很强大的功能之一是,它让您编写纯 Java 客户机/服务器实现而不丧失 RMI
类序列化的灵活性。RMI-IIOP 通过覆盖 Java 序列化并在导线上将 Java 类转换成 IIOP 做到这一点。在另一端,Java
类被作为 IIOP 从导线上读下来,接着创建这个类的一个新实例(使用反射),类的所有成员的值都完整无缺 — 瞧:这就是 IIOP 上的 Java 序列化!
为了让 RMI-IIOP 实现透明的对象定位,ORB 供应商历史上曾经使用 Java CosNaming 服务提供者(或用外行人的话说,是插件)。该插件在 JNDI API 之下工作,访问 CORBA 命名服务。尽管我没有在这里花篇幅来说明原因,但这种命名解决方案并不理想。其结果是,许多供应商 — 尤其是应用服务器供应商 — 为 RMI-IIOP 开发了专门的对象定位机制。
RMI-IIOP 也支持作为 Java CosNaming 服务的一个扩展的 INS。因为我相信 INS 将确定对象定位的未来方向,所以我们在本文将讨论的代码示例使用 INS。
注:因为 Sun 尚未完全遵循 OMG INS 标准,也尚未公开 org.omg.CORBA.ORB
接口的 register_initial_reference
,所以本文提供的源代码将不能与 Sun JDK 一起工作。您将需要 IBM Developer Kit for Java technology,版本 1.3.1 或更高版本。不过,我已经创建了一个使用命名服务的与 Sun 兼容的示例,您可以从参考资料部分下载它。
自己动手构建 RMI-IIOP
说
得够多了,让我们来编写代码吧!在以下几部分中,我们将构建一个简单的、基于 Java 的客户机/服务器 RMI-IIOP
应用程序。这个应用程序由三个部分组成:RMI 接口、服务器应用程序和客户机应用程序。示例以在 IIOP 之上的 Java
序列化为特色,所以您可以看到 Java 类如何被客户机实例化,如何传递到服务器,由服务器更改,然后将所有修改完整地回传到客户机。
第 1 部分:定义接口
在 RMI-IIOP 下,我们可以选择使用 RMI 或 IDL 来定义接口。因为我们想看看 RMI 如何运行在 IIOP 上,所以我们将使用 RMI 定义示例接口。清单 1 是我们的简单示例的 RMI 接口:
清单 1. RMIInterface.java
/* * Remote interface */public interface RMIInterface extends java.rmi.Remote { public String hello() throws java.rmi.RemoteException; public SerClass alterClass(SerClass classObject) throws java.rmi.RemoteException;}
|
RMIInterface
定义一个 hello()
方法和一个 alterClass(SerClass)
方法。后一个方法用 SerClass
作参数,SerClass
是一个实现 Serializable
的 Java 类,alterClass(SerClass)
方法返回一个类型与其参数的类型相同的类。SerClass
是一个有几个成员的简单的类,每个成员有相应的 getter 方法。这些方法如清单 2 所示:
清单 2. SerClass.java
/** * This class is intended to be serialized over RMI-IIOP. */public class SerClass implements java.io.Serializable { // members private int x; private String myString; // constructor public SerClass(int x, String myString) throws java.rmi.RemoteException { this.x=x; this.myString=myString; } // some accessor methods public int getX() { return x;} public void setX(int x) { this.x=x; } public String getString() { return myString; } public void setString(String str) { myString=str; }}
|
这就是我们简单的接口的全部。现在我们来研究一下服务器类。
第 2 部分:构建服务器
我们将使用一个既充当 RMIInterface
实现类又包含 main 方法(以启动我们的服务)的服务器类(Server.java
)。Server.java
继承 javax.rmi.PortableRemoteObject
。这样,它就包含了将自己作为 Remote
接口绑定到 ORB 和开始侦听请求所需要的全部功能。清单 3 是该服务器的代码:
清单 3. Server.java
/* * Simple server */import java.util.*;import java.rmi.Remote;import java.rmi.RemoteException;import javax.rmi.PortableRemoteObject;import javax.rmi.CORBA.Tie;import javax.rmi.CORBA.Util;import org.omg.PortableServer.POA;import org.omg.PortableServer.*;import org.omg.PortableServer.Servant;import org.omg.CORBA.ORB;public class Server extends PortableRemoteObject implements RMIInterface { // must explicitly create default constructor // to throw RemoteException public Server() throws RemoteException { } // implementation of RMIInterface methods public String hello() throws RemoteException { return "Hello there!"; } public SerClass alterClass(SerClass classObject) throws RemoteException { // change the values of SerClass and return it. // add 5 to X classObject.setX( classObject.getX() + 5 ); // alter the string classObject.setString( classObject.getString() + " : I've altered you" ); return classObject; } public static void main(String[] args) { try { // create the ORB passing in the port to listen on Properties props = new Properties(); props.put("com.ibm.CORBA.ListenerPort","8080"); ORB orb = ORB.init(args, props); // instantiate the Server // this will automatically call exportObject(this) Server s = new Server(); // now get the Stub for our server object - // this will be both // a remote interface and an org.omg.CORBA.Object Remote r=PortableRemoteObject.toStub(s); // register the process under the name // by which it can be found ((com.ibm.CORBA.iiop.ORB)orb). register_initial_reference("OurLittleClient", (org.omg.CORBA.Object)r); System.out.println("Hello Server waiting..."); // it's that easy - // we're registered and listening for incoming requests orb.run(); } catch (Exception e) { e.printStackTrace(); } }}
|
呃,这里发生着什么呢?
服务器应用程序的代码很长,那我们就分开来讲吧。首先,如前面提到过的,Server
类实现 RMIInterface
并为它的所有方法提供实现。您可以在代码的前面部分看到 RMIInterface
的 hello()
方法和 alterClass(SerClass)
方法的实现。hello()
方法只是返回字符串“Hello there!”。alterClass(SerClass)
方法用 SerClass
对象作参数,修改成员的值,然后返回新的对象 — 全都通过 RMI-IIOP。
Server.java
的 main 方法初始化一个 ORB。这个 ORB 将设置为 8080 的 com.ibm.CORBA.ListenerPort
属性作为参数传入。这将使得 ORB 在端口 8080 上侦听传入请求。请注意,com.ibm.CORBA.ListenerPort
是一个专有的 IBM 属性。如果您想在另一供应商的 ORB 上运行这些代码,那您应该参阅该供应商的文档,找到适当的属性。(Sun 使用 com.sun.CORBA.POA.ORBPersistentServerPort
,但它只在您使用 POA(portable object adapter,可移植对象适配器)伺服器(servant)时才能够工作。)
初始化 ORB 后,main 方法接着对 Server
对象进行实例化。因为这个 server 对象也是一个 PortableRemoteObject
,所以缺省构造函数会自动调用 exportObject(this)
。这个对象现在已经就绪于接收远程调用。
接着,我们需要通过调用 ORB.register_initial_reference(String,orb.omg.CORBA.Object)
注册这个对象。为此,我们需要把我们的 server 对象作为 org.omg.CORBA.Object
的引用。调用 PortableRemoteObject.toStub(s)
实现了这一点,因为所返回的对象都实现了 java.rmi.Remote
和 org.omg.CORBA.Object
。
然后,返回的 org.omg.CORBA.Object
对象向服务器端 ORB 注册为“OurLittleClient”。为了确保 INS 请求能够定位对象,我们使用注册调用 register_initial_reference
。
当 INS 调用进入 ORB 时,ORB
将查找已经以正在被请求的名称注册的对象。由于我们将对象注册为“OurLittleClient”,所以,当一个 INS 调用进入我们的服务器
ORB 要求“OurLittleClient”时,我们将知道客户机正在查找的是哪个对象。
最后,我确信您已经注意到我们将 ORB 强制转型成 com.ibm.CORBA.iiop.ORB
。因为 Sun 尚未公开 org.omg.CORBA.ORB
接口的 register_initial_reference
,所以 IBM SDK 也不能将它公开。因此,我们必须将我们的 ORB 强制转型成 IBM ORB。随着 Sun 越来越遵循 OMG,JDK 的未来版本(1.4.0 后)将可能不需要这种强制转型。
就是这样!很简单吧 — 嗯,是有点。我们的服务器现在正在等待传入客户机 INS 请求。但客户机怎么样呢?
第 3 部分:构建客户机
客户机应用程序的代码如清单 4 所示:
清单 4. Client.java
/* * Client application */import javax.rmi.PortableRemoteObject;import org.omg.CORBA.ORB;public class Client { public static void main(String[] args) { try { ORB orb = ORB.init(args, null); // here's the URL for the local host String INSUrl = "corbaloc:iiop:1.2@localhost:8080/OurLittleClient"; // get the reference to the remote process org.omg.CORBA.Object objRef=orb.string_to_object(INSUrl); // narrow it into our RMIInterface RMIInterface ri = (RMIInterface)PortableRemoteObject.narrow(objRef, RMIInterface.class); // call the hello method System.out.println("received from server: "+ri.hello()+""n"); // try RMI serialization SerClass se = new SerClass(5, "Client string! "); // pass the class to be altered on the server // of course behind the scenes this class is being // serialized over IIOP se = ri.alterClass(se); // now let's see the result System.out.println("Serialization results :"n"+ "Integer was 5 now is "+se.getX()+""n"+ "String was ""Client String! "" now is """+se.getString()+""""); } catch (Exception e) { e.printStackTrace(); } }}
|
如何分解客户机代码
客户机代码比服务器代码要简单一些。我们初始化一个 ORB,然后调用 string_to_object(String)
,其中的 string 是我们的 INS URL。构造 INS URL 相当简单:首先,我们指定我们使用 corbaloc URL(请参阅参考资料)和 IIOP 协议版本 1.2。接着,我们将主机名(www.whatever.com)和要连接的端口添加进去。最后,我们指定我们要查找的服务的名称。结果 INS URL 是 corbaloc:iiop:1.2@localhost:8080/OurLittleClient。
当我们将这个 URL 传递到 ORB.string_to_object(String)
时,ORB 将分派一个请求到所指定的服务器,以请求所请求的服务。假设一切运转正常,则 ORB 将接收回该服务的一个对象引用(实际上是一个 IOR)。然后,我们将该对象引用强制转型(narrow)成我们能够使用的东西,即 RMIInterface
,这样,我们就为开始调用方法做好了准备。
在调用了简单的 hello 方法(它应该不需要任何解释吧)之后,我们可以开始探讨 RMI-IIOP 的序列化功能了。首先,我们创建一个 SerClass
,
一个可序列化的 Java 类,并初始化它的成员变量。接着,我们将这个类传入到我们的方法,方法通过 IIOP
将类写出到服务器。服务器读入类并将它重创建为服务器端 Java 对象,修改它的成员值,然后返回它(使用
IIOP)作为方法的返回值。当接收到在远程方法调用之后重创建的对象时,我们看到它的成员确实已被服务器修改了。就是这么简单:在 IIOP 上进行
Java 序列化。
第 4 部分:运行示例
请注意,我们这里所创建的示例必须在 IBM Developer Kit for Java technology,版本 1.3.1 或更高版本中运行。如果您宁愿使用 Sun JDK,请下载特定于 Sun 的源代码,
您应该在 Sun 1.4.0 JDK 或更高版本中运行它。这个源代码包括一个解释 IBM SDK 版本和 Sun JDK 版本之间的差异的
readme.txt 文件。如果您没有 IBM Developer Kit for Java technology(而您又想要一个),请现在就下载一个;它们是免费的。
这里是运行示例的步骤:
- 下载源文件。
- 输入
javac *.java
,javac 所有文件。
- 对 server 类运行
rmic
(带 IIOP 标志):rmic -iiop Server
。
- 启动服务器:在 Windows 中,请输入
start java Server
。
- 启动客户机:在 Windows 中,请输入
start java Client
。
关于 RMI-IIOP 和 EJB 组件的一点注释
EJB
2.0 规范指出,EJB 组件必须能在 RMI 和 RMI-IIOP 上运行。添加 RMI-IIOP 作为针对 EJB
组件的在线协议,已经给将 J2EE 环境集成到现有的企业基础设施(多数是 CORBA 相当密集的)带来了很大帮助。但它也引起了一些问题。
简单地说,就是将定制构建的组件和 EJB
组件集成起来要求您(开发者)处理管道(plumbing),否则在 EJB
体系结构中它们对您来说将很抽象。到目前为止,还没有解决这个问题的简单方案,可能永远也不会有。随着诸如 Web
服务这样的技术的发展,或许会出现解决方案,但目前尚未可知。
结束语:此后该做什么
我希望本文已经向您展示了构建和运行 RMI-IIOP 客户机/服务器应用程序是多么容易。您可以修改一下我们使用的示例,用纯 CORBA 替代客户机或服务器,不过这样做将除去您应用程序中的 Java 序列化。
如果您想在 CORBA 环境中使用 RMI-IIOP,那么看看 IDL 如何映射成 Java 以及
Java 如何映射成 IDL 是值得的。如果您想在不安全的环境(即不是您自己的 PC)中部署 RMI-IIOP,那么研究一下 CORBA
安全功能(如拦截器和 CORBA 安全模型)以及其它 CORBA 企业功能(如事务处理)是个不错的主意。CORBA 所有的丰富功能在您运行
RMI-IIOP 时都可以使用。
参考资料