|
|
核心规范
当前最新版本是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时,这一点更加重要。
| | |