|
|
核心规范
当前最新版本是3.0,是在2002年8月整理发布的。CCM的3.0规范也已经发布。 CORBA规范3.0终于出来了,也许是不能再拖了吧。比较奇怪的是3.0规范的编号是2002年6月份的(02-06-33),但是规范是在7月份才整理出来的。在CORBA3.0规范中去掉了MiniCORBA和实时CORBA。 OMG组织的网站上有一个页面介绍在3.0规范将要增加的内容,我将其翻译过来,大家可以点这儿查看。因为新版本规范在不断推出,所以里面的内容可能会与现在的实际情况有点儿差异。
语言映射
规定IDL语言到Ada、C、C++、JAVA、COBOL、SmallTalk、Lisp、Python和CORBA脚本语言的映射,以及JAVA到IDL的反映射等。
对象服务
共有17个对象服务:命名服务、事件服务、时间服务、对象事务服务、安全服务、收集服务、并发服务、特性服务、持久对象服务、生命周期服务、交易对象服务、外表化服务、关系服务、查询服务、通知服务、许可服务、增强时间视图。
通用设施
主要包括国际化和时间、移动代理。
行业标准
主要有商业、金融、生命科学研究、制造业、电信、交通、医疗保健等。
规则与自由
----为何选择 CORBA 和 Java 技术?
欢迎光临有关 Java 和 CORBA 技术的这一定期栏目。此第一期将概述 Java 和 CORBA 技术,并帮助您决定如何最有效地让它们为您工作。以后的栏目将提供 Java 和 CORBA 编程的内行指导和代码。
在 1855 年,时年 26 岁的 Joshua Chamberlain 是 Bowdoin 大学的修辞学教授,在一次演讲中讲述了规则与自由之间的关系,以及二者之间失衡带来的危害。没有自由的规则是专制,而没有规则的自由是混乱。
公司信息系统部门的管理可能会形成过度的规则或过度的自由。规则过度的一个例子是只适用于一个厂商的管理指令,这样您的系统在扩展时,或在与潜在的公司合作伙伴集成时就会有问题。如果自由过多,个人或者开发小组就会根据他们来选择技术,这样,如果这些技术高手遇到他们“生疏的”优先认股权这样的话题时,扩展或集成又会变得很困难。开发人员可能会受业务环境不断变化的影响。
努力实现环境平衡
多种环境因素在决定我们的系统必须遵守的的规则。存在于一个业务环境中的系统应有足够长的寿命,以为公司和客户创造价值。持续为一个组织创造长期价值的系统必须能够应付经常的变化。合并、并购、新的管理和市场力量在改变系统的环境,以及通过使用该系统完成业务过程的用户的需求。变化是一个常数,必须将它添加到评估方程式中。当然,我们无法预知未来,但我们可以分析趋势并努力设计我们的系统,使之能够接受大量潜在的变化或系统扩展,同时对组织产生的影响又可以尽可能小。
为了做出正确的决策和评估,我们需要理解支配着环境的规则,以及我们的应用程序在该环境中应具有的自由。要理解的最重要的规则是,我们的系统处在不断发展的组织中,无论是在公司、政府还是教育组织中,都是业务问题支配技术问题。许多体系结构和平台评估都将主要问题漏掉了。他们通常会将两项或多项技术并排放在一起,然后比较技术特性列表。在完成的时候,大多数参与者会达成一致,因为他们不愿与评估小组中嗓门最大、最强硬、势力最大的派别争吵。显然,这不是作出选择的最有效方法。
确定关键业务问题
将业务问题转换为对组织需要的实际评估意味着应该着重考虑以下问题:
· 组织内的技术力量
· 不抑制程序员的开发环境
· 不对应用程序构成限制的运行时环境
· 运行时环境和开发环境的许可证问题和法律问题(可以说是规则的规则)
· 可找到有经验的开发人员
· 能找到收费合理、有见地的咨询服务公司
· 对组织的客户的影响
· 对厂商或技术合作伙伴的影响
· 组织的文化和策略
· 组织的上级管理联盟
· “大亨们”是否在讨论并购或合并
· 谁是决策者
这些业务问题通常比其他问题(比如采用每个会话一个线程的并发模型还是每个请求一个线程的并发模型)更重要。您可以走进老板的办公室,向他发表足可以充斥一次软件研讨会的技术观点,但是除非您强调了组织的需要,否则您的观点将显得非常空洞。
走近 CORBA
现在让我们用 CORBA 来解决这些问题。CORBA 是一种开放的行业标准,它由参加“对象管理组织 (OMG)”的 600 多家公司支持。这些公司包括硬件或软件公司、电信公司、金融公司、大学、医药研究所和政府机构。OMG 并不实现它们自己的规范;它们依靠厂商的实现,这些实现有助于将竟争导向到功能、性能、价格和令人鼓舞的质量这些方面。OMG 本身是一个充满生机的、活跃的组织,它的许多官员在各种会议上撰写文章并发表演讲。只要留心观察,您就会发现,他们总在关注将会对他们的组织及您的组织造成影响的各种变化。
除上述优势外,CORBA 还提供了高度的交互操作性。这保证了在不同的 CORBA 产品基础之上构建的分布式对象可以相互通信。因此,大型组织不需要让所有开发工作都使用同一 CORBA 产品。OMG 一直都在辛勤工作,以期通过加强 CORBA 规范来提供更高级别的可移植性。要知道,当厂商分化它们的产品以及开发人员添加限制时,他们往往会忘记源代码的可移植性这回事。应当指出,可移植性比以前的版本中有了实质性的提高,并且随着因特网互操作性协议 (IIOP) 形成强大的用户集团和众多联盟,可移植性将越来越好。
我们很快变会转入 Java 技术的讨论,但我们首先应将语言看作一个整体。信息系统有一个确定的规则,那就是不确定性本身。Java 编程语言可能是今天的语言,但谁知道下一种更好的语言哪一天会使它暗然失色呢?应当知道,就在此时此刻,有人正在他们的车库里加紧编写一种更好的语言。因此,CORBA 的语言无关性有助于您长期遵守信息系统环境中的这一法则。CORBA 支持一种经典的、稳定的对象模型,此模型将继续步入未来。它一直在凭借最新的语言不断发展,并将继续向极好的新语言(如 Python)扩展。
Java 编程语言及其他
此环境中可确定的另一点是它的复杂性,这是我们所不愿看到的,Java 技术正好可以派上用场。它能很好地满足需要,因为它的简单性将有助于使开发免于陷入底层的复杂性中。Java 语言在许多方面隐藏了一些基本的细节,但就整体而言,Java 技术改善从分析、设计到执行的整个过程。
在分析和设计阶段要尽可能统一和明确地表示您的系统和设计模式,这一点极为重要。“统一建模语言 (UML)”事实上已成为表述系统设计的标准语言。UML 的开发是协作方面的一个极好的故事。UML 的标准化过程也相当引人注意。UML 的标准化是通过 OMG 来实现的。OMG 认识到需要一种标准建模语言,以跨语言和环境表示分布式对象模型。Java 对象模型与 CORBA 对象模型几乎完全相同,从而很容易将您的 UML 设计映射到一种实现,同时也很容易将 UML 图映射到“Interface Definition Language (IDL)-to-Java 语言”实现。
经过分析和设计阶段的对象将有一些接口,这些接口定义其他组件将如何访问您的服务。现在需要将那些接口转换为一种实现语言。Java 技术同样使这一步很容易实现。“IDL-to-Java 语言”映射相当直接。Java 语言和 IDL 分别提供一个接口关键字,这个关键字有助于说明用每种语言表示的数据类型之间的紧密关系。Java 技术提供一种从 Java 接口到 IDL 的反向映射。不管选择哪个方向,您都应该用 IDL 表示您的接口;这使您能够使用所有其他语言与 IDL 之间的映射,并为您提供了为了在以后具有更大自由而必需的语言无关性。
体系结构案例
除了设计之外,系统的体系结构也很重要。许多系统体系结构问题涉及对现有系统(数据库、旧有系统、可用对象或可能用其他语言编写的应用程序)的包含。Java 和 CORBA 语言使得可以(而不是禁止)将这些强有力的系统带给更多的用户。这是一种解放 -- 许多系统可能许多年来都在发挥着作用。实际上,当完成将这些系统带入新世纪的所有 Y2K 以前的工作以后,在今后几年它们可能还将存在。由于其编译后的字节码结构,Java 语言将使您很容易创建和分发可移植的对象,而 CORBA 允许您将这些对象与您计算环境的其余部分进行连接和集成。
我并非想用 Java 和 CORBA 技术来排斥 Java 语言的远程方法调用、Enterprise JavaBeans 技术、Microsoft 的 DCOM 甚至 DCE。Java 和 CORBA 语言最自由的方面是,OMG 正在加紧工作,以确保 CORBA 对象能够与大量的对象交互。当然,这些连接中有些较难实现,并且最终可能会很脆弱。请记住,这些类型的连接常常是因体系结构、成本、时间或业务等原因而建立的。
Joshua Chamberlain,带着对规则与自由的见解,在他在 Bowdoin 大学的演讲过后七年加入北方军队。他功勋卓著,并证明是一位有力的领导者,所以格兰特将军选择让他接受北弗吉尼亚军队的正式投降。他向投降的士兵行行军礼以示尊敬的行动,是恢复被四年内战破坏的破碎混乱的联邦系统的新基础中的第一块基石。理解规则和自由的平衡为任何类型的组织中的参与者提供了一个清晰的图像,使他们能够清楚地理解他们所面对的系统和要避免的混乱之间的相互影响。
小结
异构系统环境中的互连和通信应该是我们的目标。我们要选择使用的规则应该允许自由地将现有的系统与新系统一起使用以满足组织的需要。在这个变化的世界中,当变化被强加给您的组织时,这些规则应该提供最大的灵活性。这些规则提供了一个限制我们的自由的操作范围,这样就不易陷入盲目开发的境况。
谢谢您耐着性子读完了本文。我不是修饰学教授,我只是一个程序员,所以在下一期我们将开始讨论一个简单的示例 -- 使用 Java 技术实现 CORBA 客户机和服务器。
探索CORBA技术的应用领域---- 一个简单的CORBA/Java示例
首 页 CORBA文章 CORBA规范 CORBA实现 CORBA应用 相关技术 CORBA论坛
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、撰稿人兼讲师
2000 年 7 月
内容:
IDL 接口
IDL 编译器
服务器
客户机
结论
6 月份,我们谈过您为什么要使用 CORBA 和 Java 技术。本月,我要通过一个可用的简单示例,让您开始探索 CORBA 技术的许多领域。不过,别忘了我们的目标是,创建这样一种分布式应用程序:使驻留在一台计算机上的客户机能向运行于另一台计算机上的服务发出请求。我们不想为诸如硬件或操作系统软件等细节问题操心,而只是想让这种服务能响应客户机的请求。
IDL 接口
全部 CORBA 结构是从一个接口开始的,理解接口的最佳方法就是想像我的汽车,对,我的汽车。虽然您不熟悉它,但如果我对您说:“开上我的车,带些三明治回来当午餐”,恐怕您就不会怀疑自己能不能驾驶我的汽车。您可能想知道它停在哪里,以及开它是否安全,但是您会确信开我的车与开您的车差别不大。这是因为,在各种汽车当中,人与汽车之间的接口已高度标准化了。我的轿车和您的跑车之间可能会有一些差异,但汽车的油门踏板、刹车和方向盘的安装都是标准的,您一定能轻松快速上路。
因为 CORBA 与语言无关,所以它依靠一种接口定义语言 (IDL),来表达客户机如何向实现接口的服务发出请求。我们的接口就是一个方法:add()。这个方法将取两个数(两个 IDL 的 long 型数)并返回这两个数之和。下面是我们的接口计算程序:
清单 1. calcsimpl.idl
module corbasem { module gen { module calcsimpl { interface calculator { long add(in long x, in long y); }; }; };};
这个接口中的 IDL 关键字有:module、interface、long 和 in。IDL 使用关键字 module 来创建名称空间,并且此关键字准确地映射为 Java 关键字 package。运行 IDL-to-Java 编译器时,生成的 Java 文件将会存到名为 calcsimpl 的子目录中。IDL 关键字 interface 完美地映射为 Java 接口,并代表一种抽象类型,因为两者都只定义您与对象通讯的方式,而不涉及对象的实现。IDL 关键字 long 是一种基本的整数类型,它至少映射为一个 4 字节的类型,这种类型在 Java 代码中就是 int。
想一想执行远程方法调用的机制,您就会发现定义参数传递的方向(客户机到服务器、服务器到客户机或者双向传递)是多么的有意义。在 IDL 操作中,这些方向用 in、out 和 inout 关键字来声明,每个参数都必须声明方向,以便使对象请求代理程序 (ORB) 知道该参数的去向。这会影响到为发送而进行的参数打包、参数解包以及内存管理。ORB 对参数了解得越多,它的效率就越高。关键字 in 表明 long x 和 long y 是从客户机传递到服务器。
图 1. 参与 CORBA 请求的各个部分
IDL 编译器
需要 IDL 编译器吗?您可能已经有了 ORB 供应商和 IDL-to-Java 编译器。但如果还没有,您从哪里获取呢?这里有好多,而且有些还可以免费下载。我推荐 Object Oriented Concepts, Inc. 的 Orbacus ORB。如果不将其用于商业目的,它还可以免费下载,而且完全符合 CORBA 2.3 规范。另外一个可试用 60 天的编译器是 Inprise 的 Visibroker,也完全符合 CORBA 2.3 规范并且可下载。如想获得这两种产品,请参阅参考资料。
接口定义以后,必须在 ORB 供应商提供的 IDL-to-Java 编译器上运行。IDL 编译器是一种精巧的实用程序,它生成 IDL 的 stub 和 skeleton 以及其它支持文件。生成的这些源文件,大部分将增强 CORBA 标准中定义的特定 IDL 类型的打包功能。编译器将生成大部分网络探测 (plumbing),这在分布式系统中非常重要。在最基本的级别中,IDL-to-Java 编译器只是一个按 CORBA 2.3 规范的定义来实现从 IDL 到 Java 语言映射的程序。手动生成这些代码既枯燥又费时,还容易出错;IDL-to-Java 编译器会处理这一切,所以您就不用操心啦;同时,它会用一定的规则约束您,并强制您执行封装。IDL-to-Java 编译器将把 CORBA-land 规则强加给您的系统。
输入下面的命令,从 Orbacus 执行 IDL-to-Java 编译器,把所有生成的文件都放在 CLASSPATH 的输出目录下。
清单 2. 调用 IDL-to-Java 编译器
jidl --output-dir c:\_work\corbasem calculator.idl
生成了什么呢?这个命令生成了构建实现所需要的全部 Java 源文件。IDL-to-Java 编译器可确保所定义的接口遵守 CORBA 规范的规则。
图 2. IDL-to-Java 编译器文件生成
下面是这些文件:
· calculator.java - 这个文件叫标记接口文件。CORBA 规范指出这个文件必须扩展 IDLEntity,并且与 IDL 接口同名。这个文件提供类型标记,从而使这个接口能用于其它接口的方法声明。
· calculatorOperations.java - 这个文件内含 Java 公共接口 -- calculatorOperations。规范指出,这个文件应该与具有 Operations 后缀的 IDL 接口同名,并且这个文件内含此接口映射的操作标记。上面定义的标记接口 (calculator.java) 可扩展这个接口。
· calculatorHelper.java - 设计 helper 类的目的是,让所需要的许多内务处理功能脱离我们的接口,但又随时可用到实现过程中。帮助程序文件含有重要的静态 narrow 方法,这种方法使 org.omg.CORBA.Object 收缩为一种更具体的类型的对象引用;在这种情况下,将是一个计算程序类型。
· calculatorHolder.java - holder 类是一个专门化类,是为了需要通过引用来传递参数的任意数据类型而生成的。这个示例中将不使用 holder 类,但我们将会在以后的栏目中经常见到它。
· calculatorPOA.java - skeleton 类为 CORBA 功能提供了请求-响应探测的一大部分。生成 calculatorPOA.java,是因为缺省方式下的实现是基于继承的,如果我们选择基于委托的实现方式,输出就会不一样。这些主题将在以后的栏目中详细介绍。
· _calculatorStub.java - 顾名思义,这是一个 stub 类。您的客户机将需要这个类来进行工作。
服务器
现在生成的文件必须在服务器上开始工作,用这个服务器实现我们的接口。所幸的是,大部分探测是适合我们的要求的,但别高兴得太早 -- 还有许多工作要做;就是说,所有这些文件都必须用在正确的地方。
让我们从 add() 方法的实现开始。
清单 3. SimpleCalcSvr.java -- add() 方法
SimpleCalcServant extends calculatorPOA { public int add(int x, int y) { return x + y; }}
请注意,我们的实现类扩展了已生成的类 calculatorPOA。从客户机发来一个请求时,该请求通过 ORB 进入 skeleton,skeleton 最终将调用 SimpleCalcServant,来完成请求并启动响应。我们的接口很简单,所以我们的实现也很简单。
服务器其余部分的实现,涉及如何围绕这个接口实现来设置 CORBA 体系结构,由于可移植性和灵活性方面的原因,许多这些调用要按 CORBA 规范执行。
我们需要完成的第一项任务是,详细说明要使用哪一个 ORB,然后予以初始化。下面的代码处理此任务:
清单 4. SimpleCalcSvr.java -- 初始化 ORB
java.util.Properties props = System.getProperties(); props.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORG"); props.put("org.omg.CORBA.ORBSingletonClass", "com.ooc.CORBA.ORBSingleton"); org.omg.CORBA.ORB orb = null; // 初始化 ORB orb = org.omg.CORBA.ORB.init(args, props);
初始化 ORB 时,需要准确地告诉它哪一个类将用作 ORBClass,哪一个类将用作 ORBSingleton 类。我们的实现将不考虑这些,但所有相关的探测则都将考虑这些。正如我前面所说的,这种情况下,我使用的是 Object Oriented Concepts, Inc. 的 Orbacus ORB,而 OOC 类在那两个 props.put() 调用中已给出。一旦填入了属性,props 就只作为一个参数传递给 ORB.init() 方法。实际情况可能不是这样;如果我们要把这个服务器移到另一个 ORB,不希望为服务器重新编码。所以,在理想情况下,我们宁愿改变一个配置文件,使之指向另一个 ORB 类,然后直接重新启动。
现在,ORB 已经到位并已初始化,并且实现也已经到位,只是尚未创建,此时,需要为实现创建一个完善的生存地点,而这可不像听起来那么容易,在一个分布式环境中,各个实现要求的环境可能略有不同。可以赋予实现许多特征。实现既可以是单线程的,也可以是多线程的;既可以是具有高度可伸缩性的对象池,也可以是单元素。这许多不同的服务器特征已产生了可移植对象适配器 (POA)。POA 使我们可以创建完善的环境,供我们的实现在其中驻留。所有符合 2.3 规范的 ORB 都会有一个根 POA,所有其它 POA 都是从根 POA 创建的。在这个简单示例中,我已将实现专用的代码分解为它自己的方法 runcalc()。
为实现创建一个环境将是我们的第一项任务,所以必须设置一个 POA。本来,CORBA 服务器使用基本对象适配器 (BOA),但是每个供应商的 BOA 都不一样,在最新版本的 CORBA 规范中,POA 已完全取代了 BOA。
清单 5. SimpleCalcSvr.java -- 设置 POA
// 从始终存在的 rootPOA // 设置可移植对象适配器 org.omg.PortableServer.POA rootPOA = org.omg.PortableServer.POAHelper.narrow( orb.resolve_initial_references("RootPOA")); org.omg.PortableServer.POAManager manager = rootPOA.the_POAManager();
从标题和定义可以看出,这是一个简单的示例。使用根 POA 而不创建新的 POA,将使事情变得简单。POA 管理器是一种封装了 POA 处理状态的对象,所以,我们使用 POA 管理器,将发给 servant 的请求排队。
还需要实例化实现:
清单 6. SimpleCalcSvr.java -- 实例化实现
// 创建计算程序接口的 servant SimpleCalcServant calcSvt = new SimpleCalcServant(); calculator calc = calcSvt._this(orb);
按照 CORBA 2.3 规范,所有 skeleton 均提供一个 _this() 方法,该方法使 servant 能得到目标 CORBA 对象的对象引用,servant 正是用目标 CORBA 对象来与这些请求相关联的。
完成实现的实例化以后,就必须把机制放到适当的位置,以便客户机能够找到它们。有许多不同的方法和服务可用来找到满足接口请求的对象。CORBA Service 定义 Naming Service 和 Trader Services,来专门帮助客户机查找对象,以处理请求。也可以通过方法调用来传递对象。
在这个示例中,我们将使用所有方法中最直截了当的一种 — 将对象引用写入一个文件,该文件将由客户机选取。对于所有的 ORB 来说,创建一个对象引用的字符串表示,或者反过来,创建由字符串到对象的引用,都是必备的功能。
清单 7. SimpleCalcSvr.java -- 编写对象引用
// 将对象引用写入一个文件 PrintWriter refstr = new PrintWriter( new FileWriter("calcref.ior")); refstr.println(orb.object_to_string(calc)); refstr.close();
最后要做的一件事,就是激活 POA,使客户机请求开始排队,并强制服务器输入其事件循环,以接收这些传入的请求。
清单 8. SimpleCalcSvr.java -- 激活 POA
// 使实现成为可用 manager.activate(); System.out.println("SimpleCalcSvr is running!"); orb.run();
客户机
如果您考虑一下正在发生的事件的机制,就会明白客户机和服务器实际上正是互为映像的。客户机将所有的参数打包以创建一个请求,然后以它自己的方式来发送这个请求。服务器只是将请求中的参数解包,执行运算,将返回值和输出参数打包,然后向客户机发回响应。客户机则将返回值和输出参数解包,然后继续处理。这样,客户机打包什么,服务器就解包什么,反之亦然。
这意味着您将会看到客户机和服务器具有相似的结构。客户机还必须创建并初始化一个 ORB。它可以是我们正在使用的 ORB,也可以是另一个供应商提供的 ORB;但是,不能是任意的 ORB,而应该是支持 IIOP 的 ORB,IIOP 是由对象管理集团 (OMG) 定义的、基于 TCP/IP 的互操作性协议。如果您的 ORB 比较旧,那么请小心,它可能无法与其它 ORB 通话。
首先,我们以相同的方式创建 ORB,就像创建服务器一样。
清单 9. SimpleCalcClient.java -- 初始化 ORB
java.util.Properties props = System.getProperties(); props.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORG"); props.put("org.omg.CORBA.ORBSingletonClass", "com.ooc.CORBA.ORBSingleton"); org.omg.CORBA.ORB orb = null; // 初始化 ORB orb = ORB.init(args, props);
看起来眼熟?应该是这样,它看起来与服务器完全一样。现在,客户机已经连接到了一个 ORB 之上,但我们的目标是调用一个服务,而这个服务是在系统中别的地方提供的,需要找到能响应请求的对象。在这个示例中,这意味着要从创建于服务器上的文件中获取一个对象引用。为了找到计算程序服务器,需要取得存储在这个文件中的对象引用的字符串版本,然后把它转换成对象引用,通过这个对象引用就可以进行调用了。
清单 10. SimpleCalcClient.java -- 获取对象引用
System.out.println("Getting reference from string..."); BufferedReader in = new BufferedReader( new FileReader("calcref.ior")  ; String ior = in.readLine(); in.close(); calculator calc = calculatorHelper.narrow( orb.string_to_object(ior));
请注意,这里使用了由 IDL-to-Java 编译器生成的 calculatorHelper 类。calcref.ior 文件含有一个对象引用,而不是含有计算程序引用。calculatorHelper 类有一个 narrow 方法,可用来将抽象类型集中到特定的计算程序类型。
仔细看一看计算程序 calc,它表示计算机空间中另外某个地方的一个服务器。最后必须做的一件事,就是调用 calc 上的方法 add()。
清单 11. SimpleCalcClient.java -- 调用 add()
System.out.println( calc.add(2,3)  ;
结论
已经讨论了很多内容,不过请想一想,都学到了什么。我们的客户机与服务器是完全隔离的,客户机不知道服务器在什么样的硬件上运行,使用的是什么操作系统,它是用什么语言编写的,它是不是多线程的,还有,它位于何处 — 是在隔壁,还是距离半个地球之遥。它只知道一点,即如果它调用 calc 中的 add(),就会得到可以指望的响应。
提供服务的情形全都是这样,电话或电力公司也是如此。当您拿起电话的时候,您所期望的是听到拔号音,然后您的呼叫能畅通连接,您并不在乎电话是通过光缆传输的还是通过卫星转发的,同样的情况在信息产业中也正在成为现实。多亏有了 OMG 和这个基本结构,我们才得以加进这个既简单而又非常有说服力的例子。
接触CORBA内幕:IOR、GIOP和IIOP
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、作家和讲师
2000 年 8 月
内容:
网络
IOR
存根
打包:GIOP 和 CDR
IIOP
结束语
7 月,我们创建了一个简单示例 -- SimpleCalc。这个示例不值得多说;它是单一方法 add(),接受两个 IDL long 型变量,返回一个 long 型变量。讲授与学习 CORBA 的一个难题是:已知它是基于分布的客户机和服务器,从一开始它就变得很复杂。必须立即与网络打交道。所以,让我们现在就来谈谈网络。
网络
如果将话题深入一点,将发现许多都值得探讨,但又很简单。我们简单的计算器服务器设计成被远程调用,CORBA 专门确保让我们不必担心客户机环境和服务器环境之间的差异。客户机对服务器的远程调用是根据远程过程调用 (RPC) 协议生成的,该协议自 20 世纪 80 年代就存在。RPC 是由各种通信模型经过多年测试得到的结果 -- 这是已经在产品环境中测试过的可靠且真实的技术。我们现在使用的 CORBA 模型就基于该模型。
图 1. 网络
可互操作对象引用 (IOR)
让我们跟踪方法调用。客户机必须首先获得计算器的实例。它通过使用 calculatorHelper.java narrow() 方法来达到这一目的。ior 是可互操作对象引用 (IOR) 的字符串表示,是从文件 calcref.ior 中检索到的。这个文件是由服务器写的,以便客户机可以定位并连接到它。对 orb string_to_object() 的方法调用只取得 ior 字符串,并将它转换成对象引用。以下是客户机中的代码,SimpleCalcClient.java:
calculator calc = calculatorHelper.narrow(orb.string_to_object(ior));System.out.println("Calling into the server");System.out.println( calc.add(2,3)  ;
IOR 中有什么?IOR 是一个数据结构,它提供了关于类型、协议支持和可用 ORB 服务的信息。ORB 创建、使用并维护该 IOR。许多 ORB 供应商提供了一个实用程序来窥视 IOR 的内部。OOC (Object Oriented Concepts, Inc.) 的 Orbacus 附带 IORDump.exe,如果您使用 Visibroker,它为您提供了 PrintIOR.exe。也有一些网站为您分析 IOR;可在 Xerox Parc 站点上找到我使用的一个实用程序。因为正在使用 Orbacus,我将对在 SimpleCalc 示例中创建的 IOR 运行 IORDump。得到以下输出:
C:\_work\corbasem\_sources\calcsimpl>iordump -f calcref.iorIOR #1:byteorder: big endiantype_id: IDL:corbasem/gen/calcsimpl/calculator:1.0IIOP profile #1:iiop_version: 1.2host: 192.168.0.10port: 4545object_key: (36)171 172 171 49 57 54 49 48 "1/2 1/4 1/2 9610" 48 53 56 49 54 0 95 82 "05816._R"111 111 116 80 79 65 0 0 "ootPOA.."202 254 186 190 57 71 200 248 ".?.9G.." 0 0 0 0 "...."Native char codeset: "ISO 8859-1:1987; Latin Alphabet No. 1"Char conversion codesets: "X/Open UTF-8; UCS Transformation Format 8 (UTF- " "ISO 646:1991 IRV (International Reference Version)"Native wchar codeset: "ISO/IEC 10646-1:1993; UTF-16, UCS Transformation Format 16-bit form"Wchar conversion codesets: "ISO/IEC 10646-1:1993; UCS-2, Level 1" "ISO 8859-1:1987; Latin Alphabet No. 1" "X/Open UTF-8; UCS Transformation Format 8 (UTF- " "ISO 646:1991 IRV (International Reference Version)"
IOR 中嵌入的是 type_id、IIOP 版本、主机地址和端口号,以及对象键。type_id 字符串是接口类型,众所周知,它是资源库标识格式。基本上,资源库标识是接口唯一的标识。这个标识可以是 DCE UUID 格式(COM 程序员比较熟悉它)或者是您指定的本地格式。IIOP 版本将帮助 IOR 阅读器(通常是 ORB)正确了解 IOR 是哪种格式,因为 OMG 总是改进规范,每个版本的阅读方法都与以前版本略有不同 。主机地址和端口号将让我们接触到与期望的对象通信的 ORB。对象键和许多其它资料都是按特定于服务的信息的 OMG 标准构建的。这是帮助 OTB 支持服务器的特定于服务的数据。例如,这些专用 IOR 组件可以编码 ORB 类型和版本,或者帮助支持 OMG 安全服务的 ORB 实现。以上大多数信息指定了字符代码集转换,这样客户机和服务器就能够互相理解。
如果通过 Xerox Parc IOR 语法分析器运行 IOR,将得到以下输出:
IIOP_ParseCDR: byte order BigEndian, repository id, 1 profile_IIOP_ParseCDR: profile 1 is 124 bytes, tag 0 (INTERNET), BigEndian byte order(iiop.c:parse_IIOP_Profile): bo=BigEndian, version=1.2, hostname=192.168.165.142, port=4545, object_key=<...1961005816._RootPOA......9G......>(iiop.c:parse_IIOP_Profile): encoded object key is <<?AC?9610058 16%00_RootPOA %00%00屎?G? %F8%00%00%00%00>(iiop.c:parse_IIOP_Profile) non-native cinfo is object key is <#AB#AC#AB196100 5816#00_RootPOA #00#00#CA#FE#BA #BE9G#C8#F8#00# 00#00#00>; no trustworthy most-specific-type info; unrecognized ORB; reachable with IIOP 1.2 at host "192.168.165.142", port 4545
IOR 中最主要的部分是帮助客户机连接到服务器的那些部分。可以在 Xerox Parc IOR 阅读器的输出中看到这些部分。但是,其它许多信息是 Orbacus 专有的,其它 IOR 阅读器不能解释它。这些专用部分是作为附加到 IOR 的数据序列出现的,并且只有构建 IOR 的 ORB 才懂得这些数据。
存根
现在,我们知道 IOR 带来了什么功能。IOR 的目的就是使客户机能够连接到服务器,以便它能够完成方法调用。客户机必须用 Add 方法将 IOR 转换成它可以调用的实际对象。这是通过使用从 IDL 编译器中生成的两个 Java 文件来完成的。客户机将首先使用 calculatorHelper 对象将 IOR 的范围缩小到 _calculatorStub 代理对象。
以下是 Orbacus 附带的 jidl 编译器生成的 narrow() 方法:
public static calculator narrow(org.omg.CORBA.Object _ob_v) { if(_ob_v != null) { try { return (calculator)_ob_v; } catch(ClassCastException ex) { } if(_ob_v._is_a(id())) { org.omg.CORBA.portable.ObjectImpl _ob_impl; _calculatorStub _ob_stub = new _calculatorStub(); _ob_impl = (org.omg.CORBA.portable.ObjectImpl)_ob_v; _ob_stub._set_delegate(_ob_impl._get_delegate()); return _ob_stub; } throw new org.omg.CORBA.BAD_PARAM(); } return null;}
可以看到,它最主要的任务是创建一个新的 _calculatorStub 对象。_calculatorStub 充当驻留在服务器上的实际计算器对象的代理对象。如果您不了解代理模式,我将非常乐意向您介绍“四人组”Design Patterns 一书。实际上,代理模式无非是创建一个代表或充当另一个实际对象的替身的对象,另一个对象将最终将调用或执行服务。代理模式是一种重要且常用的模式。在所有分布式设计中都会用到它。我敢打赌,您肯定用过这种模式,只不过从没有称您的设计为代理模式。
一旦创建了 _calculatorStub,它就代表客户机的计算器接口。add 方法在服务器中实现,而该服务器在 IOR 中定义的地址上的电脑空间中运行。至此,这就所调用的 add() 方法。这里,需要注意两点:首先,我们必须以 _calculatorStub 的形式调用 add 方法。其次,请注意客户机将中断直到调用返回,就像其它同步方法调用一样。这是一种请求响应协议,它模仿单进程应用程序。编程客户机,然后使用该请求响应协议执行客户机就像用库和 API 调用创建的常用编程开发环境一样普通自然。这并不表示您不能使用异步调用;您当然可以生成那种类型的调用。我将在以后的专栏文章中讨论那些话题。
打包:GIOP 和 CDR
至此,在体系结构中,我们已成功欺骗了客户机,使它相信服务与它在一起。但事实并为如此,并且在以后几步中,我们必须将数据和方法调用铸造成一种形式,它允许在网络上继续该调用,并且可以在另一端使用该调用。这并不是无关重要的,且这种模型已经问世好几年了。您也许已经多次见过 OSI 模型了,在图 2 中,您将看到 OSI 模型,旁边就是 OMG 所使用的模型。
图 2. OSI 的结构 vs. GIOP 协议堆栈
客户机调用接口操作时,它必须将操作数据(in 和 inout 参数)发送到服务器。此时的困难在于将数据转换成公共格式,这样服务器抽取操作数据时不会误解或错误对齐数据。因为服务器可以是任意数量不同的平台,我们应该预计到客户机和服务器之间的体系结构差异。CORBA 通过严格定义如何将数据转换或打包成公共格式来处理这种问题。然后在连接的另一端重新组成或解包数据。这是通过用最基本的结构表示数据来完成的,最基本的结构就是字节流,也就是八位元流。
CORBA 规范将八位元流定义成“一种抽象表示法,通常对应于要通过 IPC 机制或网络传输来发送到另一个进程或另一台机器的内存缓冲区”。IDL 八位元准确映射成 Java 字节。它们都是 8 位值,客户机或服务器都不打包这种值。将这些参数转换成八位元序列的根本目的是产生用于信息交换的基本结构。
现在,我们应当窥视 _calculatorStub 生成的代码的内部信息。请记住这不是由我编写的 -- 它是由 Orbacus 附带的 IDL-到-Java 编译器 jidl 生成的。
| yuxq 回复于:2003-09-17 18:49:03
| //// IDL:corbasem/gen/calcsimpl/calculator/add:1.0//public int add(int _ob_a0, int _ob_a1) { System.out.println("Inside _calculatorStub.add()"); while(true) { if(!this._is_local()) { org.omg.CORBA.portable.OutputStream out = null; org.omg.CORBA.portable.InputStream in = null; try { out = _request("add", true); out.write_long(_ob_a0); out.write_long(_ob_a1); in = _invoke(out); int _ob_r = in.read_long(); return _ob_r; } catch(org.omg.CORBA.portable.RemarshalException _ob_ex) { continue; } catch(org.omg.CORBA.portable.ApplicationException _ob_aex) { final String _ob_id = _ob_aex.getId(); in = _ob_aex.getInputStream(); throw new org.omg.CORBA.UNKNOWN("Unexpected User Exception: " + _ob_id); } finally { _releaseReply(in); } } else { org.omg.CORBA.portable.ServantObject _ob_so = _servant_preinvoke("add", _ob_opsClass); if(_ob_so == null) continue; calculatorOperations _ob_self = (calculatorOperations)_ob_so.servant; try { return _ob_self.add(_ob_a0, _ob_a1); } finally { _servant_postinvoke(_ob_so); } } }}
要注意的部分是包含 _request()、write_long() 调用,和 _invoke() 及随后的 read_long()。对 _request() 的调用使用要调用的方法名称,和显示是否需要响应的布尔 (boolean) 值。它返回 CORBA 规范指定的 org.omg.CORBA.portable.OutputStream 对象。对于可移植性,这是必要的,因为 Java 经常被下载,并且依赖于它运行的机器上的公共库。对于 ORB 是这样,对于 IO 也是这样。因此,CORBA 规范为 Java 语言定义了比其它语言更广泛的可移植类型集合。
通用 ORB 间协议 (GIOP)
通用 ORB 间协议 (GIOP) 用来为这个由不同计算机及其各种体系结构组成的凌乱世界中传送消息定义结构和格式。如果使用 GIOP 的结构和格式,并将它们应用于 TCP/IP,那么就得到 IIOP。GIOP 有两个版本:1.0 和 1.1。这就意味着我们的消息根据其符合的 GIOP 版本可能有不同的格式。
至此,我们必须看一下 GIOP 以了解请求在变成正确格式化的 CORBA 请求时所要经历的操作。尽管我们将仔细研究请求,响应只是请求的镜像图像。如果您知道请求的工作原理,那么您就能了解响应。
GIOP 请求消息分成三部分:GIOP 消息头、GIOP 请求头和请求主体。GIOP 消息头表示这就是一条 GIOP 消息。它包含 GIOP 版本、消息类型、消息大小,然后根据您是使用 1.0、1.1 还是 1.2,包含字节次序 (GIOP 1.0) 或一个位标志字段,该字段包括字节次序以及一些保留位标志。GIOP 1.1 添加了对消息存储碎片的支持,GIOP 1.2 添加了双向通信支持。更新的版本都是向下兼容的。
公共数据表示 (CDR)
公共数据表示 (CDR) 是 CORBA 调用中将使用的数据类型的正式映射。客户机生成请求时,它不必知道请求要发送到什么地方,或者哪一台服务器将响应该请求。CORBA(作为规范)和 GIOP(作为规范的一部分,定义消息结构和传送)被设计成允许实现一个接口的可能的多种不同服务器之一来响应请求。规范必须定义如何打包操作中的数据,这样所有可能的服务器都可以抽取参数并调用远程操作,并且数据转换不会产生多义性。这种转换问题的典型示例就是指针。客户机中的指针对于在另一台机器上运行另一个进程的服务器意味着什么?毫无意义。或者,变量如何在使用不同寻址方案(大尾数法,小尾数法)的机器间发送?这些数据类型必须转换成服务器能够理解并使用的流。显然,CORBA 规范在公共数据表示方面是十分详细的。这是我们不必涉足的细节层次,但如果您想要了解详细信息,请阅读规范或 Ruh、Herron 和 Klinkeron 合著的 IIOP Complete 一书。
一旦包装了所有数据,就将使用 IOR 中的信息来创建连接。您可以区别 IOR 的结构,通常必须使用 TCP 作为传送机制。但是,也可以使用其它传送(再次提醒,请参阅 CORBA 规范以获取详细信息)。ORB 守护程序负责查找 IOR 指定的对象实现,以及建立客户机和服务器之间的连接。一旦建立了连接,GIOP 将定义一组由客户机用于请求或服务器用于响应的消息。客户机将发送消息类型 Request、LocateRequest、CancelRequest、Fragment 和 MessageError。服务器可以发送消息类型 Reply、LocateReply、CloseConnection、Fragment 和 MessageError。
如果我们扯开 GIOP 消息,它看上去就像:
0x47 0x49 0x4f 0x50 -> GIOP, the key0x01 0x00 -> GIOP_version0x00 -> Byte order (big endian)0x00 -> Message type (Request message)0x00 0x00 0x00 0x2c -> Message size (44)0x00 0x00 0x00 0x00 -> Service context0x00 0x00 0x00 0x01 -> Request ID0x01 -> Response expected0x00 0x00 0x00 0x24 -> Object key length in octets (36)0xab 0xac 0xab 0x31 0x39 0x36 0x31 0x300x30 0x35 0x38 0x31 0x36 0x00 0x5f 0x520x6f 0x6f 0x74 0x50 0x4f 0x41 0x00 0x000xca 0xfe 0xba 0xbe 0x39 0x47 0xc8 0xf80x00 0x00 0x00 0x00 -> Object key defined by vendor0x00 0x00 0x00 0x04 -> Operation name length (4 octets long)0x61 0x64 0x64 0x00 -> Value of operation name ("add")0x20 -> Padding bytes to align next value
您应该了解大概情况了。这种消息流是高度结构化的。它也必须是,为了客户机可以创建服务器可以转换成实现的消息 -- 不管实现如何运行,或在哪里运行。服务器也必须为在响应客户机时使用的返回值和参数执行相同操作。此消息格式在 OMG 成就中非常重要,因为它可以实现可移植性和互操作性目标。这种可移植性将给予您我们在第一篇专栏文章中谈到的自由。您无需关心硬件、数据库或编程语言。只要关心您的信息就行了。
IIOP
我们还没有彻底结束。GIOP 是 CORBA 方法调用的核心部分。GIOP 不基于任何特别的网络协议,如 IPX 或 TCP/IP。为了确保互操作性,OMG 必须将 GIOP 定义在所有供应商都支持的特定传输之上。如果有详细和简洁的消息规范,则不会提供互操作性,因为所有供应商使用不同的传送机制来实现这个互操作性。因此,OMG 在最广泛使用的通信传输平台 -- TCP/IP 上标准化 GIOP。GIOP 加 TCP/IP 等于 IIOP!就这么简单。
需要使用已发布对象服务的客户机将使用 IOR 中的值来启动与对象的连接。我们已经绕了一个圈子,又回到了 IOR。IOR 对于 IIOP 是至关重要的,任何要对某个对象调用方法的客户机都要将“请求”消息发送到 IOR 中详细说明的主机和端口地址。在主机上,服务器进程在请求进入时会侦听端口,并将那些消息发送到对象。这就要求服务器主动侦听请求。
生活有阴阳两面,每件事都有缺点,互操作性和 IIOP 也不例外。OMG 推出和运行了 IIOP,对比 ORB 供应商它们自己的服务器上实现此功能,并且没有服务器方可移植性的年代,这是一大改进。但如果要求服务器是位置无关的,我们应该做什么?如果主机和端口值嵌入 IOR 中,每当您将对象从一个服务器移到另一个服务器,以均衡负载时,这个问题就会突然出现。可喜的是这个问题已经解决了;但又有一条坏消息,每家供应商的解决方法都不同。
结束语
现在,将负载均衡话题留到将来讨论。如果您在几年前有 CORBA“经验”(也就是说在一段时期内),而现在从事另一项研究,我相信您将感到惊喜。CORBA 规范已经获得很大进步,可以确保您为一个 ORB 编写的服务器代码可以移植到运行另一个 ORB 的另一台服务器上。解决方案非常简单,并且以经典的协议为基础。客户机和服务器间的标准交换语法基于某些 OMG 详细说明的需求。OMG 通过使用网络寻址协议 (IIOP) 独立于消息传递协议 (GIOP),为其规范创建了更多功能。这也确保随着信息工业的变动(的确发展很快),CORBA 仍能跟上它的步伐。我最喜欢的是,对于我们刚讨论的关于如何使之成功完成的 CORBA 对象调用,我不必编写代码!在 ORB 中和用 IDL 标准化接口的功能中已经概括了网络测量和打包的详细信息。
OMG 接口定义语言
----定义分布式服务的能力
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、作家和讲师
2000 年 9 月
内容:
IDL 基本类型
用户定义的类型
常数定义
用户异常
数组、序列、字符串
名称和作用域
接口
结束语
一切都要从接口定义语言 (IDL) 开始。当我们采用 RPC 或 COM 技术以及 CORBA 技术来编写分布式系统时都需要它。在各种情况下,接口定义语言提供了将对象的接口与其实现分离的能力。IDL 提供了抽象,它提供了将事务与其具体实现分离的概念。它还为我们提供了一套通用的数据类型使得我们可以使用它们来定义更为复杂的类型。我们将采用所有这些数据类型来定义分布式服务的功能。IDL 的另一个好处是它剥离了编程语言和硬件的依赖性。本文探讨了 OMG IDL 的内置类型和关键字。
IDL 是一种规范语言。它允许我们从实现中剥离对象的规范(如何与它交互)。这是一个约定:“客户机女士,如果您要调用这个方法,请传送这些参数,然后我,服务器先生,将把这个字符串数组返回给您。”使用这个接口的客户机程序员不知道接口背后的实现细节。
OMG IDL 看上去很像 C 语言。这就很容易将这两种语言以及它们的关键字做比较。但是,这种相似性只流于表面。每种语言的目的是完全不同的。我们在讨论这种语言时,您已经记住 OMG IDL 的目的是定义接口和精简分布对象的过程。
IDL 基本类型
OMG 接口定义语言有一些看上去应该很熟悉的基本类型。以下就是这些内置类型的表:
表 1. IDL 基本类型
类型 范围 最小大小(以位为单位)
short -215 到 215-1 16
unsigned short 0 到 216-1 16
long -231 到 231-1 32
unsigned long 0 到 232-1 32
long long -263 到 263-1 64
unsigned long long 0 到 264-1 64
float IEEE 单精度 32
double IEEE 双精度 64
long double IEEE 双字节扩展浮点数 15 位指数,64 位带符号小数
char ISO Latin-1 8
wchar 从任何宽字符集编码宽字符,如 Unicode 依赖于实现
string ISO Latin-1,除了 ASCII NUL 以外 可变化
Boolean TRUE 或 FALSE 未指定
octet 0 到 255 8
any 自己描述的数据类型,可表示任何 IDL 类型 可变化
整数类型
OMG IDL 的整数类型非常简单。虽然它没有提供 int 类型,但它不会受到 int 在不同平台上的取值范围不同所带来的多义性的困扰。然而,IDL 确实提供几种整数类型,2 字节 (short)、4 字节 (long) 和 8 字节 (long long) 的整数类型。
所有这些整数类型都有相应的无符号数类型。这对于 Java 程序又产生了问题,因为 Java 编程语言不支持无符号数类型。尽管这不是 OMG IDL 的特性,它还是在 Java-to-IDL 的映射中创建了一种独有的局面,我们将在下个月的专栏文章中讨论 Java-to-IDL 的映射。但在此之前,您已经考虑如何将 IDL 中的 unsigned short 映射成一种 Java 类型。使用 Java short 还是 Java int?它们各自的利弊是什么?这些是语言映射的作者必须努力解决的问题,并且这是一个很好的练习,可以帮助您为阅读下一篇专栏文章做好准备。
浮点类型
OMG IDL 浮点数类型 float、double 和 long double 遵循 IEEE 754-1985 二进制浮点数算术的标准。目前,long double 用于巨大数字,您也许会发现您的特殊语言映射还不支持这种类型。
char 和 wchar
我们都使用相同的术语,字符集就是字母或其它构成单词的字符以及其它本机语言或计算机语言的基本单元的集合。编码字符集(或代码集)是一组明确的规则,它建立了字符集和集合的每个字符与其位表示法之间的一一对应关系。
处理 char 时,必须记住 OMG IDL 必须分两个层次处理字符集。首先必须明确规定从哪个字符集生成 IDL 定义。词法约定(表示 IDL 文件的关键字、注释和文字的字符记号)规定 ISO 8859.1 字符集表示 IDL 文件中的字符。是的,连 IDL 都必须有一个标准字符集,它将构建在这个字符集上。ISO 464 定义了空字符(null)和其它图形字符。
接着,OMG 必须处理从一个计算机系统到另一个计算机系统之间的字符传输。这意味着可能涉及到从一个字符代码集到另一个字符代码集的转换,这取决于语言绑定。在上个月的专栏文章中,我们对 Orbacus Object Reference 执行了 IORDump 操作,并且发现了以下信息:
Native char codeset: "ISO 8859-1:1987; Latin Alphabet No. 1"Char conversion codesets: "X/Open UTF-8; UCS Transformation Format 8 (UTF- " "ISO 646:1991 IRV (International Reference Version)"Native wchar codeset: "ISO/IEC 10646-1:1993; UTF-16, UCS Transformation Format 16-bit form"Wchar conversion codesets: "ISO/IEC 10646-1:1993; UCS-2, Level 1" "ISO 8859-1:1987; Latin Alphabet No. 1" "X/Open UTF-8; UCS Transformation Format 8 (UTF- " "ISO 646:1991 IRV (International Reference Version)"
可以看到,IOR 可以包含代码集信息,以在转换时协调首选代码集和可用代码集。
解决了所有问题后,您应该知道 OMG IDL char 是一个 8 位变量,可以用两种方法表示一个字符。首先,它可以从面向字节的代码集编码单字节字符,其次,当在数组中使用时,它可以从多字节字符集(如 Unicode),编码任何多字节字符。
Wchar 只允许大于 8 个字节的代码集。规范不支持特殊的代码集。它允许每个客户机和服务器使用本机的代码集,然后指定如何转换字符和字符串,以便在使用不同代码集的环境之间进行传输。
Boolean
这里没有什么可以多说的 -- Boolean 值只能是 TRUE 或 FALSE。
Octet
octet 是 8 位类型。因为保证了 octet 在地址空间之间传送时不会有任何表示更改,因此这就使它变成了一种非常重要的类型。这就表示您可以发送二进制数据,并且知道当将它打包时,它的形式仍然相同。其它每种 IDL 类型在传输时都有表示变化。例如,根据 IOR 代码集信息的指示,char 数组会经历代码集转换。而 octet 数组却不会。
any 类型
IDL any 是一种包含任何数据类型的结构。该类型可以是 char 或 long long 或 string 或另一种 any,或者是已经创建的一种类型,如 Address。any 容器由类型码和值组成。类型码描述 any 的值部分中的内容是什么。
如果您拥有 C++ 经验,则可以将 any 看作是自我描述的数据类型,它类似于 void *,但更安全。如果有 Visual Basic 经验,可以将 any 看作类似于 variant。当我们讨论 IDL-to-Java 映射时,any 类型的结构和它如何对用户定义的类型产生作用将变得一目了然。
用户定义的类型
基本类型是必不可少的;它们为接口定义提供了构件块。OMG IDL 为您提供了定义您自己的类型的能力,这可以帮助减少复杂程度并且让您可以根据基本类型组成更精巧的数据类型。这些复杂的类型可以是枚举、结构和联合,或者您可以使用 typedef 来创建类型的新名称。
命名的类型
应该使用 typedef 创建新的类型名称,这将帮助解释接口或保存输入。
例如,您也许想在方法 PresentWeather(..., in float Pressure, ...) 中传送气压值。如果在该方法中使用 typedef float 语句,这将使该方法更具可读性。
typedef float AtmosPressure;
在 C++ 中,typedef 关键字表示类型定义,实际上别名也许是更为精确的术语。对于 OMG IDL,也许是这样,也许不是,这取决于其所映射到的实现语言。CORBA 规范不保证 short 的两种 typedef 是兼容的和可互换的。
在文体上,应注意不要为现有类型创建别名。您应该尝试创建不同概念的类型,它将为您的 IDL 添加可读性和可扩展性。最好是明确定义一次逻辑类型,然后在整个接口中不断使用该定义。
枚举
OMG IDL 枚举是将名称附加到数字的一种方法,从而读取代码的人就可以了解到更多的含义。OMG IDL 版的枚举看上去象 C++ 版本的枚举。
enum CloudCover{cloudy, sunny};
CloudCover 现在就成为可以在 IDL 中使用的一种新类型。由于在一个枚举中可以有最多 232 个标识,OMG IDL 保证枚举被映射到至少 32 位的类型。规范中没有规定标识的有序数值,但它规定了将保持顺序。因此,不能假设 cloudy 永远拥有序数值 0 -- 某些语言映射可能将 1 赋值给它。但可以确保 cloudy 小于 sunny。
如果认为 IDL 的目的是定义跨各种系统的接口,那么不指定序数值是明智的。您只将值发送到服务器。即, "cloudy"。在服务器空间中,cloudy 可以由 0、1 或如何实现语言规定的值表示。某些实现语言不允许您控制序数值,而 C++ 允许。OMG IDL 不允许空的枚举。
结构
struct 关键字提供了将一组变量集中到一个结构的方法。一旦创建了,struct 表示可以在整个接口定义中被使用的新类型。
struct Date { short month; short day; long year;};
定义 struct 时,要确保所创建的类型是可读的。不要在不同的名称空间中创建几个不同的同名结构,这只会使 IDL 的用户搞糊涂。
识别联合
OMG CORBA 规范将 IDL 联合描述成 C 联合类型和 switch 语句的混合物。IDL 识别联合必须有一个类型标记字段用于确定在当前实例中使用哪个联合成员。像 C++ 一样,一次只能有一个联合成员是活动的,并且可以从其识别名称来确定该成员。
enum PressureScale{customary,metric};union BarometricPressure switch (PressureScale) { case customary : float Inches; case metric : default: short CCs;};
在以上示例中,如果识别名称是 metric,或者使用了不能识别的识别名称值,那么 short CCs 就是活动的。如果识别名称是 customary,那么 float 成员 Inches 是活动的。联合成员可以是任何类型,包括用户定义的复杂类型。识别名称类型必须是整数类型(short、long、long long 等,以及 char、boolean 或 enumeraton)。
常数定义
在 IDL 中定义常数的语法和语意与 C++ 一样。常数可以是整数、字符、浮点数、字符串、Boolean、octet 或枚举型,但不能是 any 类型或用户定义的类型。这里有一些例子:
const float MeanDensityEarth = 5.522; // g/cm^3const float knot = 1.1508; // miles per hourconst char NUL = '\0';
可以用十进制、十六进制或八进制记数法定义整数常数:
const long ARRAY_MAX_SIZE = 10000;const long HEX_NUM = 0xff;
对于指数和小数,浮点字符使用常用的 C++ 约定:
const double SPEED_OF_LIGHT = 2.997925E8;const double AVOGADRO = 6.0222E26;
字符和字符串常数支持标准换码序列:
const char TAB = '\t';const char NEWLINE = '\n';
只要没有混合的类型表达式,就可以在常数说明中使用算术运算符。
用户异常
IDL 允许创建异常来指出错误条件。IDL 用户异常类似于一个结构,在这个结构中,异常可以包含所选类型的任意多错误信息。最终是从方法中使用异常。这里有一个例子:
exception DIVIDE_BY_ZERO { string err;};interface someIface { long div(in long x, in long y) raises(DIVIDE_BY_ZERO);};
异常将创建名称空间 -- 因此,异常中的成员名必须是唯一的。异常不能当作用户定义类型的数据成员使用。OMG IDL 中没有异常继承。
数组、序列和字符串
每次只传送一个元素是可以的,但我们通常有一个列表或向量或矩阵的信息要在客户机和服务器之间来回传送。数组几乎是所有编程语言所共有的类型,但一种语言的数组与另一种语言的数组实现通常是不同的。OMG IDL 开发者面临的挑战是创建一组数组类型,它可以轻易地被映射到实现语言中。这种需求产生了 IDL array 和 sequence。string 类型是一种特殊的序列,它允许语言使用它们的字符串库和优化。
数组
OMG IDL 有任意元素类型的多维固定大小的数组。所有数组都必须是有界的。数组非常适合于与拥有固定数量元素的列表一起使用,而这些元素通常都是存在的。例如:
// bounded and unbounded array examplestypedef long shares[1000];typedef string spreadsheet[100][100];struct ofArrays { long anArray[1000];};// unbounded arrays NOT ALLOWED// typedef long orders[];
必须指定数组维数,并且它们必须为正的整型常量来表示。IDL 不支持在 C 和 C++ 中的开放数组,这是因为没有指针支持。必须出现 typedef 关键字,除非指定的数组是结构的一部分。
在许多实例中,CORBA 规范所没有提及的内容与它提及的内容是一样重要的。规范不以任何方式、形态或形式指定数组下标编排方法。这表示从一种实现语言到另一种实现语言的数组下标可以是不同的,这样您不能假定将数组下标从客户机发送到服务器时,服务器会调整并指向正确的数组元素。某些语言的数组下标从 0 开始,而其它的则是从 1 开始。
序列
在开发接口定义时,会大量使用序列。如果正在处理数据数组,其中许多值相同,那么序列就可以提供灵活性。
序列是变长向量,它有两个特征:元素的最大大小,在编译时确定,可以是无限的;长度,在运行时确定。序列可以包含所有类型的元素,不管是基本类型还是用户定义的类型。
序列可以是有界的,也可以是无界的。例如:
// bounded and unbounded sequence examplestypedef sequence<long> Unbounded;typedef sequence<long, 31> Bounded;
一个无限序列可以拥有任意多个元素,只会受到平台内存大小的限制。有限序列则有边界限制。这两种序列都可以不包含元素、用户定义的类型,但可以包含其它序列。
string 和 wstring
string 等价于 char 的序列,而 wstring 表示 wchar 的序列。作为 C 和 C++ 的折衷,OMG IDL string 和 wstring 可以包含任何字符,除空字符以外。char 或 wchar 约定确定了类型为 string 的元素大小由 8 个字节表示,wstring 类型的元素大小是 16 个字节或更多。
IDL 中的字符串很特殊,然而在大多数语言中字符串都很特殊。许多语言都用库和特殊优化来处理字符串处理。通过将字符串归到它自己的类型,OMG 允许语言映射使用特殊优化,这些优化不会与通用序列一起处理。
名称和作用域
所有 OMG IDL 标识都是区分大小写的。这意味着将把两个只有字符大小写不同的标识看作是彼此的重新定义。应该注意根据区分大小写的语言,所有的定义引用必须与定义的大小写相同。
IDL 作用域规则非常易于掌握。整个 OMG IDL 内容和通过预处理器伪指令传入的所有文件共同组成了命名作用域。任何未出现在某个作用域中的定义都是全局作用域的一部分 -- 只有一个全局作用域。在全局作用域中,以下定义组成了作用域:module、interface、struct、union、operation 和 exception。
module 关键字用于创建名称空间;这是其唯一目的。您定义的模块将创建一个逻辑组,模块的自由使用防止了全局名称空间的污染。根或全局空间被认为是空的,文件扫描中每次遇到模块关键字时,字符串 "::" 和其标识都会附加到当前根的名称后面。这就可以通过包括其它名称作用域来引用其它模块中的类型,如以下示例中的 Pennsylvania::river。
一个标识可以在一个作用域中定义一次,但可以在嵌套作用域中重新定义。下例将解释这些要点:
module States { // error: redefinition // typedef sequence<string> states; module Pennsylvania { typedef string river; interface Capital { void visitGovernor(); }; }; module NewYork { interface Capital { void visitGovernor(); }; interface Pennsylvania { void visit(); }; }; module NewJersey { typedef Pennsylvania::river NJRiver; // Error // typedef string Pennsylvania; interface Capital { void visitGovernor(); }; };};
每个内部模块(Pennsylvania、New York 和 New Jersey)都有一个接口 Capital 和一个操作 visitGovernor()。但它们并不相互牵连,因为它们在各自的模块中。当我们尝试在模块 States 中创建一个同名序列时,遇到了一个重新定义 'State' 的问题。重新定义 Pennsylvania 发生在已经将它介绍为 New Jersey 中 'NJRiver' 的作用域解析标识之后。请注意,我们在带有接口 Pennsylvania 的 New York 模块中没有发生错误,因为通过某些作用域解析标识介绍外部 Pennsylvania 模块。
接口
现在该定义接口了,它是我们学习 OMG 接口定义语言的首要原因。有一个好方法来理解 IDL 接口:它指定了服务实现和使用它的客户机之间的软件约定。让我们开始定义接口吧,这将运用我们所学到的 IDL 知识。由于这一切都与通信有关,就让我们看一些定义 Listener 和 Speaker 的 IDL。Listener 必须连接到 Speaker,然后 Speaker 将消息传送给 Listener。这是一个回调的例子。
// Thrown by server when the client passes// an invalid connection id to the serverexception InvalidConnectionIdException{ long invalidId;};// This is the callback interface that// the client has to implement in order// to listen to a talker.interface Listener{ // Called by the server to dispatch messages on the client void listen(in string message); // Called by the server when the connection // with the client is successfully opened void engage(in string person); // Called by the server when the connection with the client is closed void disengage(in string person);};// interface on the server sideinterface Speaker{ // Called by the client to open a new connection // Returned long is the connection ID long register(in Listener client, in string listenerName); // Makes the server broadcast the message to all clients void speak(in long connectionId, in string message) raises(InvalidConnectionIdException); // Called by the client to sever the communication void unregister(in long connectionId) raises(InvalidConnectionIdException);};
使用这个定义,我们定义了两个新的 CORBA 接口类型:Listener 和 Speaker。每个接口都有一些方法,它们将由连接的另一端使用。客户机将通过获取对实现 Speaker 接口的服务器对象的初始对象引用来启动连接。这个对象引用可以传送给客户机,或者可以从命名服务中检索到这个引用。最重要的是,客户机首先联系 Speaker。接着,客户机(即 Listener,因为它实现 Listener 接口)必须注册到 Speaker,并将引用传给 Listener 接口。这就使它们可以从 Speaker 处接收到消息。
要注意的一点是在 register 方法中 Listener 接口被当作一个类型使用。接口名称变成了类型,可以当作参数传送。看上去就像在传送 Listener 对象,但实际是一个对象引用。这是提供位置透明性的 CORBA 模型的另一个示例。
有一点值得注意,每个对象引用 (IOR) 仅指向一个接口。每个接口都披露一个或多个分布式对象的详细信息。我说“一个或多个”是因为可以有几千个对象实现分布式系统中的同一个接口。在这个示例中,Speaker 可以将消息发送到几千个 Listeners。因而在某种程度上,IDL 接口对应于类定义,CORBA 对象对应于类实例。
结束语
对于 OMG IDL,我只是介绍了其的皮毛。显而易见,OMG IDL 提供了一组内容丰富的内置类型和关键字,它们可以用来为与分布式系统中的对象的交互创建严密的描述。由于这种语言类似于 C 语言,您应该了解所有使 C 语言变得如此成功的描述功能。所有 OMG 服务定义都是用 IDL 编写的,证明 OMG IDL 的强大功能。所有垂直市场标准化努力(Financial、CORBAMed 等)都是用 IDL 编写的,这证明了其灵活性。
学习正确和有效地使用 OMG IDL 是您开始学习 CORBA 和编写优秀的分布式系统的良好起点。每件事都从 IDL 开始,如果您在项目开始时正确运用了 IDL,那么您成功的机会会成倍增长。
IDL-to-Java的映射(1):
怎样将离散的构件接口定义转换为 Java 元素
(本文摘自IBM developerWorks)
Dave Bartlett
顾问,作家,讲师
2000年10月
内容:
前提
IDL-to-Java 库
基本数据类型
holder 类
结论
这篇文章开始阐述 IDL-to-Java 的映射。这个月的专栏介绍基本的数据类型、结构和数据传递。下个月我们将会介绍更加复杂的类型。语言映射并非无足轻重,COBRA 规范中有很大一部分阐述的就是多语言映射。
COBRA 规范详细说明了接口定义语言(IDL)并详细说明了 IDL 到几种编程语言的映射,如 C,C++,ADA,COBOL,Lisp,Smalltalk 和 Java。IDL 的优势在于它完整而详细描述了接口及操作参数。 IDL 接口提供了开发使用接口操作的 Client 和实现接口的 Server 所需要的信息。当然,Client 和 Server 并没有在接口中编写,IDL 只是一种纯粹的描述性语言。 Client 和 Server 用完全意义上的编程语言来编写。IDL 语言映射应该为接口定义提供一致的、可移植的构架。那么这些映射的接口就可以在 Server 端实现,或者像其它方法一样由 Client 端调用。对于使用的每一种编程语言来说,都需要有一种映射来方便的转换已定义的 IDL 概念。 IDL 概念到 Client 语言结构的映射取决于编程语言的结构和能力。例如,一个 IDL 异常(exception)在不提供 exception 的语言中可能被映射为结构(structure),而在提供 exception 的语言中就可以直接映射为 exception。每种语言的特性和效率必须与之相适应。
IDL 到编程语言映射的前提
所有的语言映射都有近似的结构。它们必须定义语言中的表达方法:
· 所有的 IDL 基本数据类型
· 所有的 IDL 结构数据类型
· IDL 中定义的常量
· IDL 中定义的对象引用
· 对操作的调用,包括传递参数和接收结果
· 异常,包括当操作产生异常时的处理和访问异常参数的方法
· 属性访问
· ORB 定义的操作符号,如动态调用接口,对象适配器等等。
一个完整的语言映射允许程序员以对于某种特定编程语言来说很便捷的方式来访问所有的 ORB 功能。为了支持源代码的可移植性,所有的 ORB 实现都要支持某种语言的同一映射。
MotherIDL
在本文中将使用一个叫做 MotherIDL 的文件-- motheridl.idl。以上要求这一 IDL 文件都可以做到,它的目的就是示意并检验部分映射。你最好先浏览它一下,这样我们逐步讲述的时候你就会对它比较熟悉了。
为了检验映射,你需要一个 IDL-to-Java 的编译器。每个 CORBA ORB 都至少带有一个 IDL 到某种语言的编译器。它们大多针对 C++ 或 Java 编程语言,当然也有其它的。新增加的还有针对 Python 和 Delphi 的。本栏我们要使用 Object Oriented Concepts, Inc. 的 Orbacus ORB 所带的 JIDL。(见 参考资料) 当然你也可以使用任何 IDL 到 Java 的编译器,但是要确定它兼容 CORBA 2.3,因为如果编译器的版本较早的话你的结果可能有本质的区别。
现在首先要做的就是运行 IDL-to-Java 的编译器来编译 motheridl.idl.
jidl --output-dir . . \. . \. . MotherIDL.idl
这一步很奇妙,它在提供支持和通用 CORBA 通道的同时给出了一大堆 IDL 文件所映射的 Java 文件。
IDL-to-Java 的库结构
我们要学习的第一个映射是 IDL 关键词 module 到 Java 的关键词 package 的映射。这是一个简单的完全映射。如果在 IDL 文件中有关键词 module ,你的 IDL 到 Java 编译器将会用关键词 package 生成一个 Java 类的目录结构和库结构。(如果你对于 Java 程序中如何使用关键词 package 还有什么困惑的话,那你现在应该去复习一下了。)这一映射规定了从 IDL 生成的大部分结构,对 IDL 到 Java 的映射有着重要影响。
以 motheridl.idl 为例,你将在 IDL 文件中看到如下结构:
module corbasem { module gen { module motheridl { module holderexample { ... }; module conflicts { ... }; module basictypes { ... }; module constructedtypes { ... }; module holderexample2 { ... }; module MI { ... }; module MI2 { ... }; }; };};
这转换为以下的目录结构:
E:\_work\TICORBA\Projects\corbasem\gen\motheridl>dir /AD /S Volume in drive E has no label. Volume Serial Number is B415-7161Directory of E:\_work\TICORBA\Projects\corbasem\07/15/2000 01:41p <DIR> .07/15/2000 01:41p <DIR> .. 0 File(s) 0 bytesDirectory of E:\_work\TICORBA\Projects\corbasem\gen07/15/2000 01:41p <DIR> .07/15/2000 01:41p <DIR> .. 0 File(s) 0 bytesDirectory of E:\_work\TICORBA\Projects\corbasem\gen\motheridl07/15/2000 01:41p <DIR> .07/15/2000 01:41p <DIR> ..07/15/2000 01:41p <DIR> basictypes07/15/2000 01:41p <DIR> conflicts07/15/2000 01:41p <DIR> constructedtypes07/15/2000 01:41p <DIR> holderexample07/15/2000 01:41p <DIR> holderexample207/15/2000 01:41p <DIR> MI07/15/2000 01:41p <DIR> MI2 0 File(s) 0 bytes
将 IDL 映射为 Java 元素的一个基本思想是一致性。如同其它任何类库一样,它们必须存在稳定性,也就是说在修改库中的类时不必删除现有的方法;不改变接口。CORBA 在这方面应该做的更好,因为多个 ORB 提供商将要使用这一映射来生成类库。我们希望在不同 ORB 实现之间有着一致性;这意味着可移植性。如果 ORB 提供商A的 IDL 到 Java 映射在 org.VendA.Excep 的 package 中提供了 UserException ,而提供商B在 org.VendB.UtilTypes 的 package中也提供了相同的 UserException ,Client 或 Server 的代码将不能够移植。Client 或 Server 移动到另一个 ORB 时需要改变代码并重新编译。这可不是我们选择 CORBA 的原因!我们希望并要求可移植性;因此 OMG 规定了库结构。
在 Java 编程语言中,关键词 package 将 Java 类组成类库,并控制对类库中的构件的访问。我们的 Java 类库中将包含 Java 编程语言中需要编译的所有素材,这些我们已经在 IDL 中描述过了。但是为了不同提供商的类库之间的可移植性,必须定义类库结构或者类库包,并且严格遵守这些定义。只有这样 Client 才能依赖于代码并且保证不用在不同提供商的ORB之间移植时重写代码。
在 IDL 到 Java 映射的可移植性部分中规定了 API,它提供了库结构和最小功能集,使 Java ORB中可以使用可移植的存根和骨架。因为 Java 类经常被下载,它们往往来自独立于ORB提供商的代码,因此对于 Java 编程语言的互操作性需求就超过了其它语言。出于这些原因,定义存根和骨架使用的接口是最基本的要求。如果这些结构不加以定义的话,存根(或骨架)的使用将需要一个或两个方案。一个要求由 IDL 到 Java 编译器或ORB提供商提供的类似工具(或者与ORB所使用的兼容)生成存根(或骨架),以使生成的存根能够适合ORB提供商的类库结构,另一个方案要求下载存根或骨架时同时下载整个ORB运行时环境。我们不希望采用这两种方案中的任何一个。理想的情况是将 IDL 发送到 Client 端或者由 Client 下载,利用 Client 端选择的工具生成存根,并从它们的环境连接到我们的 Server。
因此,Java 语言映射高度依赖于标准的结构,它在一套标准的 Java 包中实现 -- org.omg.*。IDL 到 Java 映射中重要的一项就是包含 PIDL,本地类型和ORB可移植接口的压缩文件。它给出了包中确切内容的定义性声明。当然,如同这一行中的其它任何事一样,在不远的将来 IDL 到 Java 映射的版本也会发生改变。但是以你对目前的映射的理解,你一定会注意到将来的版本中任何可能的二进制不兼容性
| | yuxq 回复于:2003-09-17 18:50:38
| 基本数据类型
基本数据类型的映射是很直接的。 下表会让你了解到映射是多么简洁:
IDL 类型 Java 类型 异常
boolean Boolean
char Char CORBA: ATA_CONVERSION
wchar Char CORBA: ATA_CONVERSION
octet Byte
string java.lang.string CORBA::MARSHAL, CORBA: ATA_CONVERSION
wstring java.lang.string CORBA::MARSHAL, CORBA: ATA_CONVERSION
short Short
unsigned short Short large number mismatch ?test
long Int
unsigned long Int large number mismatch ?test
long long Long
unsigned long long Long large number mismatch ?test
float Float
double Double
long double **unmapped 现在还不清楚是否会增加这一类型作为新的基本类型或者是类库的补充,如java.math.BigFloat
要浏览练习所有这些基本类型的 module,请访问 motheridl.idl 的例子.
整型(Integer)
IDL 符号整型(signed integer)到 Java 类型的映射不存在任何问题。IDL 的 short 类型映射到 Java 的 short,IDL 的 long 映射到 Java 的 int,IDL 的 long long 映射为 Java 的 long。这些都是直接映射,不会给你带来什么麻烦的。
问题在于 IDL 的无符号整型(unsigned)。Java 编程语言没有 unsigned 类型,并且它所有的整数类型都是有符号的。在多数情况下这不会发生问题,但是当一个 IDL 无符号整数取值正好落在最高位所限制的取值范围中时,类型转换就会发生不匹配的错误。如果不检查并改正这种错误,转换为 Java 后的结果就会是一个负数,而不是一个接近无符号整数类型取值上限的数值。
例如,假设有一个从 IDL 接口返回的 unsigned short 类型值,这一类型的取值范围是0到65535。 Java 的有符号 short 类型能够接收的取值范围是-32768到32767。那么你可以看到,对于任何在32767到65535之间的值映射为 Java 的 short 以后将成为负数。这就造成了不匹配的障碍,必须进行测试。对于 unsigned short, unsigned long, 以及 unsigned long long 来说也是这样。
这意味着什么呢?首先,我建议以后写 IDL 定义时不要再使用无符号整型。这会使事情简单的多。其次,如果你在使用或者支持现有的 IDL 接口,那你必须测试 输入的无符号整数,并确保它在 Java 程序中被作为负数正确的处理,或者被拷贝到取值范围较大的变量类型中。
布尔型(Boolean)和8位字节型(octet)
这两种 IDL 类型到 Java 类型的映射也是直接映射。IDL 的布尔常量 TRUE 和 FALSE 映射到相应的布尔量 true 和 false。
IDL 的字节型 octet 长度为8位,它映射为 Java 的 byte 类型。
字符型(Character)和字符串类型(string)
字符类型的映射有一些困难。首先是所使用的字符集,其次是表示整个字符集所需要的编码位数。不同的语言有不同的字符,它们分别被国际标准化组织映射为不同的字符集。这些字符集代表了某种语言的字母或符号到数字的映射。一种语言中符号的数量决定了这种语言所需要的位宽。现在有8位和16位两种字符集。
IDL 的字符型数据用8位来表示字符集中的一个元素,而 Java 的字符型数据用16位无符号整数来表示 Unicode 字符。要正确的进行类型转换,Java CORBA 运行时环境验证了所有由 IDL 的 char 型映射来的 Java char 型的有效性范围。这一方向的映射没有什么困难,因为我们是把8位的数值映射为16位的数值,Java 程序碰到的任何问题都很容易处理,因为空间是足够的。然而,要把 Java 的 char 型映射为 IDL 的 char 型,Java 的 char 型就有可能会超出 IDL 使用的字符集所定义的范围。这种情况下就会产生 CORBA: ATA_CONVERSION 的异常。 IDL 的 wchar 仅仅是映射为16位字符集的 IDL 类型,它映射为 Java 的基本类型 char。如果 wchar 超出了字符集所定义的范围,将会产生 CORBA: ATA_CONVERSION 的异常。
IDL 的 string 类型映射为 java.lang.String。别忘了 IDL 的 string 是一个 char 的序列。这意味着 IDL 的 string 必须满足 IDL char 和 IDL sequence 的要求。因此,在编译过程中要进行字符串中的字符范围检查和字符序列的越界检查。字符范围非法会引起 CORBA: ATA_CONVERSION 的异常。越界会引起 CORBA::BAD_PARAM 的异常。对于 IDL 的 wstring 类型来说其注意事项和使用规则也是一样的。
浮点型(Floating-point)
因为OMG IDL 和 Java 的浮点型数据都遵从 IEEE 754-1985 Standard for Binary Floating Point Arithmetic,所以浮点型数据的转换没有问题。然而,目前 Java 编程语言中还没有对 IDL 的 long double 类型的支持。现在还不清楚 java.math.* 是否会增加这一类型作为基本数据类型或新的包,或者说什么时候会增加,也许会作为 java.math.BigFloat 吧。这一问题就留待以后修订了。
映射的合法性检查
我们可以用 Orbacus ORB 带有的 IDL 到 Java 编译器来运行我们的 motheridl.idl 文件:
jidl --output-dir . . \. . \. . MotherIDL.idl
运行结果所产生的 Java 接口定义在 UseAllTypesOperations.java 文件中给出。
这一生成的文件检查映射的合法性。所有的 Java 类型都像我们所预期的那样。唯一的诀窍大概就在于 inout 和 out 参数位置上的数据类型--它们都是 holder。
holder 类
在任何语言中,参数传递都是一个有趣的话题,当要把OMG IDL 这样独立于语言的体系结构进行语言映射时,它又是一个伤脑筋的问题。Java 程序总是采用传值的方式。这意味着要把原语传递到方法中时,会得到一个原语的本地拷贝。然而,如果方法的参数是 Java 对象的话,则不会传递对象本身而是对象的引用。因此,被传递的是引用的拷贝,但是这个引用通过值来传递。
CORBA 规定了 in 参数和返回类型采用"值调用"(call-by-value)的方式,而 CORBA 的 out 则是"结果调用"(call-by-result)。inout 的参数类型在输入服务器时通过"值调用"的方式来传递,而在输出时则采用"结果调用"的方式。 out 和 inout 的参数传递模式不能被直接映射到 Java 的参数传递机制。要支持 out 和 inout 的参数传递模式需要另外使用 holder 类。
IDL 到 Java 的映射为所有的 IDL 基本类型定义了 holder 类,它同时也作为用户定义类型的标准格式。IDL 到 Java 编译器可以为用户定义的类型生成 holder 类,以便以后在 Java 程序中实现这些参数模式时使用。 Client 生成并传递一个适当的holder Java 类实例,这个实例的值在 Java 程序中被传递给每一个 IDL out 或者 inout 参数。Holder 实例的内容(而不是实例本身)被调用它的程序所修改,Client 使用的是调用返回后(有可能)改变了的内容。
CORBA-Java 类库中提供了 IntHolder 的例子。要注意 Java 中 public int 的值,以及 _type() 方法的返回值--它的类型是 long。记住这点是因为 Java 的 int 映射为 IDL 的 long。
记住org.omg.CORBA 包中提供了所有基本 IDL 类型的 holder 类,并为所有已命名的用户定义 IDL 类型生成 holder 类,那些用 typedef 定义的除外。
对于用户定义的 IDL 类型来说,holder 类根据映射的(Java)类型名再附加一个 Holder 来命名。对于 IDL 基本数据类型来说,holder 类的名字就是数据类型映射的 Java 类型名,开头字母大写,并附加一个 Holder(例如,IntHolder)。
每个 holder 类都有一个来自实例的构造函数,一个默认的构造函数,以及一个公有的实例成员(value)),它是一个有类型的值。默认构造函数将值域设置为 Java 语言为不同类型所定义的默认值:boolean 为 false,numeric 和 char 类型为 0,string 为 null,对象引用也是 null。为了支持可移植的存根和骨架,holder 类也实现 org.omg.CORBA.portable.Streamable 接口。
结论
这里只是 IDL 到 Java 映射的第一部分。你应该认识到,用 Java 编程语言编写基于 CORBA 的应用程序和构件要求理解 IDL 到 Java 语言映射。CORBA 规范包括很多语言映射。它们在你工作的各方面中都会有所体现。你的代码的可移植性不仅取决于OMG的库结构,而且也取决于你自己所生成的库结构。映射的目的是将接口定义转化为某种特定语言的实现,这种转化可能会以牺牲定义的完美性来换取实际的可行性。正如我们在无符号整型的例子中所看到的,你必须保持一定的警惕性以保证你的应用程序能够正常工作。
语言映射是一种翻译,因为你在使用一种语言,而要把它转换为另一种同样可以工作并且可以理解为一种实现的语言。正如一些短语和结构没法在两种自然语言之间很好的翻译一样,有些结构也不太容易映射。我们必须找到解决的方案,虽然它们可能使映射变得更复杂,但是在实现时用起来更容易。holder 类就是这样;开始理解它可能要花些功夫,但是长远来看,它提供了一种通用的、一致的解决方案。
IDL-to-Java映射(2):
使用 IDL 映射创建组件接口
(本文摘自IBM developerWorks)
Dave Bartlett
顾问、作家兼讲师
2000 年 11 月
内容:
结构
枚举
联合
序列和数组
数组
序列
异常
Any
辅助
结束语
我们就本月的 CORBA 连接中更复杂的类型和辅助类的问题,来继续研究 IDL-to-Java 映射。
上个月,在 IDL-to-Java 映射的第一部分中,我们研究了基本数据类型、结构和数据传送。本月,我们将集中精力研究映射常数和结构,讨论某些更复杂的类型,例如,序列、数组、异常和 Any 类型。最后,将研究辅助类和它们的功能。
首先,应该提醒您我们正在使用接口定义语言,任何 IDL 的目的就是创建某些组件或服务器的接口。这意味着我们正在创建新的类型。因此,让我们从 interface 关键字的映射和使用 OMG IDL 创建新类型的机制开始讨论。
接口
我们将回到所有 IDL 文件的母板 -- MotherIDL.idl。我使用这个文件来研究 IDL-to-Java 映射各个方面。在 MotherIDL 中,有一个名为 FindPerson 的接口。显示如下:
清单 1. FindPerson 接口
interface FindPerson { Person GetByName(in string sName); Person GetBySSN(in string sSSN); };
我们现在只集中讨论接口和两个方法 GetByName() 和 GetBySSN()。关键字 interface 的确切含义是什么?仔细研究 OMG CORBA 规范,就会发现 interface 关键字后带有标识(本例中是 FindPerson)。FindPerson 是接口名称,并且它定义了合法的类型名称。只要标识符合 IDL 的语法,就可以使用这种类型的标识。将 FindPerson 作为方法参数或结构成员在 IDL 中使用时,一定要记住 FindPerson 表示支持 FindPerson 接口的对象的引用。
IDL-to-Java 映射的目的是生成 Java 编程语言中的接口引用的表示,以供 Java 程序员使用,而且客户机程序员和服务器程序员都可以使用。我使用以下命令行,使用 Orbacus ORB、JIDL 附带的 IDL-to-Java 编译器来运行 MotherIDL:
jidl --output-dir ..\..\.. motheridl.idl
由于 MotherIDL 中有许多内容,因此将会得到大量生成代码的文件。而我们对于放入 corbasem\gen\motheridl\holderexample 目录下的文件 FindPerson.java 和 FindPersonOperations.java 感兴趣。
FindPerson.java 称为签名接口。它很简单:
Listing 2. FindPerson.java
public interface FindPerson extends FindPersonOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { }
接下来如何?毫无结果!而事实上有很多,只不过是隐藏的。首先注意两个 Java 关键字:public interface。是的,Java 编程语言有自己的 interface 关键字。它很重要,因为它使映射既快速又顺畅。
Java 编程语言的 interface 关键字生成不提供任何实现的完全抽象类。然而,Java 中的 interface 关键字的设计意图是允许接口创建程序建立类的形式:方法名、变量列表和返回类型,但不允许建立方法主体。那么 GetByName() 和 GetBySSN() 在哪里呢?
方法签名在 FindPersonOperations.java 中。称为操作接口的 FindPersonOperations.java 是包含如名称、参数、返回类型和导致的异常等方法细节的文件。
清单 3. FindPersonOperations.java
public interface FindPersonOperations { public Person GetByName(String sName); public Person GetBySSN(String sSSN); }
这更像人们预期的接口文件。但为什么有两个接口文件?这是出于灵活性的考虑。以前版本的映射根据要使用继承还是使用授权实现接口来生成不同的文件。Java 编程语言不支持实现多继承,因此基于继承的实现并不总是最好的。授权与继承相比,是具有相同强大功能的的组合机制,当基于继承的方案不适合时,通常由它提供解决方案。这个版本的规范中,只有一种映射适用于接口。在 IDL-to-Java 编译器上有“连接”开关,因为这两种实现方法之间的区别需要一些下游调整。关于使用继承的实现和使用授权的实现的话题将留到下个月讨论,这里需要另外讨论一下设计模式。
(如果对继承和授权的详细介绍感兴趣,请阅读“四人组”所著的 Design Patterns 一书以获取详细信息。)可以说,映射生成了一系列文件,它们给了您选择适合需要的实现设计的灵活性。
常量
变量和常量 -- 它们是非常直截了当的。变量的值可以更改,常量的值却不能更改。在某些编程语言中,二者的差异很大 -- 在某些编程语言之间却是完全混淆的。例如,在标准版之前的 C 语言中,如果要创建常量,必须使用预处理器。这将在编译器作用域外对那些常数进行控制,因此不执行类型检查。当然,这种情况在 C++ 和最终在 C 中已改变,但可以看到,它并不象想像中的那样简单,关键是将常量映射到 OMG 支持的各种语言。
Java 编程语言处理常量的能力很强,尽管它不像 C++ 一样使用 constant 关键字。在 Java 编程语言中,常量必须是原语并用 final 关键字表示。定义时必须给定值。static 和 final 的字段只有一个不能更改的存储块。如果常量是对象引用而非原语,它必须一直指向同一个对象而不能指向另一个。
IDL-to-Java 映射以两种方式处理常量:在接口内声明的常量和在接口外声明的常量。在接口内声明的常量容易处理,因为它们简单地映射到签名接口类中的字段。在 MotherIDL 中,我有一个名为 ConstructedTypes 的模块。在那里我们创建了使用常量关键字的一系列测试。这部分的回顾,如下:
清单 4. 常量示例
// Constant examples // Constant inside an interface interface PhysicalConstants { const float EquatorialRadiusEarth = 6378.388; // km }; // A constant outside an interface const float MeanDensityEarth = 5.522; // g/cm^3
可以看到 EquatorialRadiusEarth 在接口内,MeanDensityEarth 在任何接口外,仅驻留在模块嵌套中。文件 corbasem\gen\motheridl\constructuredtypes\PhysicalConstants.java 由先前给定的 jidl 命令生成,看起来如下:
清单 5. PhysicalConstants.java
public interface PhysicalConstants extends PhysicalConstantsOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { public static final float EquatorialRadiusEarth = (float)(6378.38818359375D); }
这非常简洁。常量是在接口内定义的并且它符合接口的根本目的,即允许设计程序建立类的形式。现在接口的用户可以在接口实现的全过程中使用 EquatorialRadiusEarth 了。
但如果使用跨越几个接口的常量会如何呢?这对于 Java 编程语言是需要技巧的,因为事实上每样东西都是对象,创建新的类型就意味着创建新的类。这就是为常量 MeanDensityEarth 所做的一切。创建了新的接口类 MeanDensityEarth.java:
清单 6. MeanDensityEarth.java
public interface MeanDensityEarth { public static final float value = (float)(5.5219998359680176D); }
接口类 MeanDensityEarth 封装名为 value 的单精度浮点成员中的常量。请注意,这是个公共 (public) 接口,并且和 IDL 中定义的常量同名。其它 Java 代码使用这个类时,大部分 Java 编程语言编译器直接插入值。虽然这没有您想像的那么直接,最终由编译器插入值,Java 编程语言坚持它的设计原则 -- 每样东西都是对象。(好吧,几乎每样东西。)
结构
OMG 的 IDL 有可以用关键字 struct 表示结构的概念。基本上,您可以使用它将几个原语或复杂类型结合成一种类型。Java 编程语言没有 struct 关键字,如前所述,鼓励程序员将每样东西放入对象(例如,类)。由于这些原因,IDL 结构以相同的名称映射到 final 的类。类向 IDL 成员定序的字段提供实例变量,并向所有值提供构造器,并实现 IDLEntity。还提供了一个空构造器,可以让程序员稍后填充。
让我们回到 MotherIDL 的constructedtypes模块并找到结构 SurfReport 的定义:
清单 7. SurfReport 结构
// use of struct struct SurfReport { Weather presentWeather; waveHeightT waveHeight; waterTempT waterTemp; windDirectionT windDirection; windSpeedT windSpeed; };
本 IDL 结构映射成 Java 编程语言文件 SurfReport.java,可在 MotherIDL 的constructedtypes目录中找到它:
清单 8. SurfReport.java
final public class SurfReport implements org.omg.CORBA.portable.IDLEntity { public SurfReport() { } public SurfReport(Weather _ob_a0, short _ob_a1, short _ob_a2, char[] _ob_a3, short _ob_a4) { presentWeather = _ob_a0; waveHeight = _ob_a1; waterTemp = _ob_a2; windDirection = _ob_a3; windSpeed = _ob_a4; } public Weather presentWeather; public short waveHeight; public short waterTemp; public char[] windDirection; public short windSpeed; }
枚举
已证实枚举总是有用的。程序员经常使用枚举,并且它很容易理解。但我很难理解为什么 Java 编程语言不包含 enum 关键字。它容易编码,而且我确信它不会增加编程量,我很想念它。这种映射是如何发生的?让我们看一下 MotherIDL enum Weather:
清单 9. enum Weather
enum Weather{cloudy, sunny};
IDL enum Weather 将映射成 Java 编程语言类,使用与 enum 类型相同的名称 -- Weather。这个类声明一种值方法,它返回 Weather 的值、每个 enum 元素两个静态数据,整数转换方法from_int() 和 protected 构造器。下面显示了这个类:
清单 10. Weather 类
final public class Weather implements org.omg.CORBA.portable.IDLEntity { private static Weather [] values_ = new Weather[2]; private int value_; public final static int _cloudy = 0; public final static Weather cloudy = new Weather(_cloudy); public final static int _sunny = 1; public final static Weather sunny = new Weather(_sunny); protected Weather(int value) { values_[value] = this; value_ = value; } public int value() { return value_; } public static Weather from_int(int value) { return values_[value]; } }
请注意,public static final 数据成员中有一个是 int 型变量,并且在 IDL enum 元素名称前面加上下划线 (_)。打算在 switch 语句中使用这个成员。其它数据成员是 enum 类型(本例中,Weather)。它与 IDL enum 元素标签同名(本例中,cloudy 或 sunny)。
联合
实际上,判别联合只是一个为节省内存空间而设计的结构。联合将只引用基于鉴别器的几个数据成员中的一个。这些数据成员的类型可能不同。任何时候在内存中一次只会出现一个数据成员。例如,MotherIDL 定义了联合 BarometricPressure:
清单 11. 单元 BaraometricPressure
enum PressureScale{inches,cc}; union BarometricPressure switch (PressureScale) { case inches : float BarometerInInches; case cc : short BarometerInCCs; };
PressureScale 是英寸还是立方厘米决定了要使用的元素类型是 float 还是 short。Java 原本不支持联合,因此需要构建它。
IDL 联合的映射类似于所有结构的映射,只是添加某些方法来创建鉴别联合的功能。映射启动与 IDL 联合同名的 final 类。添加到这个类的是:
· 缺省构造器
· 名为 discriminator() 的判别器读方法
· 每个分支的读方法
· 分支修改方法
· 具有多个条件标签的每个分支的修改方法
· 如果存在缺省标签相应的分支修改方法
· 如果需要缺省修改方法
供应商的具体实现可能各不相同,但要符合规范,它们必须使用以上定义的方法。请参阅 BarometricPressure.java 以获取 Orbacus ORB 是如何实现 IDL 联合的示例。
序列和数组
每次通过 OMG IDL 接口传递多个元素时,要使用数组或序列。何时使用数组或序列以及它们之间的差异已在两个月前的 IDL 文章中讨论过。现在我们要讨论的主题是 IDL 序列和数组如何映射到 Java 编程语言。映射是类似的--带有一些细微的差异--但非常简洁。
让我们看一段从包含一对数组和一对序列的 MotherIDL 摘录的代码。
清单 12. 数组
// array examples typedef char MyString[105]; struct ofArrays { long shares[1000]; string spreadsheet[100][100]; }; // bounded and unbounded sequence examples typedef sequence<long> Unbounded; typedef sequence<long, 31> Bounded; interface testbound { void testStub(in Bounded inB, out Bounded outB); };
在本例 IDL 中,我们正在创建数组结构, 第一个是一维数组,第二个是二维数组,第三个是单个孤立数组 MyString[]。
下一步,创建了两个序列:一个无界限,另一个有界限。请记住,在 IDL 列中,数组不能是无界限的,而序列却可以。最后,将序列放入接口的方法。
数组
数组映射非常简单。不为 shares[] 或 spreadsheet[] 生成 java 类。然而,为结构 ofArrays 生成 java 类。让我们看一下 ofArrays.java:
清单 13. ofArrays.java
final public class ofArrays implements org.omg.CORBA.portable.IDLEntity { public ofArrays() { } public ofArrays(int[] _ob_a0, String[][] _ob_a1) { shares = _ob_a0; spreadsheet = _ob_a1; } public int[] shares; public String[][] spreadsheet; }
IDL 数组直接映射到 与 IDL 数组标识同名的 Java 数组。数组机制不需要 Java 编程语言支持。我用机制限定,因为 ofArray 生成三个类:ofArrays.java、ofArraysHelper.java 和 ofArraysHolder.java,而 MyString 生成两个类:MyStringHolder.java 和 MyStringHelper.java。上个月的专栏文章(研究了生成的 holder 类,很快我们将详细研究辅助类。但现在,请注意 IDL 中的数组是有界限的,ofArrays.java 中的数组没有界限。您认为在何处检查数组界限?没错,在结构的辅助类--ofArraysHelper.java 和 MyStringHelper.java 中。在接口方法中打包数组(或结构)以便作为参数使用时,检查它的界限,如果它们超出了界限,将从辅助类的 write() 方法中抛出 CORBA::MARSHAL 异常。
序列
序列同样工整地映射到 Java 数组。使用序列标识作为 Java 数组的名称。映射期间,凡是需要序列类型的地方(例如,方法参数), 使用序列元素的映射类型数组,如同我们在 IDL 数组示例中看到的。打包有界限的序列作为 IDL 操作时,检查它们的界限,如果数组超出界限将发出 IDL CORBA::MARSHAL。下列代码是 testbound 接口的接口操作类,该接口在它的 testStub() 方法中使用两个序列:
清单 14. testboundOperations 接口
public interface testboundOperations { public void testStub(int[] inB, BoundedHolder outB); }
异常
IDL 异常映射对于 Java 编程语言异常来说是如此工整,以至您会认为在设计IDL 异常之前曾经参考过 Java 异常。它是那么完美和不需更改的映射以至于简化了异常的使用。所以使用它们吧!
应该注意 IDL 异常映射与结构非常相似。它们被映射成提供异常和构造器的字段实例变量的 Java 类。CORBA 系统异常继承 java.lang.RuntimeException。通过扩展 IDLEntity 的 org.omg.CORBA.UserException 从 java.lang.Exception 继承用户定义的异常。
用户异常
在我们的示例中,创建用户异常的名称是 OilSpill:
清单 15. 用户异常 OilSpill
// user defined exception exception OilSpill { long leakage; };
运行 IDL-to-Java 编译器后,生成 OilSpill 类:
清单 16. OilSpill 类
final public class OilSpill extends org.omg.CORBA.UserException { public OilSpill() { super(OilSpillHelper.id()); } public OilSpill(int _ob_a0) { super(OilSpillHelper.id()); leakage = _ob_a0; } public OilSpill(String _ob_reason, int _ob_a0) { super(OilSpillHelper.id() + " " + _ob_reason); leakage = _ob_a0; } public int leakage; }
将 OilSpill 映射到指定成 final 并扩展 org.omg.CORBA.UserException 的 Java类,它有一个名为 leakage 的公有整型变量和三个构造器:缺省构造器、正常或满构造器、“非常完整”构造器。附加的“非常完整”构造器有附加的初始字符串原因参数,在调用基本 UserException 构造器前,将它并置到标识。同样生成普遍存在的 Helper 和 Holder 类。
系统异常
CORBA 规范带有整套标准 IDL 系统异常。使用 CORBA 若干时间后,您将十分熟悉某些系统异常。系统异常映射到扩展 org.omg.CORBA.SystemException 的 final 类。每个标准 IDL异常 的 Java 类名与它的 IDL 同名,并在软件包 org.omg.CORBA 中说明。本类包含 IDL 主要和次要异常代码和描述异常的字符串。缺省构造器为次要代码提供 0,为完整代码提供 COMPLETED_NO,为原因代码提供 ""。同样有构造器接受原因并在其它字段使用缺省值,和需要指定所有三个参数的构造器相同。org.omg.CORBA.SystemException 没有公共构造器,只示例化扩展它的类。
Any
正如几个月前我们讨论的 CORBA 连接,CORBA Any 是维护其类型的自描述数据结构。Any 让您在运行时使用类型-安全转换函数来抽取和插入预定义的 IDL 类型值。可通过在 ORB 上调用 create_any() 方法来获取 Any。Any 是很有用的,它是一个与其它类型完全不同的类型,像一个超类型。如何将该事物映射到 Java 语言?
IDL 类型 Any 带有自身的“免烫”映射的 Java 类 -- org.omg.CORBA.Any -- 已经出现 CORBA 库中。它是很健壮的类,包含插入和抽取预定义类型实例的方法。
Any 类同样带有处理非原始 IDL 类型的一对类属可流化方法。之前,我们研究了使用 interface 关键字或 struct 关键字来创建新类型。这些类型是非原始类型或用户定义类型,Any 类型必须同样能够处理这些类型。要这样做,需调用 create_input_stream() 方法创建包含 Any 值的流,使用方法 read_value() 从输入流读取 Any 值。要从另一个方向进入,要清空输出流,调用 create_out_stream() 并使用 write_value() 向输出流写入 Any 值。但真正起作用的是那些我们一直讨论的辅助文件。这些辅助文件是为所有以前的类型生成的。它们无处不在--只要查看由 IDL-to-Java 编译器生成的目录便知。每个用户定义的 IDL 类型生成一种辅助类,它包含一对将它与 Any 相互转换的方法。这些生成的辅助类和 CORBA 提供的 Any 类的组合成了非常灵活的机制。因此让我们进一步研究辅助类。
辅助
现在您不会对 CORBA 有如此多的内容而感到惊奇了。如果 OMG 映射器尝试将每件事放入一个生成的文件,它将变得过于庞大和笨重。因此,它们创建辅助类,它是一个用相应类型的名称并附加 Helper 作为名称的类。类型 "My" 将有称为 "MyHelper" 的辅助类。
提供的或生成的辅助类包含用不同方式操纵 IDL 类型的方法。辅助类提供客户机能用来操纵类型的静态方法。这些包含如上所述的 Any 插入和抽取的方法,获取库标识,获取类型代码,从流中读取类型或写入类型。对于映射的 IDL 接口,辅助类包含用来将 org.omg.CORBA.Object 的对象引用造型到基本辅助类型的 narrow() 操作。
结束语
此时,应该清楚要牢固和完全掌握 CORBA,需要切实理解编程语言或用来实现 CORBA 组件的语言。在上两个专栏文章中,我们已经研究了 IDL-to-Java 映射,但还有几个其它映射,例如--C、C++、COBOL、ADA 和 Smalltalk 等等(甚至未提到 Java-到-IDL 映射,这是个新课题,与我们这里涉及的有些相反,它使 RMI 开发人员能够将他们的接口用于 CORBA 客户机)。我确信还遗漏了一些,并且 OMG 总是添加更多语言(例如,Python 和 C# )的提议。映射对于理解 CORBA 及其组件如何系统组合是重要的。
要成功对 Java 语言使用 CORBA 需要彻底理解 IDL-to-Java 映射;但是因为要彻底理解映射而付出的努力会在您的项目和 CORBA 的成就上得到巨大回报。
基于CORBA的三层B/S结构
黄启春
(浙江大学人工智能研究所 杭州 310027)
摘要:比较分析了4个典型的三层B/S结构模型,说明Applet-CORBA对象,Database结构,在企业管理应用系统中的应用优势,接着对该结构的Client和应用Server层(中间层)进行了简要说明。
关键字:CORBA B/S结构 Applet。
1.引言
随着互联网应用软件的发展以及组件技术的出现,软件体系结构逐渐从原C/S结构方式向更适合互联网应用的B/S结构模式,两层的C/S结构也逐渐转向三层或多层结构。对此我们对当前一些流行的结构做了比较分析。在client端我们考察了HTML和Applet的技术,在应用server端我们主要考察了Servlet和CORBA对象的技术,如图1所示。
HTML设计的文档表示格式与平台无关,不受限于特定的硬件和软件环境,并且具有很高的运行效率。
图1 通过Java来实现浏览器/服务器结构
Applet是一个Java程序,它存放于网络中的Web服务器上。Applet不能单独运行,它必须从一个HTML网页中启动,在浏览器中运行。
Servlet为开发者提供了一个简单的机制来扩展Web服务器的功能和存取处理业务数据。Servlet是一个基于组件的平台独立的Web应用,可以被看作一个运行于服务器端没有界面的Applet。因为Servlet完全由Java编写,它能调用所有Java的API,包括用JDBC API访问业务数据库,并且享有Java具有的可移植、可重用和防止系统崩溃的功能。目前在Servlet的基础上,扩展形成了JSP技术,它可以支持HTML和XML网页,并能非常容易的集成静态模板数据与动态内容。
在我们的工作中,我们主要分析了如下四种具有典型代表作用的结构:
HTML—Servlet—Database
HTML—Servlet—CORBA对象—Database
Applet—Servlet—Database
Applet—CORBA对象—Database
2.四种结构比较
2.1 HTML—Servlet—Database
采用Client和Server分别用HTML和Serlet技术来实现的方案具有以下四个优点:
⑴ HTML的显示速度快,代码简单,可以获得对硬件要求很低的高效的瘦客户端。
⑵ 由于HTML和Servlet采用http协议通信,降低了对网络通信协议和硬件的要求,并且很容易将client从局域网延伸到Internet,无需很多附加的工作。
⑶ HTML和Servlet均能方便的实现跨平台。
⑷ Servlet可以在服务器端进行业务逻辑处理,并且和HTML通信以及访问数据库,能够完成基本功能。
当处理的业务逻辑不是非常复杂并且对用户交互界面的要求很简单时,这些优点足以使我们选用这种方案。但是,在复杂的业务系统中,这种方案的缺点会迅速显现出来,主要有以下几点:
⑴ 由于HTML功能比较弱,不能表达较复杂的界面需求。这一点,有时可以通过使用JavaScript得到部分的改善。
⑵ Servlet是CGI的改进,因此不可避免的包括了CGI通信方式的缺点,无法完全享受到面向对象编程的优点;
⑶ 开发时调试困难,只能通过打印某些运行信息,通过运行结果和打印的信息来推测。
2.2 HTML—Servlet—CORBA对象—Database
这是JBuilder根据一个IDL利用Wizard生成的程序框架。相对于第一种结构,因为CORBA对象的引入,具有更加强大的中间层业务处理能力。恰当的分配Servlet和CORBA对象所承担的业务功能,在特定的应用背景下会取得较好的系统运行性能。但是从开发费用和开发效率方面考虑,很多情况下,完全可以把CORBA所作的工作合并到Servlet中,或者把Servlet所作的工作合并到CORBA中,没有必要多加一层结构。
2.3 Applet—Servlet—Database
因为Applet功能比HTML强大,在Client端采用Applet克服了HTML在定制用户交互界面时存在的弱点,可以完成许多一般HTML不能直接完成的工作。这种方法的缺点是Applet下载速度慢,不适合在目前拥挤的Internet上应用。
2.4 Applet—CORBA对象—Database
在图2中,我们给出了Applet-CORBA对象-Database结构的框架性描述。Client在运行时从Web服务器下载Applet程序,然后在client的浏览器中运行Applet。用户与系统的交互操作由这些Applet中的客户对象来承担。当用户对象需要调用中间层对象服务时,由ORB代理执行。这样对于客户对象来讲,只要像调用本地方法一样调用对象服务就可以了,至于代理对象如何进行实际操作对客户对象是透明的。Client的ORB利用IIOP协议与应用server的ORB通信,传递Client的对象服务请求。应用server上的业务对象收到其ORB的请求后,执行的相应的业务处理,在这期间业务对象有可能通过数据库存取对象访问数据库。概括的讲这种结构充分享受到了CORBA体系结构的优点:
⑴用Applet可以定制丰富的用户交互界面,满足复杂应用系统的需求;
⑵由于应用中间层的业务对象可以选用功能强大的对象语言实现,例如C++,从而具备处理复杂业务计算的能力和良好的运行性能.
⑶由于CORBA提供了Client和Server的对象通信框架,开发人员只需致力于业务系统对象的开发,并能方便、透明地调用分布系统中的各种对象服务;
⑷因为CORBA提供开放框架,系统的维护和功能扩展变得十分方便,增加新的功能时,不用修改以前的程序。
3. 如何选择适合应用软件特点的三层结构
在我们考虑系统的结构方案的时候,我们应该从几个方面考虑,包括在特定平台能够实现的功能范围、运行性能、开发环境的方便程度以及该平台的实用性以及发展趋势。
经过前面对几个典型结构平台的分析,我们认为对于低带宽、功能简单的应用,HTML-Servlet-Database的结构是最佳解决方案,因为它对软硬件的要求低、有良好的运行效率并且符合未来软件网络化、跨平台的发展特点。而对于高带宽、功能复杂、需要对系统留有扩充余地的应用,例如企业ERP管理软件,Applet-CORBA对象-Database的结构是理想的解决方案。
首先,ERP系统的高度复杂性和集成性,要求系统具备丰富的表达能力,虽然HTML的访问效率较高,但是其表达能力有限,而Applet却能基本满足这一要求。
其次,采用CORBA实现分布式程序,可以让用户集中精力于业务实现,而不必花太多的时间在分布实现的技术细节上,与自定义协议的实现方案相比,CORBA节省了开发时间。
第三,当前企业管理软件的应用范围大部分还是在LAN内,所以Applet的下载时间并不会对总体效率带来太大的影响。由于CORBA本身具有很好的在互联网上运行的机制,一旦互联网速度得以大大改善,这种结构的系统很容易推广互联网而不需作任何大的改动。
最后,从维护的角度考虑,这种方案很好的体现了三层结构带来的很好的可维护性。因为客户端除了浏览器,基本上不用安装其它相关的软件,并且业务逻辑主要几种于中间层,对客户端的维护工作量非常之小。在服务端的维护也很方便,这是因为CORBA对象之间良好的接口定义机制,只要用新的CORBA对象实现替换旧的CORBA对象实现,而在C/S模式下要在每一个客户端安装新的程序。
4. APPLET-CORBA对象结构分析
4.1 对client的要求
如前所述,Client层是三层结构的用户交互层,针对企业管理软件的特点和发展趋势,我们认为client层必须具有如下几个特性:
⑴ 要有方便、友好的客户交互界面。因为企业管理软件的复杂性,client必须功能足够大,所以其开发必须有强有力的技术支持。
⑵ 为了client具有良好的网络运行效率,应该力求足够小,即瘦客户端。
⑶ 需要具有跨平台的能力。
⑷ 承担一定的业务逻辑。
第1、2、3点是很容易理解的,而第4点似乎违背了业务逻辑运行于中间层对象的原则。现在我们讨论第4点特性要求,同样的,我们列出几点主要的理由:
⑴ 软件角度分析,在Applet-CORBA对象-Database结构下,开发Applet的Java语言具备良好的计算程序编程能力,一些简单的业务计算完全可以由client程序来完成。
⑵ 硬件角度分析,即使采用市场上较差的计算机作为client,其配置足以执行企业管理系统中一些并不复杂的计算。
⑶ 从各层对象之间的服务调用和通信数据量角度分析可以降低网上通信量和服务对象的繁忙程度,系统应用于Internet时,这一点更加重要。
⑷ 从用户界面友好性的角度分析可以避免一些因对象服务调用产生的时延,从而获得更快的响应用户操作,同样当系统应用于Internet时,这一点也更加重要。
⑸ 从系统开发的角度分析可以将client和应用server上对象间的接口数量控制在一定的范围,从而加快开发的速度并且获得一定的可维护性。
4.2 中间层应用server
中间层负责处理业务逻辑,其面向对象性的设计具有极好的可扩充性。根据中间层对象所承担的任务,他们主要可以被分为两大类:
⑴ 业务对象。
⑵ 数据存取对象。
业务对象的任务范围为从处理简单的client服务请求到复杂的业务计算。数据存取对象的任务是访问各种数据源——本文中主要指各种数据库。这些数据源分布在系统中,或同构,或异构,它们对于client完全透明。
由于CORBA提供了众多的服务,业务对象服务器的功能得以增强。例如利用CORBA的事件服务,业务对象能在client提出要求之前通知一些系统故障,避免不必要的网络传输,从而提供系统的容错能力。
中间层的跨平台能力是决定整个软件系统的可移植性能力的关键因素,我们的系统具有良好的跨平台能力,主要表现在几个方面:
⑴ 采用面向对象工具UML设计,中间层设计具有平台独立性;
⑵ 中间层的实现使用C、C++的标准类库,避免使用开发语言中与操作系统平台相关的类库和功能;
⑶ 采用CORBA机制实现client与对象之间的通讯;
⑷ 建立数据存取对象,根据ODBC标准编写与数据库的接口。
在此情况下,在WINDOWS操作系统平台上开发的系统(公认的目前最流行的操作系统,尤其在商用领域),作少量的修改就可以移植到其它的操作系统平台上去。
4.3 数据库层
数据库分布于系统中的服务器上,它们存储着系统中业务信息以及包括用户权限等控制信息。通常有关系型数据库和对象型数据库两种。目前,企业管理应用系统中,关系数据库占有绝对优势,这是因为关系数据库的逻辑存储模型基础——关系表——非常适合企业业务数据的表示。关系型数据库主要产品有DB2,SYBASE,ORACLE,SQL SEVER,INFORMIX,
| | yuxq 回复于:2003-09-17 18:51:10
| INTERBASE等。但是,由于我们在中间层建立了数据存储层,它向业务对象屏蔽了数据信息的来源方式,使得中间层业务对象跨平台成为可能。
参考文献
[1] CORBA教程,李师贤等译,清华大学出版社,1999
[2] 异构环境下用于构件管理的CORBA接口的设计与实现,赵惠等,计算机科学 Vol.26.No.6
[3] CORBA与RMI在构造分布式程序中的对比研究,朱鹏等,计算机科学 Vol.26.No.7
Three Tier B/S Architecture Based on CORBA
Huang Qichun
(Institute of Artificial Intelligence, Zhejiang Unversity,Huangzhou,310027)
Abstract Four typical three tier B/S architecture models are analysed. It shows that the architecture of Applet-CORBA object-Database is more suitable for the enterprise information application system. Then client and server tier are introduced.
Keywords CORBA B/S architecture Applet
新一代BOSS系统的CORBA解决方案
(本文转自于陕西移动通信网站)
2001年12月14日
摘要
由于CORBA自身的特点——开放性、可扩展性、安全性和先进性——与新一代BOSS 系统的技术要求可以很自然的相吻合,中国移动(深圳)公司提出了以CORBA体系为中心的有自己特色的解决方案。本文阐明了中国移动(深圳)公司的新一代BOSS系统的CORBA解决方案,并以浙江省GPRS和梦网短信计费系统的开发实例说明该方案的可行性和先进性。
关键词
BOSS CORBA ORB IDL BOSS系统建设目标 BOSS系统技术要求
1 前言
中国移动BOSS系统(Business & Operation Support System,业务运营支撑系统)是基于计算机网络及相关应用技术、用以支持中国移动业务运营的系统。从功能上讲,BOSS系统涵盖了计费、结算、帐务、业务管理、客服等方面,并根据业务需要与相关外部系统进行互联。
现有移动BOSS系统大多采用"分布模式",即:全省每个地市分公司都设一个业务管理中心,建一套独立的营业帐务系统,存储和管理本地市分公司的客户数据、营业数据和帐务数据。这种方式下平台建设、业务管理、数据资料、系统设计都过度分散,而且实时性不足,基于这种情况,为适应不断开展的新数据业务,不断增长的数据量和不断提高的实时性要求,对现有的BOSS系统必须进行整体化、集中化、实时化和三层平台的改造。
BOSS系统的建设目标是实现“三个特征、两个能力、一个综合”。 “三个特征”即以能提供“个性化、社会化、信息化”服务为重要特征;“两个能力”即具有“满足未来业务发展需要”、“满足实时处理”的能力;“一个综合”即提供一个综合性的业务处理平台。系统以客户为中心,提供各种客户化定制服务,实现统一界面、统一平台、统一服务、统一标准和统一质量的要求;具有较强的实时处理能力;具有良好的扩展性,以满足新业务、新服务的开展;具有统一的业务处理和管理流程、统一的接口、统一的协议以及统一的数据格式。
我们提出的BOSS系统解决方案,以CORBA技术为核心,以灵活、模块化的整体架构为突出特点,充分利用先进技术开发新一代的BOSS系统。
面向对象的技术解决了传统对象技术只存在于一个程序中,外部无法访问的缺点,使不同厂家的软件通过不同的地址空间、网络和操作系统可以交互访问,大大提高系统的可维护性和可重用性。CORBA(Common Object Request Broker Architecture)是生成面向对象系统的技术规范,称为对象请求中介,可以使不同语言编写或在不同平台上运行的应用软件在分布式网络(如Internet)上进行通信。其具体实现、位置及所依附的操作系统对客户来说都是透明的。
我们不仅在理论上可以从多方面证明CORBA技术的先进性和可行性,而且我们在实际上已经将CORBA技术应用到大数据量的计费系统中,并取得良好的效果。在文章的后面部分将有更详细的描述。
2 BOSS系统体系结构
新一代BOSS系统实现过程中,需遵循如下技术原则:
(1)开放性:基于业界开放式标准,中国移动将进行全国统一规划,为未来的业务发展奠定基础;
(2)灵活性与可扩展性:为适应WTO所带来的商机和国内市场的不断发展成熟,新一代BOSS系统应具有方便扩展设备容量和提升设备性能的能力,应具备支持业务处理灵活配置的功能,以及业务功能重组与更新的灵活性;
(3)安全可靠性:系统集中性强,特别是数据集中存放,使新一代BOSS系统对安全性提出更高的要求,系统应能提供良好的安全可靠性策略,支持多种安全可靠性技术手段,制定严格的安全可靠性管理措施;
(4)先进性:应采用先进成熟的设备和技术,确保系统的技术先进性,保证投资的有效性和延续性。按照两级系统、三层结构的原则,对计费、结算、帐务、业务及客服等功能进行集中、统一的规划和整合,使中国移动的BOSS系统成为一体化的、信息资源充分共享的支撑系统。“两级系统”是指BOSS系统分为集团公司级BOSS系统(全国中心)和省级BOSS系统(省中心)两级。“三层结构”是指BOSS系统在逻辑结构上包含数据核心层、业务逻辑层和接入层三层。
遵循两级系统、三层结构的原则,BOSS系统的建设应实现企业有效资源的高度共享;优化业务流程,提高客户管理水平,提高服务质量;为管理决策提供科学、准确、及时的依据。
3 采用CORBA开发新一代BOSS系统
CORBA很好地结合了面向对象和分布处理技术,而这两者的结合正是当今软件产业的发展方向。下面从CORBA的结构开始分析采用CORBA开发新一代BOSS系统的可行性和优越性。
3.1 从体系结构上看,CORBA由四部分组成。
(1)对象请求代理ORB:它是CORBA的核心,它保证在分布式异构环境中,透明地向对象发送和接收请求,帮助实现应用组件之间的互操作。ORB是支持CORBA构件相互作用的“软总线”(Software Bus),服务性构件可以向它登记注册,当客户性对象需要某种服务时,可以向它发出请求,ORB负责搜索已在其“注册登记”了的服务性对象,找到后启动该服务,传送请求给该服务性对象,并将结果传送回给客户性对象。
本文开始的图所示为一个独立的ORB的结构,箭头说明ORB的调用关系。为了提出一个请求,客户端可以使用动态调用接口(Dynamic Invocation)或者客户端的Stub程序。客户端也可以直接和ORB交互。
(2)公共对象服务(Common Object Services):这是一些最有可能被用来支持分布式对象环境下构造应用的标准化组件,目前已通过的公共对象服务包括对象命名服务、事件服务、对象生存期服务、永久对象服务、对象关系服务等。
(3)公共设施(Common Facilities):它是比公共对象服务力度更大的可重用的构件块,它主要用来帮助构造跨多个应用域的应用程序,典型的公共设施包括用户接口、信息管理、系统管理和任务管理等。
(4)应用对象(Application Objects):它是指供应商或用户借助于ORB、公共对象服务及公共设施而开发的特定产品,它不在CORBA体系结构中实行标准化。
(5)IDL(Interface Definition Language)Stubs和Skeletons
IDL是一种接口定义语言,通过它实现了对象接口与对象实现的分离,屏蔽了语言和系统软件带来的异构件。通过标准的IDL编译器,可生成客户端的IDL Stub和服务器端的Skeleton,这两者就如同客户端程序和服务器端程序联接ORB的粘着剂,IDL Stub提供了访问对象服务的静态接口,而Skeleton则包含了服务对象的静态接口并负责实现与对象实现中具体方法的连接。
(6)DII(Dynamic Invocation Interface)
DII使得Client应用开发者在运行时可以动态地创建和发送对服务对象的请求,包括查找定义了服务对象接口的元数据、在请求中添加参数、删除请求、发送远程调用等。
(7)接口池(Interface Repository)
接口池主要包含了用IDL定义的服务对象接口的信息,它主要是为动态调用提供相关的接口信息,CORBA提供的API可通过访问接口池获取和修改所有已注册对象的接口描述、对象中的方法及参数。
(8)对象适配器(Object Adapter)
对象适配器是服务器端管理对象实现和对象引用的主要机制,它接收ORB传来的服务请求,实例化服务对象,产生并解释服务对象的对象引用,根据服务请求选择并激活正确的服务对象,再把服务结果送到ORB。
3.2 由上面的分析可见,采用CORBA开发新一代BOSS系统的特点非常突出:
(1)它采用软构件及软总线的概念,实现了系统异构平台之间的统一,客户端与服务器完全可以在不同的平台上,用不同的语言来编写应用,任何厂家、计算机、操作系统、编程语言及网络环境下的基于CORBA的应用均使用IIOP标准协议,所以任何基于CORBA的应用均能协同工作,具有很好的开放性和灵活性。这一点对应了BOSS系统的开放性要求。同时,使用接口描述语言编写的对象接口,使得与语言无关的独立性成为可能。IDL使得所有CORBA 对象以一种方式被描述,仅仅需要一个由本地语言(C/C++、CORBA或Java)到IDL的“桥梁”。
(2)CORBA通过对象引用技术来唯一确定分布式环境下的对象实例,实现了分布式对象处理功能。
(3)一个对象实现可为多个客户端应用调用,也可调用其他的对象实现,实现了对象的可重用性和互操作性。针对BOSS系统的三层体系结构,这个特点对应BOSS系统的第二点技术要求——灵活性和可扩展性。采用CORBA技术时,系统可以无须与现有的硬件、网络和软件结构打交道,就可以把PC机及它的应用程序同企业的其他部分连接起来,并且能为动态变化的企业环境提供适应性。特别是当CORBA与面向对象的语言,如C++,Java等联合起来使用的时候,更是能将其优点充分发挥。我们在浙江所做的GPRS计费系统和短信系统采用的正是CORBA与C++的配合,取得了良好的效果,其中表现出来的最突出的优点体现在于系统的灵活性。
(4)CORBA提供的公共对象服务功能很强,其中基于事件服务的主动服务Push和Pull技术,是处理实时系统的一种很好的技术。
(5)CORBA提供了很好的容错机制。容错机制不仅能实现负载均衡,还能使每一个对象同时在两个或多个服务器上运行,当其中的一个出现故障时,系统能自动切换到另一个服务器。如果多个服务器的硬件配置相同,服务器会拥有很高的稳定性和安全性,使服务器真正能够实现高速度、高稳定性处理大量数据。满足BOSS系统的第三个技术要求——安全性要求。
(6)CORBA是OMG组织在1991年提出的公用对象请求代理程序结构的技术规范。CORBA有很广泛的应用,它易于集成各厂商的不同计算机,从大型机一直到微型内嵌式系统的终端桌面,是针对大中型企业应用的优秀的中间件。其先进性和优越性是实践中证明了的。可以满足BOSS系统的先进性要求。
3.3 我们在实现浙江省GPRS计费系统和梦网短信计费系统时,积累了利用CORBA与C++结合开发处理大数据量,对实时性、安全性有高要求系统的丰富经验,同时掌握了利用CORBAScript脚本语言灵活和功能强大等优点进行开发,利用XML文件作为配置文件,利用正则表达式进行检错等技巧,与CORBA体系相结合,并取得良好的效果。在用户界面的处理上,我们采用cvs、cgi、html、CORBAScript、Oracle等技术,做到人机交互、客户端与服务端密切结合,直接交互,大大方便了用户的数据管理。
综上,我们从理论上和实践上分析了新一代BOSS系统的CORBA解决方案的可行性和先进性。在市场开放、用户需求不断更新的今天,中国移动的新一代BOSS系统完善的解决方案对于更有效地方便企业的管理、改善企业服务,提高企业的服务水平、管理水平和经营决策水平,向世界一流通信运营企业迈进具有十分重要的战略意义和现实意义。中国移动(深圳)公司的新一代BOSS系统的CORBA解决方案从满足新一代BOSS系统的技术和市场要求出发,采用先进可靠的技术,力求实现一个高质量的BOSS系统。
集成EJB和CORBA
从一个非JAVA应用访问EJB
摘要:
EJB与CORBA的集成能力对于集成基于JAVA或非JAVA的应用来说是很重要的。本文描述了如何实现一个EJB与一个CORBA的C++应用相集成。它阐述了几个重要的集成问题,尤其是那些EJB采用JAVA固有的或是用户定义的对象作为参数或返回值的方法时涉及的问题。
EJB对于用JAVA来开发关键业务应用程序是非常重要的。但是,业务应用不是孤立存在的,当今,企业需要集成各种应用。从而,把基于EJB的解决方案与现有的应用系统集成起来就变得越来越重要了。
在本文中,我将说明如何从一个非JAVA语言编写的应用中访问EJB。更加特别地是,我将讨论从一个CORBA的C++客户端访问会话和实体Bean(它使用同步的IIOP协议进行通信)。我没有提到消息驱动Bean,尽管你可能想从其它语言编写的应用中使用MOM产品来访问它们。
1. RMI-IIOP
会话Bean和实体Bean使用远程方法调用(RMI)来进行同步通信。J2EE1.3要求JAVA客户端使用RMI-IIOP。RMI-IIOP采用CORBA的IIOP协议,这使得RMI-IIOP与CORBA相兼容。换句话说,不是基于JAVA开发的客户端可以通过CORBA与EJB进行通信。
要实现这点,你必须使用符合J2EE1.3的应用服务器。以前的EJB规范没有要求你去用RMI-IIOP协议。而是,应用服务器采用了RMI-JRMP或是其它私有协议。另外,你必须使用符合CORBA2.3.1或更高版本的ORB。以前的CORBA版本没有实现与RMI-IIOP协议进行互操作所必需的规范,尤其是后来集成中CORBA规范和JAVA到IDL的语言映射规范中的用值传递对象的规范(可以参看CORBA/IIOP规范2.6版,在“值类型语义”一章)。
值类型语言增加了用值来传递对象的概念,是由RMI引来,加入到CORBA中的。CORBA最初并不支持这项功能;但是,这个概念对于实现JAVA与CORBA之间的互操作是至关重要的。
JAVA到IDL语言映射规范定义了如何把JAVA接口映射到CORBA的IDL语言。这个定义使CORBA分布对象可以访问本来不具有CORBA的IDL的EJB(还有那些RMI-IIOP分布对象)。特别的是,这个规范定义了一个JAVA的RMI子集,叫RMI/IDL,它可以让你
映射到IDL,用IIOP(或是更通用,是GIOP协议)作为通信的底层协议。
2. RMI/IDL
许多RMI/IDL数据类型遵循一定的约束;我们来看一下那些最重要的类型。更详细的信息,请参看JAVA到IDL的语言映射规范。
表1显示了JAVA基本类型到IDL的映射。
表1:JAVA到IDL的映射
Java OMG IDL
void void
boolean boolean
char wchar
byte octet
short short
int long
long long long
float float
double double
JAVA包映射为IDL的模块。RMI/IDL中的远程接口映射为IDL的接口并具有相对应的名字。但是,那些用JavaBean命名方式用来只读或读写属性的方法被映射为IDL的属性。后面我将提到这个。
JAVA中可序列化的对象映射为CORBA的值类型。值类型为CORBA提供了用值来进行传递的语义。值类型是属于本地的,不能被远程调用。它们不注册到ORB中,也不需要标识符,因为它们的值就是它们的标识符。更详细的信息,请参阅《Professional J2EE EAI》和CORBA/IIOP规范2.6版。
就象我已经提到过的,所有的JAVA或序列化的对象,包括JAVA固有的和用户定义的,都将映射为值类型。但是,这个规则也有一些例外----例如,当你想把java.lang.String映射到IDL时。如果把它定义为常量(final static),这个对象将被映射为IDL的wstring。在其它情况下,包括作为方法的参数或返回值,该对象都被映射为值类型CORBA::WStringValue。这个值类型是CORBA模块的一部分,它的IDL定义如下:
valuetype WStringValue wstring;
这等同于下面的IDL定义:
valuetype WStringValue {
public wstring data;
};
但是,要记住,第一种定义能够更干净地映射到JAVA。表2列出其它特殊的映射情况。
表2:其它重要的特殊映射情况
Java OMG IDL
java.lang.Object ::java::lang::_Object
java.lang.String ::CORBA::WStringValue or wstring
java.lang.Class ::javax::rmi::CORBA::ClassDesc
java.io.Serializable ::java::io::Serializable
java.io.Externalizable ::java::io::Externalizable
java.rmi.Remote ::java::rmi::Remote
org.omg.CORBA.Object Object
3. 实现集成
后面我将返回来讨论值类型,先讨论用户定义的类,再讨论内嵌的类,如Vectors、Collections和Enumerations。现在,让我们看一下CORBA和EJB集成的基本方式。首先,我们需要一个EJB。在第一个例子中,我们使用一个简单的会话Bean,它只使用简单的数据类型作为方法的参数和返回值。我们没有强行去用值类型。(注意:从CORBA客户端访问实体Bean跟访问会话Bean的过程一样。)
这个方式是最简单的;但是,你不能把它用在复杂的接口上。它的好处是:你可以使用不支持值类型的ORB。许多CORBA产品都是这样(不支持值类型),尤其是那些不是用C++实现的产品。
这个例子中,我将使用C++版本的ORBacus4.1.0作为CORBA的ORB,使用VC++6.0作为编译客户端代码的编译器。为部署这个例子中的EJB,我将使用JBoss3.0.0。你可以从网上下载ORBacus4.1.0(也可以从IONA网站上下载)和JBoss3.0.0。
你可以使用任何支持CORBA2.3.1或更高版本的ORB产品(只要它支持到C++的映射)、一个相对应的C++编译器和一个支持J2EE1.3规范的应用服务器。理论上讲不需要修改代码;但是,如果你使用其它产品,小的改动可能是必要的。
4. EJB会话Bean
让我们简单地看一下这个名叫CorbaEai的会话Bean。在我们的第一个例子中,这个简单的远程接口包含一个计算两个整数之和的简单方法。
package eai;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface CorbaEai extends EJBObject {
public int sum(int a, int b) throws RemoteException;
}
在你熟悉了集成过程之后,我将告诉你如何扩展这个接口以包含使用用户定义的对象和内嵌对象的方法。
要转这个例子,你必须把会话Bean部署到一个应用服务器上。这要求你定义Home接口、实现这个Bean的实现类、定义部署描述器、创建jar文件并最终部署这个EJB。我不写这些步骤了,但是你可以下载这个例子的源码包。
5. 开发CORBA客户端
要开发CORBA客户端,我们需要完成以下步骤:
l 从会话Bean的Home接口和组件远程接口生成IDL
l 简化生成的IDL
l 编译IDL接口,生成相应的编程语言的代码----我们这个例子中是C++----来生成桩及其它必需的映射
l 确定如何使用JNDI作为CORBA的名字服务
l 开发C++的客户端和支持值类型
l 创建客户端
5.1. 生成IDL
为从JAVA接口生成IDL,你可以使用任何支持JAVA语言到OMG的IDL映射规范的工具。例如:
l rmic编译器,由JSDK1.3或更版本提供,使用-idl选项;
l java2idl,由VisiBroker提供;
l rmic和ejbc(使用-idl)编译器,由BEA的WebLogic提供
其它的应用服务器提供类似的工具。需要明确的是,从EJB接口生成IDL接口要比从RMI-IIOP接口生成复杂得多。首先,EJB接口继承于EJBObjec和EJBHome接口,它们定义了一些基本方法。因为IDL接口也必须继承于这些接口,所以其工具也必须为这些接口生成IDL接口。其次,EJB的home接口中的方法至少抛出CreateException异常,所以其工具也必须把这个异常和一些其它用户定义的异常映射到IDL。
这儿我用的是JSDK1.3.1提供的rmic,注意要把包含要映射的接口的jar包包含在classpath中(对于JBoss来说,要包含$JBOSSHOME\client\ jboss-j2ee.jar)。这儿需要映射的是eai.CorbaEai和eai.CorbaEaiHome两个类:
rmic -idl -classpath "%classpath%;corbaeai.jar" eai.CorbaEai eai.CorbaEaiHome -d ./idl
rmic编译器在目录./idl下生成IDL文件,共有26个。
5.2. 简化生成的IDL的文件
用C++来实现所有的生成的接口和值类型是很费时间的。但是我们并不需要所有的生成的接口,因为有一些方法我们没有去用。所以我们可以通过简化生成的IDL接口文件来节省一大部分工作。
对于这第一个例子,我们不去用EJBObject和EJBHome两个接口上的方法,我们也不需要EJBMetaData。我们也不需要特殊的RemoveException异常,这样,我们就可以不用RemoteExceprion和RemoteEx两个IDL接口。我们还可以除去下列IDL接口:java.io、java.lang和java.rmi。
为进一步简化IDL文件,我们把所需要的IDL接口放在一个文件中。经过简化以后,得到以下IDL文件CorbaEai.idl:
#include "OB/orb.idl" //ORBacus中的orb.idl在OB目录下。
module javax {
module ejb {
valuetype CreateException {
};
#pragma ID CreateException "RMI:javax.ejb.CreateException:FD98A9711F66DF7F:575FB6C03D49AD6A"
exception CreateEx {
CreateException value;
};
};
};
module eai {
interface CorbaEai {
long sum(
in long arg0,
in long arg1  ;
};
#pragma ID CorbaEai "RMI:eai.CorbaEai:0000000000000000"
interface CorbaEaiHome {
::eai::CorbaEai create(  raises (
::javax::ejb::CreateEx  ;
};
#pragma ID CorbaEaiHome "RMI:eai.CorbaEaiHome:0000000000000000"
};
5.3. 编译IDL接口
接下来,我们把IDL接口映射到客户端的编程语言。我们在客户端用C++,这儿我们用ORBacus的idl编译器(你可以用任何支持CORBA2.3.1或更高版本规范的IDL到C++的编译器)。
如果你仔细看IDL的文件,你会发现它包含了一个文件orb.idl。在我们的例子中,我们用ORBacus自带的orb.idl文件,它在$ORBacusHome/idl/OB目录下。因此,在使用IDL编译器时,我们要指定包含orb.idl的路径。我们用ORBacus所带的IDL编译器进行编译,因为我们只是编写客户端,所以不需要服务方的骨架文件:
idl -IE:\OOC\idl --no-skeletons CorbaEai.idl
会生成stub文件CorbaEai.h和CorbaEai.cpp。
5.4. 使用JNDI作一个CORBA名字服务
在我们开发C++客户端之前,先考虑一下如何获得会话Bean的Home接口CorbaEaiHome的初始引用。CorbaEaiHome已经注册到应用服务器的JNDI上,所以C++的客户端必须访问JNDI名字服务。
有一些应用服务器,就象BEA的WebLogic,提供了一个跟CORBA名字服务相兼容的接口去访问JNDI。为测试这一点,我们可以用WebLogic的host2ior工具,它将输出这个名字服务的使用IIOP和IIOP/SSL的IOR。对我们这个例子来说,我们只对非安全的IIOP感兴趣。JBoss也提供一个CORBA名字服务,从JBoss的启动日志中可以看到这个服务的IOR。
CORBA名字服务是一个有着标准接口的CORBA分布对象,从技术上讲,它和其它CORBA对象一样。因此,我们可以通过IOR直接连接到JNDI。
但是,一个更好的方法是通过用一个关键字NameServie析取初始引用来得到名字服务的引用。在本例中,我们在启动客户端时指出所要用的名字服务,我们通过命令行实现,在后面将会提到。
5.5. 开发C++客户端
我们开发一个简单的C++客户端来调用会话Bean CorbaEai上的sum()方法。假定你熟悉CORBA。客户端的主程序Client.cpp如下:
#include <OB/CORBA.h>
#include <OB/CosNaming.h>
#include <CorbaEai.h>
#ifdef HAVE_STD_IOSTREAM
using namespace std;
#endif
int run(CORBA::ORB_ptr orb, int argc, char* argv[])
{
//
// Get naming service
//
CORBA::Object_var obj;
obj = orb -> resolve_initial_references("NameService");
CosNaming::NamingContext_var nc =
CosNaming::NamingContext::_narrow(obj.in());
//
// Resolve names with the Naming Service
//
CosNaming::Name name;
name.length(1);
name[0].id = CORBA::string_dup("Corba-Eai");
name[0].kind = CORBA::string_dup("");
cout << "Resolved `CorbaEaihome'" << endl;
CORBA::Object_var aObj = nc -> resolve(name);
eai::CorbaEaiHome_var ceaihome = eai::CorbaEaiHome::_narrow(aObj.in());
assert(!CORBA::is_nil(ceaihome.in()));
cout << "Creating a new instance" << endl;
eai::CorbaEai_var ceai = ceaihome->create();
cout << "Result: "<< ceai->sum((CORBA::Long)15,(CORBA::Long)20)<< endl;
return EXIT_SUCCESS;
}
int main(int argc, char* argv[], char*[])
{
int status = EXIT_SUCCESS;
CORBA::ORB_var orb;
try
{
orb = CORBA::ORB_init(argc, argv);
status = run(orb, argc, argv);
}
catch(const CORBA::Exception& ex){
cerr << ex << endl;
status = EXIT_FAILURE;
}
if(!CORBA::is_nil(orb)){
try {
orb -> destroy();
}
catch(const CORBA::Exception& ex){
cerr << ex << endl;
status = EXIT_FAILURE;
}
}
return status;
}
5.6. 建立和运行客户端
我们用VC++6.0建立C++的客户端。源文件是idl编译生成的客户端的stub文件CorbaEai.h和CorbaEai.cpp和主程序文件Client.cpp。生成可执行文件CorbaEai.exe。
按照以下步骤运行本例子:
l 启动应用服务器,在我们的例子中是JBoss3.0。为了启动IIOP,所以用all模式来启动JBoss3.0.0(可能集群服务启动不起来,如果起不来,可以先把集群服务屏蔽掉)。
l 部署会话Bean CorbaEai到JBoss应用服务器上。
l 使用下面的命令启动C++客户端程序:
CorbaEai –ORBInitRef NameService=corbaloc::localhost:8683/JBoss/Naming/root
注意在本例中,EJB应用服务器和客户端在同一台机器上。如果要进行远程通信,需要用一个真正的名字或是IP地址代替localhost。
6.值类型
这个例子还是十分的不完美。它没有为Java的可序列化对象实现值类型,这就意味着我们不能捕捉到远程异常(因为正象IDL中所表示的,它们被映射成值类型)。另外,在第一个例子中我还限制了方法只使用原始的数据类型。
为了理解CORBA是如何处理Java的可序列化的类,你必须看一下CORBA的值类型。Java的可序列化对应着值类型。每一个被作为参数、返回值或异常传自于/传递到CORBA客户端的Java可序列化的对象都应该用客户端的编程语言再实现一次,在我们的例子中就是C++。这需要一定的工作。不幸的是,我不知道那些对Java的固有类型,如Remote、Throwable、EJBObject和EJBHome完全支持的CORBA实现。据我所知,只有IBM的WebSphere应用服务器提供了一个值类型的库,它包含了诸如Integer、Float、Vector、Exception和OutputStream等这些常用的Java类型的C++值类型的实现。根据WebSphere4.0应用服务器的文档,它的值类型库只支持WebSphere的C++ ORB。
对每一个值类型,IDL到C++的编译器生成一个指针类型的定义_ptr和一个_var类。_var类自动管理为对象引用动态分配的内存。一个转换操作符让你也可以把一个_var赋给_ptr。还需要提醒的是,原始的Java构造器不能映射到IDL。但是,当IDL映射到C++时,构造器变成工厂类的init方法。
为实现值类型(比如用C++),你需要:
l 实现一个值类型的类,它要继承于值类型的基类(由IDL编译器生成)和缺省的值引用计数的基类。对于值类型,你必须手工实现引用的计数。
l 实现工厂类和工厂方法。
l 把工厂注册到ORB。
让我们看一下CreateException值类型的例子。首先,我们声明它的实现类CreateExceptionImpl:
class CreateExcepionImpl : public virtual ::javax::ejb::OBV_CreateException,
public virtual CORBA: efaultValueRefCountBase
{
我们至少必须实现构造器和_copy_value()方法,它简单地一个新的值类型类的实例:
public:
CreateExceptionImpl() : OBV_CreateException() { }
virtual ~CreateExceptionImpl() { }
CORBA::ValueBase* _copy_value() {
return new CreateExceptionImpl();
}
我们也能够为其它一些方法提供实现,如message()、localizedMessage()和toString():
CORBA::WStringValue* message() {
return new CORBA::WStringValue(CORBA::wstring_dup(L”javax::ejb::CreateException”));
}
CORBA::WStringValue* localizedMessage() {
return new CORBA::WStringValue(CORBA::wstring_dup(L”javax::ejb::CreateException”));
}
CORBA::WStringValue* toString() {
return new CORBA::WStringValue(CORBA::wstring_dup(L”javax::ejb::CreateException”));
}
};
接下来,我们定义工厂类CreateExceptionFactory。我们至少要实现create_for_unmarshal()方法,但是我们还要实现create__()方法。这两个方法都返回一个新实现的类实例:
class CreateExceptionFactory: public ::javax::ejb::CreateException_init
{
public:
CreateExceptionFactory() { }
virtual ~CreateExceptionFactory() { }
javax::ejb::CreateException* create__() {
return new CreateExceptionImpl();
}
CORBA::ValueBase* create_for_unmarshal() {
return new CreateExceptionImpl();
}
};
最后,我们把工厂注册到ORB上。ORB接口提供register_value_factory()方法,它接收库ID号和工厂实例作为参数。我们可以从IDL中得到库ID号(它由#pragma指示)。下面的例子示范了如何注册CreateException:
orb->register_value_factory(“RMI:javax.ejb.CreateException:FD98A9711F66DF7F:575FB6C03D49AD6A”, (CORBA::ValueFactory)new CreateExceptionFactory);
我们简单地实现了在CORBA客户端中所需要的所有值类型。尽管这看起来有些复杂,但是你将会发现这个过程并不是很难而且你可以定义模板类来自动生成它。
7.开发一个更高级的CORBA客户端
现在你对CORBA的值类型已经熟悉了,你可以为具有更复杂接口的EJB开发CORBA客户端。
在这个例子中,我们用几个使用了用户自定义对象和固有的Java对象的方法来扩展CorbaEai会话Bean的接口。首先,我们添加sumObj()方法来将两个Integer对象相加:
public Integer sumObj(Integer num1, Integer num2) throws RemoteException;
然后我们为一个可序列化的类MyType添加一个getter/setter操作对:
public MyType getMyType() throws RemoteException;
public void setMyType(MyType mt) throws RemoteException;
MyType类非常简单,它包含两个属性(为简单起见,我没有列出相对应的getter/setter操作方法):
package eai;
import java.io.Serializable;
public final class MyType implements serializable {
public int a;
public double b;
}
最后,我们添加一个返回Java向量的方法getVector()和一个返回Java集合的方法getCollection():
public Vector getVector() throws RemoteException;
public Collection getCollection() throws RemoteException;
这些方法代表了大多数基于EJB的应用中的典型方法。完整的例子可以参见源代码2。
开发这个CORBA客户端的过程跟第一个例子相似。首先,我们生成IDL接口。我们使用跟前面一样的命令;但是,这次我们不再简化IDL。
注意,为会话Bean CorbaEai远程组件接口生成的IDL接口遵循RMI/IDL映射属性访问方法的规则。使用JavaBean命名方式来读写/只读属性的方法没有映射到IDL操作,而是映射到IDL属性。在我们的例子中,我们有getMyType()和setMyType()读写属性的方法和getVector()、getCollection()的只读方法。这些方法映射到下面的IDL属性:
attribute ::eai::MyType myType;
readonly attribute ::java::util::Vector vector;
readonly attritube ::java::util::Collection collection;
为了从C++访问这些属性,你必须知道它们是如何从IDL映射到C++(或者你选择的其它语言)。对C++,每一个属性映射到两个重载的和属性一样名字的C++函数,一个用来设置属性,一个用来读取属性。只读属性只映射到一个读取函数。更详细的信息请参阅C++语言映射。
其它方法,如sum()和sumObj(),都是一对一地映射:
long sum(in long arg0, in long arg1);
::java::lang::Integer sumObj(in ::java::lang::Integer arg0, in ::java::lang::Integer arg1);
接下来,我们用ORBacus的idl编译器把IDL接口编译成C++。你可以一个一个地来编译所有的IDL文件,也可以把所有的IDL文件放在一个文件中编译。
现在,我们可以编写C++客户端的代码。第一,按照我前面解释的,为所有有关的值类型提供值类型的实现(值类型的类和工厂类)。在例子中,我们为下面这些值类型定义实现:
l MyType
l Integer
l Vector
l CreateException
l RemoveException
我们还要把这些值类型注册到ORB。
你也许要奇怪为什么我们没有为Collection定义一个值类型。你知道,Collection是一个接口,它由类AbstractCollection实现,提供了一个框架性的实现。JavaSDK为更多的特定接口提供了实现,如List。为了理解这种类和接口之间的层次关系,请看以下的UML图:
Collection的接口和实现类层次
你可以把Vector看成Collection接口的一个可能的实现,所以我们用它来访问Collection。
实际上C++用来调用CorbaEai会话Bean方法的代码是比较简单的。为调用sumObj()方法,我们先创建两个Integer值类型并插入数值,然后调用方法、输出结果:
::java::lang::Integer_var javaInt1 = new IntegerImpl();
::java::lang::Integer_var javaInt2 = new IntegerImpl();
javaInt1->value(15);
javaInt2->value(20);
::java::lang::Integer_var javaIntR = ceai->sumObj(javaInt1, javaInt2);
cout << "sumObj(): " << javaIntR->value() << endl;
为了调用getMyType()和setMyType()方法,回忆一下它们是如何被映射到IDL属性的。所以,我们用C++重载的方法myType()。下面的代码调用会话Bean上的getMyType()方法:
::eai::MyType_var mt = ceai->myType();
cout << "getMyType(): " << mt << endl;
为了调用setMyType()方法,我们先创建一个MyType实例并填充必要的数值;剩下的就是调用它了:
cout << "setMyType()... " << endl;
::eai::MyType_var mt2 = new MyTypeImpl();
mt2->a((CORBA::Long)9);
mt2->b((CORBA: ouble)9.9);
ceai->myType(mt2);
注意,这儿的方法名字已经变了,因为我们用的是JavaBean的命名方式(get/set方法)。否则,方法就不会改变。
对getVector()方法也是一样,它被映射到一个只读的IDL属性。所以,我们用C++的vector()方法来访问它:
::java::util::Vector_ptr vec = ceai->vector();
cout << "getVector(): " << vec << endl;
这个过程中最有趣的事情也许是我们如何访问Collection的过程。调用会话Bean的getCollection()方法(在C++中是collection())后,我们把它显式的转换为一个Vector:
::java::util::Collection_ptr coll = ceai->collection();
::java::util::Vector_ptr cvec = ::java::util::Vector::_downcast(coll);
cout << "Collection: " << cvec << endl;
最后,注意,我们可以应用那些从EJBObject继承来的远程组件接口方法。如最重要的方法remove()。所以,我们可以得出结论我们的例子中有以下一行:
ceai->remove();
理想的集成
在本文中,我已经说明了在CORBA和EJB的的集成是可能的。但是,用非Java开发一个EBJ的客户端并不象你所希望的那么简单,尤其是当你使用那接收和返回对象或是用户定义类型的方法。这是应用EJB组件的绝大多数情况,所以你必须使用CORBA的值类型。最主要的问题是你还要必须实现Java固有的类型。我希望CORBA的销售商尽快提供值类型的实现,如IBM的WebSphere应用服务器。我也希望不同的CORBA产品之间的互操作问题能够尽快解决。
无论如何,当你把一个现有的不是基于Java的应用跟新的基于J2EE的方案进行集成时,EJB和CORBA之间的互操作是十分重要和有用的。
译者说明:
l 在第7节中,采用值类型,当用rmic把JAVA接口映射到IDL时,我使用了-noValueMethods,因为如果带着该参数编译出来的IDL文件要多一些,再映射到C++后,很难编译过去。
l 即使使用-noValueMethods选项,最后生成的C++程序还是不能完全进行正常地编译和运行(涉及到Java固有的那些类型:Integer、Vector、Collection),所以只好将一些语句屏蔽掉了。
| | qjlemon 回复于:2003-09-17 19:08:33
| 好贴!
不过我这会加班,就是要把与Corba相关的部分从程序里清掉 ,做一个精简版本 
| | gadfly 回复于:2003-09-18 09:10:19
| 基于corba做应用的确太重量级。
适合于复杂和异构的应用体系。
不过我知道,一些新的网络设备都提供corba接口,来获取和设置配置。屏蔽了底层的访问。
| | clytcn 回复于:2003-09-18 13:43:42
| 谁能给个corba的例子,暗夜研究研究
| | qjlemon 回复于:2003-09-18 13:50:59
| 从borland.com下一个visibroker,有linux和windows版,aix、hp、solaris的都有,那里面就有例子。如果是5.0的还有中文的帮助,非常好。
| | slg1972 回复于:2003-09-20 10:47:59
| 有的朋友说他没有cics好,我也看了cics,好象的确没有cics好,corba到底前途怎么样呀,我想学又不敢呀。
| | qjlemon 回复于:2003-09-20 11:16:25
| Corba说白了是一个“协议“, 一个”规范“,cics是种“产品”,visibroker则是Corba的一个”实现",这些东西根本不能混为一谈,不存在谁好谁坏的问题。
| |
|