UML软件工程组织

IDL-to-Java 映射:第二部分
使用 IDL 映射创建组件接口
Dave Bartlett
顾问、作家兼讲师
2000 年 11 月

我们就本月的 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 连接中:

在 Web 上

有益的下载

参考书籍

关于作者
Dave Bartlett 居住在美国宾夕法尼亚州的 Berwyn,他是顾问、作家兼讲师。他是 Hands-On CORBA with Java 的作者,这是一本适用于公共课程或企业内部培训的 5 日教程。目前,Dave 正将课程资料编成书,书名是 Thinking in CORBA with Java。Dave 拥有宾州大学的工程和商业硕士学位。如果您对某个主题还有疑问或感兴趣,可通过 dbartlett@pobox.com 与 Dave 联系。



版权所有:UML软件工程组织