0%

官方系统架构设计师教程-ch10-构建平台与典型架构

构件平台与典型架构

几乎没有构件能独立的部署,它们大多数依赖于特定的基础设施平台。由于行业高度竞争,公用构建基础设施目前只有CORBA+Java和Microsoft COM+CLR两大阵营。尽管只有两大阵营,SOA技术也飞速发展,不同平台构件连接能力有了一定改善,但在设计、管理、规范等方面存在很大差异。因此,我们有必要了解这些平台特点和差异,为应用开发选择合适的构件开发平台。

Corba 学习笔记 (一)
Corba执行过程
JNDI

OMG方式

成立于1989年的对象管理组(OMG)是目前计算工业中最大的组织。作为一个非营利性组织,OMG旨在通过规范化对象开放市场的所有层次上的互操作性。至2002年,有近800成员加入OMG。

对象请求代理

CORBA的主要目标就是使不同语言、不同实现和不同平台间能进行交互。因此,OMG从没有停步在“二进制”标准上(可配置、可执行级的标准),而是保证每个细节都被标准化,使其能顾及不同的实现及CORBA兼容不同产品的独立供应商增值的需要。这一开放式方法的不利一面就是CORBA兼容产品不能在二进制级进行有效的互操作,只能以较高的代价在高层协议上协作。OMG的跨ORB(对象请求代理)协议——IIOP(Internet InterORB Protocol)互操作协议,在1995年7月的CORBA 2.0中被规范化。与ORB的互操作兼容则必须支持ⅡOP。在1996年7月的CORBA 2.0更新版本中,加入了一条关于相互作用的协议,该协议明确了基于CORBA的系统与基于微软COM系统之间的互操作细节。

CORBA包括三个基本部分:一套调用接口、对象请求代理(ORB)和一套对象适配器。面向对象操作的调用实现后期绑定。对象引用所指代的对象实现决定了被调用方法的最终实现。调用接口支持不同级别的后期绑定,同时编排调用参数,使ORB核心能定位接收对象,调用方法,以及传递参数。在接收端,一个对象适配器还原参数,调用接收对象相应的方法。图10-1简单地描述了基本的CORBA结构。

公共对象服务规范

现有的CORBA Service包含16种对象服务(CORBA服务),其中的通告服务是电信领域设施正式的组成部分。这些服务划分为两大类:一类服务应用于企业计算系统。这些系统往往将CORBA对象视为模块,并视CORBA为易用的通信中间件,此时的CORBA服务大多用来支持大规模的操作;另一类服务则应用于细粒度的对象操作,但目前这些服务的实用价值较差。CORBA 3.0中的持久状态服务(Persistent State Service, PSS)可能是一个例外,它替代了CORBA 2.0中的持久对象服务(POS)。PSS是CORBA构件模型的三个主要支撑服务之一,另两个是事务服务和通告服务。值得注意的是,大型基于CORBA系统往往只使用少量的CORBA服务,包括名字服务、安全服务、事务服务和交易服务。现有大部分ORB产品并不试图支持全部的CORBA服务也说明了这一点。

图10-1 基于ORB系统的简化结构找不到图片(Image not found)
支持企业分布式计算的服务

许多大型企业系统只是将CORBA作为对象总线,依靠ORB与其他各种各样的系统进行互操作。名字服务是关键服务之一。

1)命名服务,交易器服务

每个对象内部都有唯一的标志符。命名服务则允许任意地给对象赋予一个名字,这个名字在其所属的命名语境中是唯一的。而命名语境所形成的层次结构,使得所有的名字形成名字树。

交易器服务允许给对象赋予一个复杂的描述,从而允许客户基于该描述来定位所需的对象。交易器通过交易语境来组织对象。客户则在指定的交易语境中根据对象描述的部分内容或关键字来搜寻对象,而搜寻结果往往是一个包含满足查询条件的一组对象的列表。OMG交易器服务规范同时被ISO(ISO/IEC 13235-1)和ITU(ITU-T推荐X.950)所采用。

2)事件服务,通告服务

事件服务允许定义那些从事件生产者被发送到事件消费者的事件对象。由于信息只能从生产者流向消费者,因而事件对象是不变的。事件必须通过事件通道传播,从而松散了生产者和消费者之间的耦合关系。事件可以具有类型(使用OMG IDL定义),而通道可以根据类型过滤事件。

事件通道支持“推”和“拉”两种方式的事件通告模型。在“推”模型中,事件生产者调用事件通道的“推”方法将事件上传给事件通道,事件通道进而调用所有注册的事件消费者的“推”方法将事件传给消费者。在“拉”模型中,事件消费者调用事件通道的“拉”方法,这将导致事件通道调用所有注册的事件生产者的“拉”方法,此时获得的新事件将返回给消费者。

1998年,通告服务为事件服务增加了几个重要的特征——服务质量(Quality of Service, QoS)规范和管理;标准的类型化和结构化事件;基于类型和QoS的动态事件过滤;作用于资源、通道、一组消费者或单个消费者的事件过滤;针对资源、通道和客户的事件发现。值得注意的是,通告服务本身并不是CORBA服务,而是电信领域工作组(Te1DTF)提交的CORBA设施。

3)对象事务服务

对象事务服务(Object Transaction Service, OTS)是建立分布式应用最重要的服务之一。OMG于1994年12月制定的OTS规范在大多数的ORB产品及若干J2EE服务器中得到支持。OTS实现必须支持平坦事务,而嵌套事务是可选的。遵循X/Open分布事务处理标准的其他事务服务可以与OTS集成。同样,多个异构ORB提供的事务也可集成。

在基于构件的系统中,嵌套事务似乎不可避免。因为一个构件的实现可能创建一个覆盖一系列操作的事务闭包,而这些事务属性无须在构件接口中声明。这种独立扩展性原则需要嵌套事务的支持。作为唯一的OTS实现必须支持的事务类型,平坦事务在构件系统中的价值有限。实际上,现有的主流事务中间件也不支持嵌套事务,这是它们共同的缺点。

OTS自动维护当前的事务语境,该语境将随请求在ORB系统中传递,也可传递给其他的非CORBA的事务系统。对于CORBA对象,事务语境可以传递给任何实现了Transactional Object的对象。当前的事务语境可从ORB获得,因此必须保证随时可用。事务操作,如begin, commit, rollback都在当前的语境中定义。

所有希望在一次事务中执行修改,或者需要执行事务控制的对象都必须向OTS协调器注册。该协调器可从当前语境中获得。一个资源可以指明它是否支持嵌套事务。任何资源都必须实现Resource接口,从而允许协调器执行两段提交协议(众所周知,两段提交协议在完全分布的环境下可能发生死锁,这只能通过特定的广播协议避免;三段提交协议能够避免死锁问题(Mullender, 1993),但开销太大。因此,OTS规定协调器在逻辑上必须是集中式的)。

OTS的设计目标之一是希望将事务控制作为一个独立的服务,但目前更普遍的是将事务和其他服务集成到应用服务器提供的语境或容器中。

4)安全服务

可靠的安全服务对于一个跨越多个相互信赖的组织的分布系统极为重要。安全服务必须得到普及。所有可互操作的ORB或可共同工作的系统必须协作,而这要求为所有的参与者建立统一的安全策略。

CORBA安全规范定义了一系列的服务,包括认证、安全通信、证书委托(也称为“替身”)及防抵赖等。目前仅有少数产品完全支持该规范,如BEA的WebLogic和IBM的WebSphere。很多产品仅仅依赖Netscape的安全套接字(Secure Socket Layer, SSL)实施安全保障,尤其采用独立ORB,而不是完全集成的应用服务器时。因为利用SSL可以很方便地实现简单的认证及安全的通信,但不能支持类似委托和防抵赖等较高级的安全机制。

5)支持细粒度对象互操作的服务

尽管有些服务,包括收集、外部化和查询服务,仍未被任何产品实现(原因很多,如查询服务的规范过于松散,收集服务的某些假设不切实际,本节仍将介绍余下的服务,以便读者对OMA涉及的对象服务有一个全面的认识。

6)并发控制服务

该服务支持对资源进行加锁和解锁。锁必须依赖于事务的语境或其他语境才能获得。依赖事务语境创建的锁将作为事务回滚的部分被释放。锁具有不同的模式,如读锁、写锁、升级锁。其中,读锁允许多个客户同时执行读操作;而写锁保证只有一个客户才能执行写操作;升级锁是可以升级为写锁的读锁,支持互斥的读操作。锁有多个锁集合。每个受保护的资源都拥有一个锁集合,该集合决定了可用的锁的种类及数量。一个锁集合的工厂接口支持创建新的锁集合。锁集合不是事务型就是非事务型的,并可与其他锁集合建立关联。锁协调器可以释放指定锁集合中所有的锁。

7)许可服务

组装构件的过程中需要获取所有非免费构件的使用许可。许可服务支持多种类型的许可模式。该服务定义了两个接口(抽象)——许可服务管理器和特定于厂商的许可服务。如果一个对象与一个许可协议绑定,那么它可以通过许可服务管理器检查其使用是否合法。

8)生命周期服务

这类服务支持创建、复制、移动和删除CORBA对象及其相关的对象组。下面将介绍如何利用关系服务提供的包含与引用关系来处理对象组。包含关系支持嵌套复制,即所有被包含的对象都会被复制。为了支持对象创建,生命周期服务支持注册与获取工厂对象。一旦获得所需的工厂对象,就能够用它来创建新的对象。

生命周期服务允许删除对象或对象组,但并不提示何时销毁该对象。这意味着分布式内存管理需要高层应用的参与,这被认为是CORBA较为明显的缺点。相比较而言,DCOM支持分布引用的计数,Java和CLR甚至支持基于租借的远程引用的分布式垃圾收集。

9)关系服务

关系服务指允许定义和维护对象之间的关系。不依赖语言级的指针或引用,该服务引入了一种关系模型,以支持在不影响相关对象的情况下创建对象间的关系。但是,关系服务基本上没有实际应用,甚至没有产品实现,极有可能被基于CCM的业务对象关系所取代。

10)持久状态服务

持久性是指对象在其创建程序终止后仍然存活。为此,CORBA 2.0制定了持久对象服务(POS),用来支持CORBA对象的持久性。尽管在1994年年初就被OMG标准化为关键服务,但直到1996年年中才出现一个beta版的实现。一些报告甚至指出该规范及其与其他对象服务的互操作存在严重的技术问题。另外,POS没有解决“正确性”的问题,尤其是它把存储的申请交给应用代码处理。POS规范最终在CORBA 3.0版本中被新的持久状态服务(PSS)所代替。

11)外部化服务

这项服务支持对象网和对象流之间的双向映射。对象网外部化后再内部化意味着创建该对象网的副本。外部化服务并不保证引用的完整性,仅保留同时外部化的对象之间的引用。外部化使得对象网的值复制成为可能。而外部化对象所需的其他对象的引用可保存为ORB为对象引用提供的字符串标志。

对象必须实现Streamable接口才能被外部化。为了外部化一个Streamable对象,必须首先调用实现了Stream接口的某个对象的外部化方法,该方法将调用流对象的externalize_to_stream方法,并传递一个实现StreamIO接口的对象。最后,流对象将任何OMG IDL定义的数据类型或实现写入streamIO对象。流对象也可以外部化由关系服务定义的整个对象图表。

12)属性服务

这种服务允许将任意的属性与对象关联起来,被关联对象必须实现PropertySet接口。属性可以独立地或成组地添加、获取和删除。如果一个对象还实现了PropertySetDef接口,则可按以下4种模式中的任一种进一步控制属性,这4种模式是:标准属性(可以修改和删除)、只读属性(能被删除但是不能修改)、强制标准属性(能被修改但不能删除)和强制只读属性。

属性服务并不说明属性的语义和内容。一般而言,对于程序有用的属性都需要由程序显式地赋予相应的信息。作为一个重要的例子,系统管理工具被赋予“粘贴”特性来有效地跟踪对象。

13)对象查询服务

该服务用来依靠属性定位对象。该服务类似于对象交易服务,但该服务定位对象实例而不是定位服务器。查询使用的属性由对象公布或者允许通过操作获得。有两种查询语言可供选择,面向对象数据库管理组的ODMG-93对象查询语言(Object Query Language, OQL)和扩展的SQL。一个更为普遍的查询语言正在建立。

查询服务定义了其自身的一个简单的集合服务——是通用集合服务的子集。查询结果集返回给用户时会用到集合。这些简单的集合提供了有序集的语义,包括增加或删除元素和元素集的操作。服务提供了一个Iterator接口来支持对集合元素的遍历。

14)对象集合服务

对象集合服务支持各种抽象拓扑集合,例如,包、集合、队列、表、树、角色模型是Smalltalk集合类库(Goldberg与Robson,1983,1989)。CORBA的集合服务(基于CORBA对象的相对重权模型)是否可与本地的对象集合库竞争是一个有争议的问题,另外,对象库可能更适于在ORB间传输各种形态与属性的集合。

15)时间服务

这一服务处理拥有众多异步时钟的分布式系统固有的误差问题。许多应用程序中,用实时信息将内部事件(如创建文件)与外部通用时间建立关联。一个时间服务必须在允许的误差范围内实现这种关联,并避免其他非因果的关联。例如,假设一个新对象的产生是对另一个对象触发某事件的反应,那么,如果给前一个对象赋予“生成日期”的时间戳,而该时间戳却先于后一个对象产生的时刻,此时就会产生一个非因果的时间信息——这恰恰是非因果时间服务的典型结果。

CORBA构件模型

CORBA 3.0是CORBA标准中最新的一个。尽管2002年6月左右该规范最后部分仍未定稿,但针对CORBA 2.0全面的改进已经获得了显著的进展。除了对象服务的全面修订,最主要的成就恐怕就是新的CORBA构件模型(CCM)——尽管最终的CCM规范的发布仍未定案。(有时,CCM也被称做CORBA构件。)

可移植对象适配器

$\color{red}{\text{CORBA对象适配器主要的作用}}$ 就是 $\color{green}{\text{在一个ORB和真正接收调用并且返回结果的对象实现之间进行协调}}$ 。目前采用的对象适配器的规范针对可移植的对象适配器,它代替了已过时的基本对象适配器。目前还没有其他的对象适配器规范。这种可移植对象适配器的一个实例为一组对象接收请求。任何ORB支持的服务器进程至少有一个POA(Portable Object Adapter)的实例,当然,该进程中的每个服务对象都可能有一个POA实例。

一个POA实例通过将收到的请求传递给一个“服务体”来对其进行处理。“服务体”是CORBA对象的实现。图10-2给出了一个典型的工具使用场景,从一个IDL定义开始,客户端的指代,服务器端的POA骨架,服务器端的“服务体”模板被一一建立。开发者可以通过完成该模板来补充实现细节。CORBA对象不强制使用面向对象语言,因而“服务体”也不一定为“类”。如果使用了面向对象语言,那么“服务体”就是类的实例。

CCM构件

一个CCM应用程序是CCM构件的一个组装,其中构件可以是客户创建的或者是现成的、企业内部的或者是后来获得的。企业级JavaBean构件和CCM构件能够在一个应用程序中集成在一起。单个构件通过构件包发布,该构件包含有一个描述其内容的XML文档,还可以包含支持不同平台的二进制代码。CCM的装配包含一个描述它们所引用的构件包信息的XML文档,以及它们的部署配置。

图10-2 根据IDL文件生成指代、POA骨架、服务体模板找不到图片(Image not found)
CCM容器

CORBA 3.0定义了一个构件实现框架(Component Implementation Framework, CIF),其中包括接收CIDL(Component Implementation Definition Language,构件实现描述语言)输入并产生实现代码的生成器。另外,每个构件实例都放在一个CCM容器里。构件通过容器的接口与POA、事务、安全、持久化及通知服务相作用。一个容器同样也有插座接口来接收对构件实例的回调。

CORBA设施

CORBA设施可以分为水平的(普遍的)和垂直的(特定领域的)支持。不管是哪种支持,每个CORBA设施都定义了一个特定的构件框架,从而能够集成构件。最初,OMG试图标准化4个领域的水平设施:用户界面、信息管理、系统管理和任务管理。但是这些努力都失败了,而且水平设施在今天的OMA(Open Mobile Architecture)中的影响力很弱,之所以保留下来,是因为垂直设施的工作很可能产生并不特定于单个领域的设施。水平设施的例子,或者已经被标准化或者正在考虑之中,包括全球服务、移动代理、时间和打印设施。

领域任务组定义了垂直设施。在2002年年初,有10个这样的任务组:商业企业集成命令控制、计算机通信和集成、财政、卫生保健、生命科学研究、制造业、空间、电信、运输和共用设施建设。

SUN公司的方式

Java构件技术的概述

就像上面说到的,Applet是Java中最初引起广泛关注并取得突破的地方。事实上,Java最初为了使不可靠的并可下载得到的Applet能够在客户端浏览器的进程中执行,在很多地方进行了特别设计,因而,不会造成无法接受的安全隐患。为实现这个目的,在Java中,编译器会检查Applet代码的安全性。这个做法的指导思想是:一个通过了编译器检查的Applet代码不会带来安全隐患。由于编译得到的字节码仍然可能被人修改,代码在装载时刻会被再次检查(称为“校验”)。通过校验的Applet是安全的,并受强制安全策略的约束。这一点对于现有的包括C++和对象Pascal在内的绝大多数编程语言来说都是不可能实现的。当然,安全策略可以在Smalltalk或者Visual Basic这些解释执行的语言中得到加强。然而Java是为允许编写在目标环境下有效执行的代码而设计的。这是通过所谓的“即时编译器(JIT)”实现的。

Java与Java 2

虽然最初Java的规范集深受Applet思想的影响,Java 2平台(在1998年后期发布)打破原有框架,并将Applet改变成一个边缘的角色。Java 2引入了平台版本的概念,从Java规范集中选出,并共同服务于一组特定用户关心的问题。图10-3给出了Java 2的组织形式。更多的关于Java 2的内容将在下面的章节中介绍。作为Java标志性的平台版本,J2EE(Java 2平台企业版)最初于1999年年底发布,并获得了巨大成功。J2EE是一组以EJB为核心的规范,在这些规范之下是由许多不同厂商提供的应用服务器(其中最大的两个厂商是提供WebSphere产品的IBM公司和提供WebLogic产品的BEA公司;到2001年年底,在Flashline.com评测比较表中列出了40个左右的厂商,它们的产品的价格从免费/开放源码到每个CPU 75000美元不等)。微型版本J2ME也相当成功,特别是在用于移动电话的部分,企业版为构件化软件提供了丰富的环境。

除了版本之外,Java 2还区分了运行环境(Runtime Environent, RE)、软件开发工具包(Software Development Kit, SDK)和参考实现。运行环境是Java虚拟机和必须具有的J2SE API的实现。运行环境一般与一个SDK的版本相对应,SDK提供包括编译器和调试器在内的开发工具。容易混淆的是,按顺序为Java 1规范编号的1.x编号被继续用来给Java 2的运行环境和SDK编号。所以,运行环境可以这样提出“Java 2运行环境,标准版,v1.4”。

图10-3 Java 2组织结构找不到图片(Image not found)
运行环境和参考实现

Java运行环境(Java Runtime Environment, JRE)是J2SE平台的一部分,而J2SE本身又是J2EE平台的一个子集。JRE包括运行时刻、核心库和浏览器插件。Sun公司的JRE 1.4参考实现基于HotSpot运行时刻和提供对JIT编译的二进制代码进行在线再优化的HotSpot JIT编译器。单独的HotSpot编译器也有对应客户端和服务器端环境的版本。它们的区别在于根据内存占用历史信息、启动时刻、吞吐量和延时等不同而折中并对目标过程进行优化。Java SDK 1.4在包含Java编译器、调试器、平台调试体系结构API(JPDA)和用于生成文档(javadoc)的工具的同时,也包含了JRE 1.4。图10-4给出了J2SE平台1.4版本的主要结构。

J2EE体系结构概况通过使用专有的构件模型来区分了J2EE支持的范围。JavaBean和它的核心技术可以被用在图中几乎所有的层次。此外,请注意图中的箭头表示了控制流的典型情况,当然,并不完全。数据流一般也沿着同样的路径,但在两个方向都存在。一个底层的用于支持J2EE所有部分的系统是通过JNDI(Java Naming and Directory Interfoce,Java的命名与目录接口)访问的命名和目录的基础结构。另一个集成平台是通过JMS(Java Message Service,Java消息服务)可访问的消息基础结构。在EJB容器的消息驱动构件的帮助下,消息在到来的时候可以触发处理过程。消息和命名/目录是两个重要的集成的层次服务,但也有一些其他的部分,比如事务协作和安全服务。

图10-4 Java 2平台标准版1.4的组织结构(资料来源:java.sun.com)找不到图片(Image not found)
图10-5 J2EE体系结构概况找不到图片(Image not found)

JavaBean

JavaBean填补了部分空白,成为一种新的可行的产品——Java构件,我们称之为Bean(Sun, 1996)。不幸的是,Java中类和对象之间明确的区别并没有被贯彻到JavaBean中。尽管一个Bean的确是一个构件(一系列类和其他资源),但是它的那些定制好的连接的实例仍然被称做Bean。这很让人迷惑。因此,我们用Bean来指代构件,用Bean实例来指代构件对象。“Bean对象”将会带来困扰,因为一个Bean通常包含了许多Java对象。

Bean模型主要包括以下几个方面。

(1)事件:Bean可以声明它们的实例是潜在的事件源或者特定类型事件的监听者。一个组装工具能够把事件源和监听者连接起来。

(2)属性:Bean通过成对的getter和setter方法暴露出一系列的属性。这些属性可以用来进行定制或者编程。属性的变化可以触发事件,也可以被事件强制修改。一个受限的属性只有在修改不被禁止的情况下才可以被修改。

(3)自检:一个组装工具能够检查一个Bean,发现这个Bean的属性、事件,以及所支持的方法。

(4)定制:使用组装工具,一个Bean实例能够通过设置它的各种属性来完成定制。

(5)持久化:定制好的、已经连接的Bean实例需要进行保存,以便在应用程序使用它的时候重新装载。

基本的Java服务

经过这些年,Java已经添加了许多标准服务。这一节我们将看到反射、对象序列化,以及Java本地接口。

反射

Java的核心反射服务是一个集合体,包括原始Java语言的特性、一套支持类(在JDK 1.1中引入),以及支持类文字的语言特性。反射服务受到活动安全策略的约束,它允许我们:

(1)检查类和接口,包括它们的属性域和方法。

(2)构建新的类实例和新数组。

(3)对象和类的属性域的访问和修改。

(4)数组元素的访问和修改。

(5)对象和类的方法调用。

由此,反射服务涵盖了Java语言的所有特性。Java语言级的访问控制机制,比如域的私有性,被大大增强了(无限制的访问对于实现可信任的底层服务,例如便携式调试器,是很有用的。为了实现这些无限制的访问,Java平台的调试器体系结构提供了一个特别的接口——JPDA(Java Platform Debugger Architecture)。为了进行反射操作,反射服务引入了包java.lang.reflect。

类Field,Method,以及Constructor提供了关于属性域、方法和构造器的反射信息,这些信息由它们描述,并对这个域、方法和构造器进行类型安全的使用。这三个类都是最终的,没有公有的构造器。它们三个都实现了接口Member,这使得我们可以弄清楚成员如何被调用,确定成员的改动及该成员属于哪个类或者接口。

对象序列化

在JDK 1.0.2以前,Java都不支持把对象序列化至字节流——仅仅支持基本类型。如果一个应用想把整个对象网写到输出流,它需要使用特别的编码方案来遍历和序列化对象自身。Java对象序列化服务解决了这个问题,它通过定义一个标准的连续编码方案来达到目标,同时提供编码和解码(“序列化”和“反序列化”)对象网的机制。

一个对象能够被序列化,它必须实现接口java.io.Serializable。另外,所有不应该被序列化的域需要用暂时修饰符标记。这一点很重要,因为域可能指向巨大的计算机结构(比如缓存)或者固有绑定到当前JVM的值(比如打开文件的描述符)。对实现Serializable接口的对象而言,足够的信息被写入一个流,以使得反序列化能继续进行,即使使用不同(但是兼容)的类版本。通过实现方法readObject和writeObject,可以进一步控制将哪些信息或者添加更多的信息写入流。如果这些方法没有实现,所有指向可序列化对象的非暂时域将自动被序列化。指向对象的共享引用被保存起来。

Java本地接口

Java本地接口(Java Native Interface, JNI)为每一个平台规定了本地调用方式,在Java虚拟机之外我们可以调用本地代码。JNI还规定了这些外部代码如何按照传递过去的引用来访问Java对象。这包括了调用Java方法的可能性。JNI并没有规定Java二进制对象模型——也就是说,它没有规定在一个特定的Java虚拟机中属性如何被访问及方法如何被调用。同一平台上不同Java虚拟机之间的互操作仍然是一个未解决的问题,比如实时编译器这样的边界服务。JNI允许本地方法:

(1)创建、检查和更新一个Java对象。

(2)调用Java方法。

(3)捕捉和抛出异常。

(4)装载类,获得类的信息。

(5)进行运行时刻类型检查。

Java AWT和JFC/Swing

Java抽象窗口工具包(Abstract Windowing Toolkit, AWT)和Java基础类(Java Foundation Classes, JFC)提供了一个图形用户接口,这对于任何Java开发都具有重要意义。

基于委托的事件模型——这也许是在JDK 1.1中最富有戏剧性的改变。以前的事件模型基于从构件类的继承及事件管理方法的重载。“基于委托”有一点用词不当,因为它沿用了COM中的术语委托。JDK 1.1事实上提供了一个基于转发的事件模型。对象连接和组装被用来更好地实现继承。

● 数据传输和剪贴板支持:就像COM通用数据传输服务,AWT定义了可传输数据项的概念。Internet MIME(多用途Internet邮件扩展)类型被用来和非Java的应用程序相互作用。

Java应用程序之间的数据传输也可以直接使用Java类。

● 拖放:支持在Java和非Java应用程序之间进行拖放(通过连接底层系统的拖放协议,比如Windows中的OLE)。

● Java 2D:新的2D图形和图片类。Java2D包含了线、文本和图片。支持的属性包括图片合成、Alpha合成(透明化)、精确颜色空间定义和转化,以及面向显示的图像操作。

● 打印:打印模型比较简单。非显式地处理打印的图形构件将使用它们的屏幕透视方法被打印出来。因此,对于简单的构件,打印可以随时进行。然而,打印模型并不处理打印那些需要在多个页面分布的嵌入内容(ActiveX打印模型对此提供了支持,它和容器合作,允许嵌入的内容跨业若干页进行打印。这个是一个复杂的模型,然而只有很少的ActiveX容器真正实现了这个高级打印模型)。

● 可访问性:允许所谓的辅助技术来和JFC及AWT构件交互的接口。辅助技术包括屏幕读取器、屏幕放大器和语音识别。

● 国际化:以Unicode 2.1字符编码为基础,提供了对文本、数字、日期、货币,以及用户自定义对象和当地习惯相适应的支持,使用语言和区域标志符来识别正确的格式。

各种构件——Applet,Servlet,Bean和Enterprise Bean

在Java领域中定义了5种不同的构件模型,而且将来可能会出现更多。这其中不仅包括了Applet和JavaBean模型(J2SE的一部分),还有Enterprise JavaBean,servlet和应用程序客户端构件(J2EE的一部分)。本节将对这些不同的构件模型进行简要地描述。

servlet/JSP和EJB是J2EE服务器端模型的两个关键元素,在下面的论述中将对它们进行更详细的介绍。在J2EE中,所有的构件将被打包成JAR文件,这样J2EE应用就可以将这些JAR文件包含进来。J2EE中的构件都有一个很重要的方面,就是它们都支持部署描述符(Deployment Descriptors)。部署描述符是一个XML文件,和相应的构件一起打包,用来描述这个构件应该怎样进行部署。部署是指根据实际的部署语境将一个构件进行准备的动作,这一步骤可以是,也经常是从安装软件的概念中分离出来的。部署描述符的详细内容依赖于特定的构件模型。例如,一个EJB实体Bean的描述符,就要有容器管理的持久性的描述,以及对EJB实体Bean中的属性到数据库中表的映射的详细描述。

Applet是第一个Java构件模型,用于轻量级的可下载的构件,以增强网站在浏览器中的视觉效果。最初的Applet安全模型非常严格,以致Applet不比eye candy发送得多。eye candy是指在通过别的技术捕捉一个区域,这些技术包括GIF动画、Macromedia Shockwave和Flash技术、JScript、对HTML的增强(包括DHTML(动态HTML)的引入)等。为了充分利用浏览器端技术,绝大多数的基于J2EE的应用均使用servlet和JSP来通过脚本生成HTML页面,而不是通过下载Applet来实现。

第二个Java构件模型是JavaBean。它主要用于支持基于连接的程序,比如同时用在客户端和服务器端的程序。从历史上来说,JavaBean在客户端占比重较大的应用中使用得更广泛,而在服务器端有时候还会被EJB所替代。但这种观点从技术角度上来说是错误的:EJB远远不只是它的名字看上去的那样,和JavaBean的相似之处也很少。当建造一个明确支持可视化应用设计模式的客户端应用时,JavaBean依然是有用的(就像J2SE 1.3中所述,一个Bean也可以是一个Applet。但无论如何,这种支持是有限的——BeanApplet所接受的总是空的Applet语境和存根)。

EJB是Java的第三个构件模型。它使用容器集成服务,用以支持EJB(构件)使用声明属性和部署描述符的方式来请求服务。在最新的修订版中,JavaBean也加入了容器模型,但它的容器模型与EJB容器有很大的不同。前者仅仅是一种容纳的机制,而后者则是一种声明型构造驱动的模式。因为JavaBean不需要在设计时之外与用户交互,所以也可以使用JavaBean来构造更复杂的EJB。(JavaBean和EJB与NET框架中的构件类及被服务的构件类大致对应。)

第四个Java构件模型是servlet。它跟Applet相似,但属于服务器端构件模型,而且是通常由Web服务器进程进行实例化的轻量级构件,Web页面就是一个典型的例子。Java Server Page(SP)是一种与之匹配的技术,能够声明式地定义要生成的页面,然后JSP会被编译成servlet。

J2EE引入的第五种Java构件模型为应用客户端构件。它本质上是在客户端的不受限制的Java程序。一个客户端构件通过用命名语境的JNDI企业来访问J2EE服务器中的环境属性、EJB和各种资源。这些资源可以包括对E-mail(通过JavaMail)或数据库(通过JDBC)的访问。

对Java构件模型不同种类的支持是不同的需求的反映。但无论如何,要在这些不同的领域实际地建立构件市场,还需要在基于领域的概念等方面建立更深入的标准化。现在,只有很少的EJB构件在需要开发它们的系统之外的其他系统中使用。

高级Java服务

本节将介绍Java如何在企业级范围支持分布式计算。实际上Java中有4种模式支持分布式计算——RMI,RMI over IIOP,CORBA和EJB容器(本身是建立在RMI或RMI over IIOP上的)。EJB在前面已经论述了。本章将论述其他的几种,以及一些最重要的支持分布式应用的服务。

分布式对象模型和RMI

分布式计算主要由对象序列化服务和远程方法调用(Remote Method Invocation,RMI)服务支持。这两种服务都是在JDK 1.1引入的。下面的章节介绍RMI和RMI over IIOP,它们有细微的差别。

一个分布式对象的句柄为一个接口类型的引用——它不能指向一个remote对象类及其超类。能够远程访问的接口必须直接或间接从java.rmi.Remote继承下来。一个远程操作可以由于网络或远程硬件故障而失败。remote接口的所有方法都要声明检查java.rmi.Remote Exception异常。将参数传给远程操作很有意思,如果一个参数是remote接口类型,那么就是按引用来传,其他类型则按值来传——这就意味着,参数将在调用端序列化,在调用remote接口方法的时候反序列化。Java对象不需要序列化。如果企图传一个无法序列化的对象将会抛出运行时刻异常。如果语言规定使用Java RMI,那么编译器可以静态地规定只有可序列化对象可以通过值来传送,而且所有的方法都声明Remote Exception异常。

Java分布式模型扩展了垃圾回收。它将所有有远程引用指向它们的对象都记录下来,这样就可以支持分布式的垃圾回收了。回收器是基于Network Object(Birrel,1993)的工作的。分布式垃圾回收是RMI比当今其他模型出色的一点。唯一的另外一种基于Network Object的和有租用引用思想的就是CLI remoting,但这种方法的提出比JavaRMI晚了四年。

Java和CORBA

一个OMG IDL到Java的绑定和OMG最先给出的Java到OMG IDL的绑定,是1998年在CORBA 2.2中定义的。将CORBA包含在Java项目的一个重要原因是能用ⅡOP和非Java系统通信。为了能访问CORBA服务,通常使用Java规范定义的接口会更方便,这些接口能够映射到兼容CORBA或其他服务。

企业级服务接口

J2EE的重要部分就是一些适合于企业级服务的接口。这些服务接口也可以通过CORBA来建立。然而,Java-CORBA的集成必然会引起一些冲突。与此相反,在本节讨论的以Java为中心的接口,从客户和实现者的角度来减小这些不足。

1)Java命名和目录接口(JNDI)

在计算系统中的一个全局性问题就是通过名字或属性来定位服务的问题。命名服务针对前一个问题,目录服务针对后一个。命名服务的例子包括Internet域名服务(DNS)、RMI注册表和CORBA命名服务。目录服务的例子包括兼容LDAP的目录系统,比如Novell的eDirectory、微软的Active Directory和开放源码的OpenLDAP(www.openldap.org)。

JNDI为命名服务(javax.naming)和目录服务(javax.naming.directory)提供了统一的API。Context这个最普遍使用的接口使命名语境对lookup方法有效,使用这个方法就可以根据名字来定位对象了。一个命名语境也可以用来对绑定在某个语境中的名字进行列表,或者是解除一个绑定,或是创建或删除一个子语境。

EJB的Bean的一个重要的命名语境就是EJB容器提供的环境命名语境(Environment Narning Context, ENC)。通过它可以访问环境属性、其他Bean和资源。接口DirContext扩展Context来提供目录功能,包括检查和更新与目录上列出的对象相关联的属性,以及通过值来搜索一个目录第10章 构件平台与典型架构

几乎没有构件能独立的部署,它们大多数依赖于特定的基础设施平台。由于行业高度竞争,公用构建基础设施目前只有CORBA+Java和Microsoft COM+CLR两大阵营。尽管只有两大阵营,SOA技术也飞速发展,不同平台构件连接能力有了一定改善,但在设计、管理、规范等方面存在很大差异。因此,我们有必要了解这些平台特点和差异,为应用开发选择合适的构件开发平台。

10.1 OMG方式

成立于1989年的对象管理组(OMG)是目前计算工业中最大的组织。作为一个非营利性组织,OMG旨在通过规范化对象开放市场的所有层次上的互操作性。至2002年,有近800成员加入OMG。

10.1.1 对象请求代理

CORBA的主要目标就是使不同语言、不同实现和不同平台间能进行交互。因此,OMG从没有停步在“二进制”标准上(可配置、可执行级的标准),而是保证每个细节都被标准化,使其能顾及不同的实现及CORBA兼容不同产品的独立供应商增值的需要。这一开放式方法的不利一面就是CORBA兼容产品不能在二进制级进行有效的互操作,只能以较高的代价在高层协议上协作。OMG的跨ORB(对象请求代理)协议——IIOP(Internet InterORB Protocol)互操作协议,在1995年7月的CORBA 2.0中被规范化。与ORB的互操作兼容则必须支持ⅡOP。在1996年7月的CORBA 2.0更新版本中,加入了一条关于相互作用的协议,该协议明确了基于CORBA的系统与基于微软COM系统之间的互操作细节。

CORBA包括三个基本部分:一套调用接口、对象请求代理(ORB)和一套对象适配器。面向对象操作的调用实现后期绑定。对象引用所指代的对象实现决定了被调用方法的最终实现。调用接口支持不同级别的后期绑定,同时编排调用参数,使ORB核心能定位接收对象,调用方法,以及传递参数。在接收端,一个对象适配器还原参数,调用接收对象相应的方法。图10-1简单地描述了基本的CORBA结构。

10.1.2 公共对象服务规范

现有的CORBA Service包含16种对象服务(CORBA服务),其中的通告服务是电信领域设施正式的组成部分。这些服务划分为两大类:一类服务应用于企业计算系统。这些系统往往将CORBA对象视为模块,并视CORBA为易用的通信中间件,此时的CORBA服务大多用来支持大规模的操作;另一类服务则应用于细粒度的对象操作,但目前这些服务的实用价值较差。CORBA 3.0中的持久状态服务(Persistent State Service, PSS)可能是一个例外,它替代了CORBA 2.0中的持久对象服务(POS)。PSS是CORBA构件模型的三个主要支撑服务之一,另两个是事务服务和通告服务。值得注意的是,大型基于CORBA系统往往只使用少量的CORBA服务,包括名字服务、安全服务、事务服务和交易服务。现有大部分ORB产品并不试图支持全部的CORBA服务也说明了这一点。

alt

图10-1 基于ORB系统的简化结构

1.支持企业分布式计算的服务

许多大型企业系统只是将CORBA作为对象总线,依靠ORB与其他各种各样的系统进行互操作。名字服务是关键服务之一。

1)命名服务,交易器服务

每个对象内部都有唯一的标志符。命名服务则允许任意地给对象赋予一个名字,这个名字在其所属的命名语境中是唯一的。而命名语境所形成的层次结构,使得所有的名字形成名字树。

交易器服务允许给对象赋予一个复杂的描述,从而允许客户基于该描述来定位所需的对象。交易器通过交易语境来组织对象。客户则在指定的交易语境中根据对象描述的部分内容或关键字来搜寻对象,而搜寻结果往往是一个包含满足查询条件的一组对象的列表。OMG交易器服务规范同时被ISO(ISO/IEC 13235-1)和ITU(ITU-T推荐X.950)所采用。

2)事件服务,通告服务

事件服务允许定义那些从事件生产者被发送到事件消费者的事件对象。由于信息只能从生产者流向消费者,因而事件对象是不变的。事件必须通过事件通道传播,从而松散了生产者和消费者之间的耦合关系。事件可以具有类型(使用OMG IDL定义),而通道可以根据类型过滤事件。

事件通道支持“推”和“拉”两种方式的事件通告模型。在“推”模型中,事件生产者调用事件通道的“推”方法将事件上传给事件通道,事件通道进而调用所有注册的事件消费者的“推”方法将事件传给消费者。在“拉”模型中,事件消费者调用事件通道的“拉”方法,这将导致事件通道调用所有注册的事件生产者的“拉”方法,此时获得的新事件将返回给消费者。

1998年,通告服务为事件服务增加了几个重要的特征——服务质量(Quality of Service, QoS)规范和管理;标准的类型化和结构化事件;基于类型和QoS的动态事件过滤;作用于资源、通道、一组消费者或单个消费者的事件过滤;针对资源、通道和客户的事件发现。值得注意的是,通告服务本身并不是CORBA服务,而是电信领域工作组(Te1DTF)提交的CORBA设施。

3)对象事务服务

对象事务服务(Object Transaction Service, OTS)是建立分布式应用最重要的服务之一。OMG于1994年12月制定的OTS规范在大多数的ORB产品及若干J2EE服务器中得到支持。OTS实现必须支持平坦事务,而嵌套事务是可选的。遵循X/Open分布事务处理标准的其他事务服务可以与OTS集成。同样,多个异构ORB提供的事务也可集成。

在基于构件的系统中,嵌套事务似乎不可避免。因为一个构件的实现可能创建一个覆盖一系列操作的事务闭包,而这些事务属性无须在构件接口中声明。这种独立扩展性原则需要嵌套事务的支持。作为唯一的OTS实现必须支持的事务类型,平坦事务在构件系统中的价值有限。实际上,现有的主流事务中间件也不支持嵌套事务,这是它们共同的缺点。

OTS自动维护当前的事务语境,该语境将随请求在ORB系统中传递,也可传递给其他的非CORBA的事务系统。对于CORBA对象,事务语境可以传递给任何实现了Transactional Object的对象。当前的事务语境可从ORB获得,因此必须保证随时可用。事务操作,如begin, commit, rollback都在当前的语境中定义。

所有希望在一次事务中执行修改,或者需要执行事务控制的对象都必须向OTS协调器注册。该协调器可从当前语境中获得。一个资源可以指明它是否支持嵌套事务。任何资源都必须实现Resource接口,从而允许协调器执行两段提交协议(众所周知,两段提交协议在完全分布的环境下可能发生死锁,这只能通过特定的广播协议避免;三段提交协议能够避免死锁问题(Mullender, 1993),但开销太大。因此,OTS规定协调器在逻辑上必须是集中式的)。

OTS的设计目标之一是希望将事务控制作为一个独立的服务,但目前更普遍的是将事务和其他服务集成到应用服务器提供的语境或容器中。

4)安全服务

可靠的安全服务对于一个跨越多个相互信赖的组织的分布系统极为重要。安全服务必须得到普及。所有可互操作的ORB或可共同工作的系统必须协作,而这要求为所有的参与者建立统一的安全策略。

CORBA安全规范定义了一系列的服务,包括认证、安全通信、证书委托(也称为“替身”)及防抵赖等。目前仅有少数产品完全支持该规范,如BEA的WebLogic和IBM的WebSphere。很多产品仅仅依赖Netscape的安全套接字(Secure Socket Layer, SSL)实施安全保障,尤其采用独立ORB,而不是完全集成的应用服务器时。因为利用SSL可以很方便地实现简单的认证及安全的通信,但不能支持类似委托和防抵赖等较高级的安全机制。

5)支持细粒度对象互操作的服务

尽管有些服务,包括收集、外部化和查询服务,仍未被任何产品实现(原因很多,如查询服务的规范过于松散,收集服务的某些假设不切实际,本节仍将介绍余下的服务,以便读者对OMA涉及的对象服务有一个全面的认识。

6)并发控制服务

该服务支持对资源进行加锁和解锁。锁必须依赖于事务的语境或其他语境才能获得。依赖事务语境创建的锁将作为事务回滚的部分被释放。锁具有不同的模式,如读锁、写锁、升级锁。其中,读锁允许多个客户同时执行读操作;而写锁保证只有一个客户才能执行写操作;升级锁是可以升级为写锁的读锁,支持互斥的读操作。锁有多个锁集合。每个受保护的资源都拥有一个锁集合,该集合决定了可用的锁的种类及数量。一个锁集合的工厂接口支持创建新的锁集合。锁集合不是事务型就是非事务型的,并可与其他锁集合建立关联。锁协调器可以释放指定锁集合中所有的锁。

7)许可服务

组装构件的过程中需要获取所有非免费构件的使用许可。许可服务支持多种类型的许可模式。该服务定义了两个接口(抽象)——许可服务管理器和特定于厂商的许可服务。如果一个对象与一个许可协议绑定,那么它可以通过许可服务管理器检查其使用是否合法。

8)生命周期服务

这类服务支持创建、复制、移动和删除CORBA对象及其相关的对象组。下面将介绍如何利用关系服务提供的包含与引用关系来处理对象组。包含关系支持嵌套复制,即所有被包含的对象都会被复制。为了支持对象创建,生命周期服务支持注册与获取工厂对象。一旦获得所需的工厂对象,就能够用它来创建新的对象。

生命周期服务允许删除对象或对象组,但并不提示何时销毁该对象。这意味着分布式内存管理需要高层应用的参与,这被认为是CORBA较为明显的缺点。相比较而言,DCOM支持分布引用的计数,Java和CLR甚至支持基于租借的远程引用的分布式垃圾收集。

9)关系服务

关系服务指允许定义和维护对象之间的关系。不依赖语言级的指针或引用,该服务引入了一种关系模型,以支持在不影响相关对象的情况下创建对象间的关系。但是,关系服务基本上没有实际应用,甚至没有产品实现,极有可能被基于CCM的业务对象关系所取代。

10)持久状态服务

持久性是指对象在其创建程序终止后仍然存活。为此,CORBA 2.0制定了持久对象服务(POS),用来支持CORBA对象的持久性。尽管在1994年年初就被OMG标准化为关键服务,但直到1996年年中才出现一个beta版的实现。一些报告甚至指出该规范及其与其他对象服务的互操作存在严重的技术问题。另外,POS没有解决“正确性”的问题,尤其是它把存储的申请交给应用代码处理。POS规范最终在CORBA 3.0版本中被新的持久状态服务(PSS)所代替。

11)外部化服务

这项服务支持对象网和对象流之间的双向映射。对象网外部化后再内部化意味着创建该对象网的副本。外部化服务并不保证引用的完整性,仅保留同时外部化的对象之间的引用。外部化使得对象网的值复制成为可能。而外部化对象所需的其他对象的引用可保存为ORB为对象引用提供的字符串标志。

对象必须实现Streamable接口才能被外部化。为了外部化一个Streamable对象,必须首先调用实现了Stream接口的某个对象的外部化方法,该方法将调用流对象的externalize_to_stream方法,并传递一个实现StreamIO接口的对象。最后,流对象将任何OMG IDL定义的数据类型或实现写入streamIO对象。流对象也可以外部化由关系服务定义的整个对象图表。

12)属性服务

这种服务允许将任意的属性与对象关联起来,被关联对象必须实现PropertySet接口。属性可以独立地或成组地添加、获取和删除。如果一个对象还实现了PropertySetDef接口,则可按以下4种模式中的任一种进一步控制属性,这4种模式是:标准属性(可以修改和删除)、只读属性(能被删除但是不能修改)、强制标准属性(能被修改但不能删除)和强制只读属性。

属性服务并不说明属性的语义和内容。一般而言,对于程序有用的属性都需要由程序显式地赋予相应的信息。作为一个重要的例子,系统管理工具被赋予“粘贴”特性来有效地跟踪对象。

13)对象查询服务

该服务用来依靠属性定位对象。该服务类似于对象交易服务,但该服务定位对象实例而不是定位服务器。查询使用的属性由对象公布或者允许通过操作获得。有两种查询语言可供选择,面向对象数据库管理组的ODMG-93对象查询语言(Object Query Language, OQL)和扩展的SQL。一个更为普遍的查询语言正在建立。

查询服务定义了其自身的一个简单的集合服务——是通用集合服务的子集。查询结果集返回给用户时会用到集合。这些简单的集合提供了有序集的语义,包括增加或删除元素和元素集的操作。服务提供了一个Iterator接口来支持对集合元素的遍历。

14)对象集合服务

对象集合服务支持各种抽象拓扑集合,例如,包、集合、队列、表、树、角色模型是Smalltalk集合类库(Goldberg与Robson,1983,1989)。CORBA的集合服务(基于CORBA对象的相对重权模型)是否可与本地的对象集合库竞争是一个有争议的问题,另外,对象库可能更适于在ORB间传输各种形态与属性的集合。

15)时间服务

这一服务处理拥有众多异步时钟的分布式系统固有的误差问题。许多应用程序中,用实时信息将内部事件(如创建文件)与外部通用时间建立关联。一个时间服务必须在允许的误差范围内实现这种关联,并避免其他非因果的关联。例如,假设一个新对象的产生是对另一个对象触发某事件的反应,那么,如果给前一个对象赋予“生成日期”的时间戳,而该时间戳却先于后一个对象产生的时刻,此时就会产生一个非因果的时间信息——这恰恰是非因果时间服务的典型结果。

10.1.3 CORBA构件模型

CORBA 3.0是CORBA标准中最新的一个。尽管2002年6月左右该规范最后部分仍未定稿,但针对CORBA 2.0全面的改进已经获得了显著的进展。除了对象服务的全面修订,最主要的成就恐怕就是新的CORBA构件模型(CCM)——尽管最终的CCM规范的发布仍未定案。(有时,CCM也被称做CORBA构件。)

1.可移植对象适配器

CORBA对象适配器主要的作用就是在一个ORB和真正接收调用并且返回结果的对象实现之间进行协调。目前采用的对象适配器的规范针对可移植的对象适配器,它代替了已过时的基本对象适配器。目前还没有其他的对象适配器规范。这种可移植对象适配器的一个实例为一组对象接收请求。任何ORB支持的服务器进程至少有一个POA(Portable Object Adapter)的实例,当然,该进程中的每个服务对象都可能有一个POA实例。

一个POA实例通过将收到的请求传递给一个“服务体”来对其进行处理。“服务体”是CORBA对象的实现。图10-2给出了一个典型的工具使用场景,从一个IDL定义开始,客户端的指代,服务器端的POA骨架,服务器端的“服务体”模板被一一建立。开发者可以通过完成该模板来补充实现细节。CORBA对象不强制使用面向对象语言,因而“服务体”也不一定为“类”。如果使用了面向对象语言,那么“服务体”就是类的实例。

2.CCM构件

一个CCM应用程序是CCM构件的一个组装,其中构件可以是客户创建的或者是现成的、企业内部的或者是后来获得的。企业级JavaBean构件和CCM构件能够在一个应用程序中集成在一起。单个构件通过构件包发布,该构件包含有一个描述其内容的XML文档,还可以包含支持不同平台的二进制代码。CCM的装配包含一个描述它们所引用的构件包信息的XML文档,以及它们的部署配置。

alt

图10-2 根据IDL文件生成指代、POA骨架、服务体模板

3.CCM容器

CORBA 3.0定义了一个构件实现框架(Component Implementation Framework, CIF),其中包括接收CIDL(Component Implementation Definition Language,构件实现描述语言)输入并产生实现代码的生成器。另外,每个构件实例都放在一个CCM容器里。构件通过容器的接口与POA、事务、安全、持久化及通知服务相作用。一个容器同样也有插座接口来接收对构件实例的回调。

10.1.4 CORBA设施

CORBA设施可以分为水平的(普遍的)和垂直的(特定领域的)支持。不管是哪种支持,每个CORBA设施都定义了一个特定的构件框架,从而能够集成构件。最初,OMG试图标准化4个领域的水平设施:用户界面、信息管理、系统管理和任务管理。但是这些努力都失败了,而且水平设施在今天的OMA(Open Mobile Architecture)中的影响力很弱,之所以保留下来,是因为垂直设施的工作很可能产生并不特定于单个领域的设施。水平设施的例子,或者已经被标准化或者正在考虑之中,包括全球服务、移动代理、时间和打印设施。

领域任务组定义了垂直设施。在2002年年初,有10个这样的任务组:商业企业集成命令控制、计算机通信和集成、财政、卫生保健、生命科学研究、制造业、空间、电信、运输和共用设施建设。

10.2 SUN公司的方式

10.2.1 Java构件技术的概述

就像上面说到的,Applet是Java中最初引起广泛关注并取得突破的地方。事实上,Java最初为了使不可靠的并可下载得到的Applet能够在客户端浏览器的进程中执行,在很多地方进行了特别设计,因而,不会造成无法接受的安全隐患。为实现这个目的,在Java中,编译器会检查Applet代码的安全性。这个做法的指导思想是:一个通过了编译器检查的Applet代码不会带来安全隐患。由于编译得到的字节码仍然可能被人修改,代码在装载时刻会被再次检查(称为“校验”)。通过校验的Applet是安全的,并受强制安全策略的约束。这一点对于现有的包括C++和对象Pascal在内的绝大多数编程语言来说都是不可能实现的。当然,安全策略可以在Smalltalk或者Visual Basic这些解释执行的语言中得到加强。然而Java是为允许编写在目标环境下有效执行的代码而设计的。这是通过所谓的“即时编译器(JIT)”实现的。

1.Java与Java 2

虽然最初Java的规范集深受Applet思想的影响,Java 2平台(在1998年后期发布)打破原有框架,并将Applet改变成一个边缘的角色。Java 2引入了平台版本的概念,从Java规范集中选出,并共同服务于一组特定用户关心的问题。图10-3给出了Java 2的组织形式。更多的关于Java 2的内容将在下面的章节中介绍。作为Java标志性的平台版本,J2EE(Java 2平台企业版)最初于1999年年底发布,并获得了巨大成功。J2EE是一组以EJB为核心的规范,在这些规范之下是由许多不同厂商提供的应用服务器(其中最大的两个厂商是提供WebSphere产品的IBM公司和提供WebLogic产品的BEA公司;到2001年年底,在Flashline.com评测比较表中列出了40个左右的厂商,它们的产品的价格从免费/开放源码到每个CPU 75000美元不等)。微型版本J2ME也相当成功,特别是在用于移动电话的部分,企业版为构件化软件提供了丰富的环境。

除了版本之外,Java 2还区分了运行环境(Runtime Environent, RE)、软件开发工具包(Software Development Kit, SDK)和参考实现。运行环境是Java虚拟机和必须具有的J2SE API的实现。运行环境一般与一个SDK的版本相对应,SDK提供包括编译器和调试器在内的开发工具。容易混淆的是,按顺序为Java 1规范编号的1.x编号被继续用来给Java 2的运行环境和SDK编号。所以,运行环境可以这样提出“Java 2运行环境,标准版,v1.4”。

alt

图10-3 Java 2组织结构

2.运行环境和参考实现

Java运行环境(Java Runtime Environment, JRE)是J2SE平台的一部分,而J2SE本身又是J2EE平台的一个子集。JRE包括运行时刻、核心库和浏览器插件。Sun公司的JRE 1.4参考实现基于HotSpot运行时刻和提供对JIT编译的二进制代码进行在线再优化的HotSpot JIT编译器。单独的HotSpot编译器也有对应客户端和服务器端环境的版本。它们的区别在于根据内存占用历史信息、启动时刻、吞吐量和延时等不同而折中并对目标过程进行优化。Java SDK 1.4在包含Java编译器、调试器、平台调试体系结构API(JPDA)和用于生成文档(javadoc)的工具的同时,也包含了JRE 1.4。图10-4给出了J2SE平台1.4版本的主要结构。

J2EE体系结构概况通过使用专有的构件模型来区分了J2EE支持的范围。JavaBean和它的核心技术可以被用在图中几乎所有的层次。此外,请注意图中的箭头表示了控制流的典型情况,当然,并不完全。数据流一般也沿着同样的路径,但在两个方向都存在。一个底层的用于支持J2EE所有部分的系统是通过JNDI(Java Naming and Directory Interfoce,Java的命名与目录接口)访问的命名和目录的基础结构。另一个集成平台是通过JMS(Java Message Service,Java消息服务)可访问的消息基础结构。在EJB容器的消息驱动构件的帮助下,消息在到来的时候可以触发处理过程。消息和命名/目录是两个重要的集成的层次服务,但也有一些其他的部分,比如事务协作和安全服务。

alt

图10-4 Java 2平台标准版1.4的组织结构(资料来源:java.sun.com)

alt

图10-5 J2EE体系结构概况

10.2.2 JavaBean

JavaBean填补了部分空白,成为一种新的可行的产品——Java构件,我们称之为Bean(Sun, 1996)。不幸的是,Java中类和对象之间明确的区别并没有被贯彻到JavaBean中。尽管一个Bean的确是一个构件(一系列类和其他资源),但是它的那些定制好的连接的实例仍然被称做Bean。这很让人迷惑。因此,我们用Bean来指代构件,用Bean实例来指代构件对象。“Bean对象”将会带来困扰,因为一个Bean通常包含了许多Java对象。

Bean模型主要包括以下几个方面。

(1)事件:Bean可以声明它们的实例是潜在的事件源或者特定类型事件的监听者。一个组装工具能够把事件源和监听者连接起来。

(2)属性:Bean通过成对的getter和setter方法暴露出一系列的属性。这些属性可以用来进行定制或者编程。属性的变化可以触发事件,也可以被事件强制修改。一个受限的属性只有在修改不被禁止的情况下才可以被修改。

(3)自检:一个组装工具能够检查一个Bean,发现这个Bean的属性、事件,以及所支持的方法。

(4)定制:使用组装工具,一个Bean实例能够通过设置它的各种属性来完成定制。

(5)持久化:定制好的、已经连接的Bean实例需要进行保存,以便在应用程序使用它的时候重新装载。

10.2.3 基本的Java服务

经过这些年,Java已经添加了许多标准服务。这一节我们将看到反射、对象序列化,以及Java本地接口。

1.反射

Java的核心反射服务是一个集合体,包括原始Java语言的特性、一套支持类(在JDK 1.1中引入),以及支持类文字的语言特性。反射服务受到活动安全策略的约束,它允许我们:

(1)检查类和接口,包括它们的属性域和方法。

(2)构建新的类实例和新数组。

(3)对象和类的属性域的访问和修改。

(4)数组元素的访问和修改。

(5)对象和类的方法调用。

由此,反射服务涵盖了Java语言的所有特性。Java语言级的访问控制机制,比如域的私有性,被大大增强了(无限制的访问对于实现可信任的底层服务,例如便携式调试器,是很有用的。为了实现这些无限制的访问,Java平台的调试器体系结构提供了一个特别的接口——JPDA(Java Platform Debugger Architecture)。为了进行反射操作,反射服务引入了包java.lang.reflect。

类Field,Method,以及Constructor提供了关于属性域、方法和构造器的反射信息,这些信息由它们描述,并对这个域、方法和构造器进行类型安全的使用。这三个类都是最终的,没有公有的构造器。它们三个都实现了接口Member,这使得我们可以弄清楚成员如何被调用,确定成员的改动及该成员属于哪个类或者接口。

2.对象序列化

在JDK 1.0.2以前,Java都不支持把对象序列化至字节流——仅仅支持基本类型。如果一个应用想把整个对象网写到输出流,它需要使用特别的编码方案来遍历和序列化对象自身。Java对象序列化服务解决了这个问题,它通过定义一个标准的连续编码方案来达到目标,同时提供编码和解码(“序列化”和“反序列化”)对象网的机制。

一个对象能够被序列化,它必须实现接口java.io.Serializable。另外,所有不应该被序列化的域需要用暂时修饰符标记。这一点很重要,因为域可能指向巨大的计算机结构(比如缓存)或者固有绑定到当前JVM的值(比如打开文件的描述符)。对实现Serializable接口的对象而言,足够的信息被写入一个流,以使得反序列化能继续进行,即使使用不同(但是兼容)的类版本。通过实现方法readObject和writeObject,可以进一步控制将哪些信息或者添加更多的信息写入流。如果这些方法没有实现,所有指向可序列化对象的非暂时域将自动被序列化。指向对象的共享引用被保存起来。

3.Java本地接口

Java本地接口(Java Native Interface, JNI)为每一个平台规定了本地调用方式,在Java虚拟机之外我们可以调用本地代码。JNI还规定了这些外部代码如何按照传递过去的引用来访问Java对象。这包括了调用Java方法的可能性。JNI并没有规定Java二进制对象模型——也就是说,它没有规定在一个特定的Java虚拟机中属性如何被访问及方法如何被调用。同一平台上不同Java虚拟机之间的互操作仍然是一个未解决的问题,比如实时编译器这样的边界服务。JNI允许本地方法:

(1)创建、检查和更新一个Java对象。

(2)调用Java方法。

(3)捕捉和抛出异常。

(4)装载类,获得类的信息。

(5)进行运行时刻类型检查。

4.Java AWT和JFC/Swing

Java抽象窗口工具包(Abstract Windowing Toolkit, AWT)和Java基础类(Java Foundation Classes, JFC)提供了一个图形用户接口,这对于任何Java开发都具有重要意义。

基于委托的事件模型——这也许是在JDK 1.1中最富有戏剧性的改变。以前的事件模型基于从构件类的继承及事件管理方法的重载。“基于委托”有一点用词不当,因为它沿用了COM中的术语委托。JDK 1.1事实上提供了一个基于转发的事件模型。对象连接和组装被用来更好地实现继承。

● 数据传输和剪贴板支持:就像COM通用数据传输服务,AWT定义了可传输数据项的概念。Internet MIME(多用途Internet邮件扩展)类型被用来和非Java的应用程序相互作用。Java应用程序之间的数据传输也可以直接使用Java类。

● 拖放:支持在Java和非Java应用程序之间进行拖放(通过连接底层系统的拖放协议,比如Windows中的OLE)。

● Java 2D:新的2D图形和图片类。Java2D包含了线、文本和图片。支持的属性包括图片合成、Alpha合成(透明化)、精确颜色空间定义和转化,以及面向显示的图像操作。

● 打印:打印模型比较简单。非显式地处理打印的图形构件将使用它们的屏幕透视方法被打印出来。因此,对于简单的构件,打印可以随时进行。然而,打印模型并不处理打印那些需要在多个页面分布的嵌入内容(ActiveX打印模型对此提供了支持,它和容器合作,允许嵌入的内容跨业若干页进行打印。这个是一个复杂的模型,然而只有很少的ActiveX容器真正实现了这个高级打印模型)。

● 可访问性:允许所谓的辅助技术来和JFC及AWT构件交互的接口。辅助技术包括屏幕读取器、屏幕放大器和语音识别。

● 国际化:以Unicode 2.1字符编码为基础,提供了对文本、数字、日期、货币,以及用户自定义对象和当地习惯相适应的支持,使用语言和区域标志符来识别正确的格式。

10.2.4 各种构件——Applet,Servlet,Bean和Enterprise Bean

在Java领域中定义了5种不同的构件模型,而且将来可能会出现更多。这其中不仅包括了Applet和JavaBean模型(J2SE的一部分),还有Enterprise JavaBean,servlet和应用程序客户端构件(J2EE的一部分)。本节将对这些不同的构件模型进行简要地描述。

servlet/JSP和EJB是J2EE服务器端模型的两个关键元素,在下面的论述中将对它们进行更详细的介绍。在J2EE中,所有的构件将被打包成JAR文件,这样J2EE应用就可以将这些JAR文件包含进来。J2EE中的构件都有一个很重要的方面,就是它们都支持部署描述符(Deployment Descriptors)。部署描述符是一个XML文件,和相应的构件一起打包,用来描述这个构件应该怎样进行部署。部署是指根据实际的部署语境将一个构件进行准备的动作,这一步骤可以是,也经常是从安装软件的概念中分离出来的。部署描述符的详细内容依赖于特定的构件模型。例如,一个EJB实体Bean的描述符,就要有容器管理的持久性的描述,以及对EJB实体Bean中的属性到数据库中表的映射的详细描述。

Applet是第一个Java构件模型,用于轻量级的可下载的构件,以增强网站在浏览器中的视觉效果。最初的Applet安全模型非常严格,以致Applet不比eye candy发送得多。eye candy是指在通过别的技术捕捉一个区域,这些技术包括GIF动画、Macromedia Shockwave和Flash技术、JScript、对HTML的增强(包括DHTML(动态HTML)的引入)等。为了充分利用浏览器端技术,绝大多数的基于J2EE的应用均使用servlet和JSP来通过脚本生成HTML页面,而不是通过下载Applet来实现。

第二个Java构件模型是JavaBean。它主要用于支持基于连接的程序,比如同时用在客户端和服务器端的程序。从历史上来说,JavaBean在客户端占比重较大的应用中使用得更广泛,而在服务器端有时候还会被EJB所替代。但这种观点从技术角度上来说是错误的:EJB远远不只是它的名字看上去的那样,和JavaBean的相似之处也很少。当建造一个明确支持可视化应用设计模式的客户端应用时,JavaBean依然是有用的(就像J2SE 1.3中所述,一个Bean也可以是一个Applet。但无论如何,这种支持是有限的——BeanApplet所接受的总是空的Applet语境和存根)。

EJB是Java的第三个构件模型。它使用容器集成服务,用以支持EJB(构件)使用声明属性和部署描述符的方式来请求服务。在最新的修订版中,JavaBean也加入了容器模型,但它的容器模型与EJB容器有很大的不同。前者仅仅是一种容纳的机制,而后者则是一种声明型构造驱动的模式。因为JavaBean不需要在设计时之外与用户交互,所以也可以使用JavaBean来构造更复杂的EJB。(JavaBean和EJB与NET框架中的构件类及被服务的构件类大致对应。)

第四个Java构件模型是servlet。它跟Applet相似,但属于服务器端构件模型,而且是通常由Web服务器进程进行实例化的轻量级构件,Web页面就是一个典型的例子。Java Server Page(SP)是一种与之匹配的技术,能够声明式地定义要生成的页面,然后JSP会被编译成servlet。

J2EE引入的第五种Java构件模型为应用客户端构件。它本质上是在客户端的不受限制的Java程序。一个客户端构件通过用命名语境的JNDI企业来访问J2EE服务器中的环境属性、EJB和各种资源。这些资源可以包括对E-mail(通过JavaMail)或数据库(通过JDBC)的访问。

对Java构件模型不同种类的支持是不同的需求的反映。但无论如何,要在这些不同的领域实际地建立构件市场,还需要在基于领域的概念等方面建立更深入的标准化。现在,只有很少的EJB构件在需要开发它们的系统之外的其他系统中使用。

10.2.5 高级Java服务

本节将介绍Java如何在企业级范围支持分布式计算。实际上Java中有4种模式支持分布式计算——RMI,RMI over IIOP,CORBA和EJB容器(本身是建立在RMI或RMI over IIOP上的)。EJB在前面已经论述了。本章将论述其他的几种,以及一些最重要的支持分布式应用的服务。

1.分布式对象模型和RMI

分布式计算主要由对象序列化服务和远程方法调用(Remote Method Invocation,RMI)服务支持。这两种服务都是在JDK 1.1引入的。下面的章节介绍RMI和RMI over IIOP,它们有细微的差别。

一个分布式对象的句柄为一个接口类型的引用——它不能指向一个remote对象类及其超类。能够远程访问的接口必须直接或间接从java.rmi.Remote继承下来。一个远程操作可以由于网络或远程硬件故障而失败。remote接口的所有方法都要声明检查java.rmi.Remote Exception异常。将参数传给远程操作很有意思,如果一个参数是remote接口类型,那么就是按引用来传,其他类型则按值来传——这就意味着,参数将在调用端序列化,在调用remote接口方法的时候反序列化。Java对象不需要序列化。如果企图传一个无法序列化的对象将会抛出运行时刻异常。如果语言规定使用Java RMI,那么编译器可以静态地规定只有可序列化对象可以通过值来传送,而且所有的方法都声明Remote Exception异常。

Java分布式模型扩展了垃圾回收。它将所有有远程引用指向它们的对象都记录下来,这样就可以支持分布式的垃圾回收了。回收器是基于Network Object(Birrel,1993)的工作的。分布式垃圾回收是RMI比当今其他模型出色的一点。唯一的另外一种基于Network Object的和有租用引用思想的就是CLI remoting,但这种方法的提出比JavaRMI晚了四年。

2.Java和CORBA

一个OMG IDL到Java的绑定和OMG最先给出的Java到OMG IDL的绑定,是1998年在CORBA 2.2中定义的。将CORBA包含在Java项目的一个重要原因是能用ⅡOP和非Java系统通信。为了能访问CORBA服务,通常使用Java规范定义的接口会更方便,这些接口能够映射到兼容CORBA或其他服务。

3.企业级服务接口

J2EE的重要部分就是一些适合于企业级服务的接口。这些服务接口也可以通过CORBA来建立。然而,Java-CORBA的集成必然会引起一些冲突。与此相反,在本节讨论的以Java为中心的接口,从客户和实现者的角度来减小这些不足。

1)Java命名和目录接口(JNDI)

在计算系统中的一个全局性问题就是通过名字或属性来定位服务的问题。命名服务针对前一个问题,目录服务针对后一个。命名服务的例子包括Internet域名服务(DNS)、RMI注册表和CORBA命名服务。目录服务的例子包括兼容LDAP的目录系统,比如Novell的eDirectory、微软的Active Directory和开放源码的OpenLDAP(www.openldap.org)。

JNDI为命名服务(javax.naming)和目录服务(javax.naming.directory)提供了统一的API。Context这个最普遍使用的接口使命名语境对lookup方法有效,使用这个方法就可以根据名字来定位对象了。一个命名语境也可以用来对绑定在某个语境中的名字进行列表,或者是解除一个绑定,或是创建或删除一个子语境。

EJB的Bean的一个重要的命名语境就是EJB容器提供的环境命名语境(Environment Narning Context, ENC)。通过它可以访问环境属性、其他Bean和资源。接口DirContext扩展Context来提供目录功能,包括检查和更新与目录上列出的对象相关联的属性,以及通过值来搜索一个目录语境。因为DirContext是从Context继承而来的,所以一个目录语境也是命名语境。绝大部分的语境是通过对其他语境的递归查找而找到的。而查找的起始点就是通过初始化InitialContext这个类得到的。

JNDI也定义了一个事件API(javax.naming.event)、一个支持LDAP v3的超过DirContext的功能,以及一个能够使命名和目录服务的提供者与JNDI连接起来的服务提供者接口(javax.naming.spi)。事件机制用来为改变通知进行注册。J2SE 1.4内置了4种服务提供者——CORBA命名、DNS、LDAP和RMI。

2)Java消息服务(JMS)

异步消息是将实例的操作和覆盖的组装模型通过消息进行通信。基于事务的消息队列建立的可靠性级别,正常情况下需要基于同步调用的模型。灵活的消息路由、广播和过滤增强了灵活性。JMS是Java对消息系统的访问机制,但它本身并不实现消息。

$\color{red}{\text{JMS}}$ 支持点对点分发的消息队列,也支持多个目标订阅的消息主题。当消息发布给一个主题的适合,消息就会发送给所有那个主题的订阅者。JMS支持各种消息类型(二进制、流、名—值表、序列化的对象和文本)。通过声明与SQL的WHERE相近的句段,可以建立消息的过滤器。

3)Java数据库连接(JDBC)

JDBC是根据流行的微软ODBC(Open DataBase Connectivity,开放数据库连接)标准建立的一个通用的与数据库交互的方法。JDBC API分成核心API(在java.sql包和J2SE的一部分中)和JDBC可选包(在javax.sql包中,J2SE可选但J2EE必须遵循)。JDBC像ODBC一样,需要驱动程序将JDBC API映射到特定数据库的本地接口。

JDBC驱动程序有4种。Type 1和Type 2驱动通过JNI来使用本地代码(非Java代码)。Type 1驱动使用具有通用接口的本地代码,而Type 2允许使用数据库特定的接口。最普遍的Type 1驱动就是JDK包含的JDBC-ODBC桥,它将JDBC调用映射为ODBC调用。因为ODBC是用字句的驱动模型来访问特定的数据库,因此这种方式相对会慢一些。Type 3和Type 4驱动都是纯Java的。Type 3通过网络协议和数据库网关来间接地访问数据库,Type 4则是直接访问数据库。驱动程序的选择不会影响客户的代码,因为JDBC API本身是不受驱动影响的。对于性能来说,Type 4通常是最好的,接下来是Type 2,再接下来是Type 1,最后是Type 3。

4)Java事务API和服务(JTA, JTS)

事务管理常常是由EJB容器来委派的,但有些情况还需要显式的事务管理。CORBA对象事务服务(OTS)或者它的Java实现(Java Transaction Service, JTS)可以用于这个目的。然而,EJB中有一个更为简单的接口,称为Java事务API。它是服务器/容器的实现使用的低级XA接口(X/Open事务API接口标准)和EJB Bean的实现可以访问的高级客户端接口。

在JTS(或OTS)中,需要显式地和仔细地使用事务中的资源,这样,这个显式的对事务的划分会形成一个边界。高级JTA(Java Transaction API)接口,这个容易出错的功能是由EJB容器来完成的。然而,由于资源由长事务所掌握,显式的事务管理仍然是很容易出错的,并且将引起不一致或效率低下。

J2EE连接器架构(JCA)

$\color{red}{\text{JCA标准化连接}}$ 是由J2EE 1.3首先提出的,它位于J2EE应用服务器和企业信息系统(Enterprise Information System, EIS)之间,比如数据库管理、企业资源规划(ERP)、企业资产管理(Enterprise Asset Management, EAM)和客户关系管理(CRM)系统。不是用Java开发的企业应用或者在J2EE框架内的应用都可以通过JCA连接。JCA是在javax.resource包和它的子包(cci, spi和spi.security)中定义的(JCA的形式也用于Javacryptography API的缩写)。

Java和XML

Sun是XML的一个早期提倡者。然而,最初Java对XML的支持只是限定在定义能够处理XML文档的接口,能够表示XML文档(Document Object Module, DOM)和XML流(Sinple API for XML, SAX)的模型。而更多对XML支持,包括对XML Schema和Web服务标准的支持已经作为预发布版本,在2002年初加了进来。

Java和Web服务——SunONE

SunONE(Sun开放网络环境)是J2EE的扩展,它可以通过特殊的servlet来处理Web服务协议。SunONE也包含了以前由iPlanet策划的J2EE服务器产品。(注意:Netscape公司和Sun公司组建的iPlanet联盟在2002年年初已经结束了,并把iPlanet开发的产品留给了Sun公司。在2002年年初,iPlanet在J2EE的市场份额中占7%,排名在它之前的是IBM公司的WebSphere和BEA公司的WebLogic(各占34%),紧随其后的是Oracle公司(占6%)。随着在2002年年初Java Web服务开发包(JavaWSDP)可用版的发布,Sun公司对SOAP、WSDL、UDDI都提供了支持。JavaWDSP包括了Java为XML消息处理(Java API for XML Messaging, JAXM)、XML处理(Java API for XML Processing, JAXP)、XML注册(JAXR)和基于XML的RPC(JAX-RPC)提供支持的API。另外它还包括JSP标准标签库(Java Server Pages Standard Tag Library, JSTL)、Ant创建工具、Java WSDP登记服务器、网络应用工具,以及Apache Tomcat网络服务器容器。

Microsoft的方式

从某种意义上讲,微软选择的是最简单的路线。它没有提出一整套标准,并期望依此改变自己的系统。相反,它不断地对已有的应用和平台基础进行再工程。构件技术是渐进引入的,这就可以获益于以前的成功技术,例如,Visual Basic控件(VBX,一种不是面向对象的构件!),对象链接和嵌入(OLE),OLE数据库连接(ODBC),ActiveX,微软事务服务器技术(MTS),以及主动式服务器端页面技术(ASP)。

在技术标准的领域里,微软的主要兴趣放在Internet标准(Internet Engineering Task Force, IETF)和Web标准(W3C)上。最近,它的.NET规范的一部分被ECMA(European Computer Manufacturers Association)组织采纳。ECMA是欧洲的一家标准化组织,它被视为通向ISO的捷径(参见ECMA, 2001a, 2001b)。微软并未试图让自己的方法与OMG或Java标准保持一致。尽管Java曾在微软的战略中扮演过一段重要角色,它目前的地位却已不高,而仅仅是为了继续支持一项较老的Visual J++产品。这其中有一部分是解决Sun公司和微软公司争讼所带来的后果。而且,微软还重点瞄准使用Visual J++6.0的用户,靠着Visual J#.NET的名称,推出一个向.NET迁移的工具。

作为.NET计划的一部分,微软正在推动语言无关性,把它作为CLR的一条主要原则,并构造了一种新的语言C#。C#吸纳了Java的很多成功特性,另一方面,它又新增若干独有的特征(例如值类型),且不支持某些关键的Java特征(例如内部类)。C#虽然定位于CLR的模型语言,但它与若干其他语言占有同等地位,包括被全面革新过的Visual Basic .NET, Managed C++(对C++的扩展,它与ANSI兼容),以及许多被其他供应商或组织支持的语言。

在依赖语境的组装方面,微软、OMG和Sun这些公司技术之间的螺旋演进很有意思。依赖环境的组装最先在COM套间模型被粗略描述,又在微软事务服务器(Microsoft Transaction Server, MTS)中被丰富,在被Enterprise JavaBean采纳和改进的同时它又在COM+技术中独立地发展,后来被CORBA构件模型(CCM)采纳和改善,最后,它变为CLR中的一项可扩展的和开放的机制,与此同时,EJB 2.0的发展超越了意欲成为各项技术超集的CCM,这意味着CCM规范进入了“维护阶段”。

COM可能在未来多年内仍很重要,而且,CLR与它的互操作能力格外强。鉴于此,下面对微软方法的讨论就以COM介绍开始。COM+在COM基础上新增了服务,CLR的首次发布是使用COM互操作来提供COM+服务的,因此,许多服务并非冗余。

第一个基础关联模型——COM

COM是微软平台上所有构件的基石,微软还将之在Macintosh系统实现。在其他的许多平台,COM也被诸如Software AG、惠普这样的第三方厂商实现。即便如此,可以说,COM从未在微软的Windows平台之外赢得更多支持。但是,COM的基本理念却有着相当的影响力。

COM所定义的一个基础实体是接口。在二进制层面上,一个接口被表示为指向一个接口结点的指针。而一个接口结点唯一被指定的部分是置于其内部第一个域的另一个指针,这个指针指向一个过程变量表(或者说,函数指针表)。这些表源自C++等语言用来实现虚函数(方法)的表,因此,也被称做vtable。图10-6所示为二进制层面的一个COM接口。

图10-6 COM接口的二进制表示找不到图片(Image not found)

表示COM对象的通常方法是将其画成带有插口的盒子。由于每个COM对象都有IUnknown接口(它标志着COM对象),通常把IUnknown接口置于COM对象图的顶端。图10-7所示为COM对象图,这里是一个ActiveX文档对象。

图10-7 COM对象描述找不到图片(Image not found)

回到IUnknown接口。它的“真实”名字当然是它的IID,即00000000-0000-0000-C000-000000000046。但为了方便,所有接口也有一个可读名。根据习惯,可读接口名以字母I开头。与IID不同,可读名并不保证是唯一的。因此,编程中的接口引用均使用IID。

IUnknown接口的首要用途是在最抽象的情况下标志COM对象,此时COM对象没有任何特殊功能。因此,IUnknown接口的引用可被用来和ANY类型的引用或面向对象语言的Object引用比较。从某种意义上,IUnknown用词不当。它并不是一个未知的接口,相反,它是唯一能被保证永远存在的接口。对于一个没有别的已知接口的COM对象,它被获知的唯一途径就是通过IUnknown接口的引用。

IUnknown接口只提供对任何COM接口都必须的三个强制性方法。第一个强制性方法是QueryInterface,前面已提及。另两个强制性方法名为AddRef和Release。结合关于何时调用的规则,这两个强制性方法被用来控制对象的生命周期。后面会有更多解释。使用类似COM IDL的表示法,IUnknown可被定义为:

图片详情找不到图片(Image not found)

类型HRESULT被大多数COM接口的方法用来表示调用的成功或失败。QueryInterface则用它表明查询的接口是否被支持。如果接口属于一个远程对象,HRESULT也可能表示网络错误。

每个COM对象都会进行引用计数,或者是对整个对象体,或者是对每个单独的接口结点。引用计数变量被共享使用的情况下,COM对象不能释放接口结点,即使这个结点已经没有引用。一般来说,这样做没有问题。而共享一个引用计数变量也是通常的做法。可是,某些情况下,接口结点会占据很多资源,例如当它们保留着一个大缓冲结构时。对于这类接口结点,可以使用独立的引用计数变量,以便结点可以尽早释放。这种根据需要创建和删除接口结点的技术有时被称做“快速装卸接口(Tear-Off Interface)”。

当对象或结点被创建,其第一个引用被传出之前,引用计数变量会初始化为1。之后,每逢一个引用复制被创建,计数值必须增加(AddRef);每逢一个引用被丢弃,计数值必须减少(Release)。在引用值变成0的瞬间COM对象无法被访问,因此它就该自行销毁。销毁工作的一部分是通过调用Release方法释放对其他对象的引用。其后果是,被正待销毁对象引用的所有对象都会被递归销毁。最终,被销毁的对象释放它所占有的内存空间。

COM对象重用

COM不支持任何形式的实现继承。注意,COM没有定义或考虑单独的构件从内部如何去实现。构件可以由使用了实现继承的类组成。无论何种情况,缺少实现继承并不意味着缺少对重用的支持。为实现对象重用,COM支持两种形式的对象组装:包含(Containment)和聚集(Aggregation)。

包含就是一种简单的对象组装技术,其含义是一个对象拥有指向另一个对象的唯一引用。从概念上来说,前者(称做外部对象)“包含”后者(称做内部对象)。外部对象只是把请求转发给内部对象。所谓转发,就是调用内部对象的方法,以实现对某个外部对象方法的调用。

包含能重用内含于其他构件的实现。特别是,对于使用外部对象的客户程序,包含是完全透明的。调用接口函数的客户无法辨别调用是由提供接口的对象处理,还是被转发给另一个对象处理。如果包含层次较深,或者被转发的方法本身相对简单,包含会存在性能上的问题,因此COM定义第二类重用形式,即聚集。聚集的基本思想很简单,直接把内部对象的接口引用传给外部对象的客户,而不再转发请求。对此接口的调用将直接到达内部对象,从而省去转发的代价。当然,只有在外部对象不希望截取调用以执行诸如过滤等额外处理时聚集。还有,保持透明性是很重要的,因为外部对象的客户无法辨别哪个特定接口是从内部对象聚集而来的。

接口和多态

COM接口可通过(单)接口继承从其他COM接口中派生。实际上,所有COM接口都直接或间接地继承了Iunknown,它是接口体系中的公共基类型。除了Iunknown外,只有Idispatch和Ipersist这两种重要的基接口被公共继承。COM中接口继承为什么如此鲜为使用呢?

令人吃惊的是,COM的接口继承与其支持的多态无关。例如,假定客户持有一个接口,比方说IDispatch的引用。实际上,客户引用的接口可以是IDispatch的任何子类型。换句话说,函数表可以包含IDispatch所需之外的方法。但重要的是,客户无法发现这一点。如果客户想要更特殊的接口,必须使用QueryInterface。这样就能保证获得更多的方法,至于返回的接口结点实际上是否就是QueryInterface被调用发出的那个结点,对客户来说没有关系。

接口和版本化。一旦公布,COM接口和它的规范不允许以任何形式改变。这种避免的方法既解决了语法问题,也解决了语义上的脆弱基类的问题。换言之,COM中的IID可用于标志接口中的版本。因为接口总是通过IID被请求的,系统中的所有参与者都对接口的版本达成一致。CORBA讨论中所提及的传递性版本冲突问题在COM中不会发生。

构件可以选择实现接口的多个版本,只不过处理方式就像处理任何别的不同接口一样。使用这种策略,基于COM的系统能并发支持旧接口和新接口,同时允许渐进的迁移。在某些系统中,由单个对象实现的多个接口被合并成单个类的命名空间,类似上述的策略实现起来就变得困难,或至少不自然。对于建立在传统对象模型(像Java或CORBA)之上的方法,这会给二进制兼容性带来问题。CLR避免此问题的方法是,在相同的类实现的不同接口上,允许分别实现具有相同名字和签名的方法。除此之外的其他方面,CLR还是基于传统对象模型。

COM对象的创建和COM库

创建对象的最简单方法是调用CoCreateInstance(所有COM库的过程名以Co起头,它代表COM)。此函数需要一个CLSID和一个IID,然后创建指定类(CLSID)的新实例,并返回所请求类型(IID)的接口。如果COM无法定位或启动能实现所请求CLSID的服务器,或者指定的类不支持所请求的接口,就会返回错误提示。

创建COM类的实例对象时,COM需要把给定的CLSID映射为包含所请求类的实际构件。为此目的,COM支持系统注册器,它类似CORBA存储器。注册器指明哪些服务器是可用的,它们支持哪些类。服务器可以是进程内(inprocess)服务器、本地服务器和远程服务器这三种类型中的一种。进程内服务器支持存在于客户进程中的对象;本地服务器支持的对象位于客户所在的机器上,但在不同的进程内;远程服务器支持的对象位于不同的机器上。

CoCreateInstance接受一个额外的参量,用于指定何种服务器是可接受的。CoCreateInstance查询注册器以定位服务器。若服务器尚未被激活,就载入并启动它。对于进程内服务器,需要载入和链接动态链接库(DLL)。而对于本地服务器,独立的可执行文件会被载入。最后,对于远程机器,会联系远程机器上的服务控制管理器,以载入并启动该机器上的服务器(以中间件观点看,SCM起着类似CORBA ORB的作用)。

COM服务器具有定义好的结构,包含一个或多个类,对每个类它又实现一个工厂对象(在COM里,工厂对象被称做类工厂。这个名称可能让人误解,因为工厂创建的不是类,而是类的实例)。工厂是支持IClassFactory或IClassFactory2接口的对象,使用后一个接口意味着需要许可机制。COM使用工厂的原因是,COM对象不一定是简单的单体对象(single-object),因此其创建需要由其构件而非系统提供的服务来指定。

图10-8含两个coclasses的COM服务器,每个都有一个工厂。启动时,自注册服务器为每个类创建一个工厂对象,并将之注册到COM。CoCreateInstance使用工厂对象创建实例。为了提升性能,客户也可以使用CoGetClassObject获得对工厂的直接访问。在需要创建许多新对象时,这种做法较有用。很多时候,客户所要的不是具体的类,而是更一般的东西。例如,客户并不使用对应Microsoft Word的CLSID,而是使用对应rich text的CLSID。为了支持这种一般性CLSID和相应的配置,COM允许一个类仿真另一个类。仿真配置保存在系统注册器里。例如,某个仿真项也许会指定类Microsoft Word仿真类rich text。

从COM到分布式COM(DCOM)

COM透明地扩展COM的概念和服务。DCOM中已存在客户端代理(Proxy)对象和服务器端桩(Stub)对象,它们只被用于支持进程间通信。DCOM建立在这两者的基础上,在前面谈到远程服务器时已暗示过DCOM服务。

为支持跨进程或跨机器的透明通信,COM在客户端创建代理对象,在服务器端创建桩对象。为了单个机器内进程间的通信,代理和桩需要实现的,仅仅是从简单数据类型到字节流和从字节流到简单数据类型的映射。因为发送和接收进程在同样的机器上执行,所以不需要担心数据类型是如何表达的。而当接口引用被传递时,尽管仍在相同机器的不同进程间,情况也会变得稍微复杂些。

图10-8 一个COM服务器支持两个各带工厂的COM类找不到图片(Image not found)

跨进程传递的接口引用需要被映射为对象引用,它的意义在穿过进程时仍能维持不变。当接到对象引用时,COM需要确定对应的代理对象存在于接收端。然后,COM选择该代理的对应接口,并传送这个接口引用而非先前的那个接口引用。先前的引用会指向“错误”进程的接口。

图10-9显示了客户向对象A发出一个调用。被调用的方法只有一个参量,它引用对象B的一个接口。由于对象A位于另一进程,本地代理对象中转此调用。代理决定对象B的对象标志符(OID)和被传递接口的接口指针标志符(IPID)。OID和IPID一起随着客户进程ID被传递给服务器进程的桩。桩使用OID定位对象B的本地代理,使用IPID定位具体的接口。接着,桩代表客户发出先前的调用,它将本地B代理的接口引用传给调用接受者——对象A。

图10-9 单机上进程间接口引用的编排与起源找不到图片(Image not found)

DCOM所用的方法相当近似。有两点不同,在不同机器上数据类型的表达可能是不一样的,并且对象引用需要包含比OID和IPID更多的信息。为了解决数据表达的差异,DCOM将数据整理成平台无关的网络数据表达(NDR)形式。为了形成与机器无关的对象引用,DCOM将OID、IPID和那些足以定位对象输出器(Object Exporter)的信息结合在一起。对象输出器是DCOM提供的对象,它知道如何绑定服务器公布的对象。每个对象输出器有唯一的标志符(OXID),它被包括在对象引用中。

复合文档和OLE对象

链接和嵌入是微软的复合文档标准。创造OLE的原本意图是为了将各以应用为中心的遗留程序融合成单一的以文档为中心的范型。也有可能创建只存在于OLE环境中的对象,ActiveX就是最好的例子。然而,OLE也继续用变化的OLE集成程序来支持独立的应用。这种实用化的一面使很多OLE技术非常复杂。然而,可以按平滑的路径进行技术迁移,以保护在开发和用户培训方面的投资,这样就可以保住客户。

如同COM之上的每项技术,OLE可被概括为一组预定义的COM接口。OLE所需的几项关键技术由COM服务提供。这包括结构化存储、绰号、包含拖放的统一数据传输、可连接对象和自动化支持。

OLE复合文档的方法对文档容器和文档服务器进行区分。文档服务器提供某种内容模型和显示、操作内容的能力。文档容器没有自己的内容,但可以接受任意文档服务器提供的内容成分。许多文档容器也是文档服务器,这即是说,它们支持外来的成分,同时也有自己的内容。大多数流行的“重家伙”,像微软的Office应用(如Word、Excel和PowerPoint等),是结合为一体的服务器和容器。例如,Excel有自己的内容模型,它是按照电子数据表排列的数据和公式单元。Excel也是容器,作为容器,它能接受所插入的Word文本对象。

.NET框架

.NET框架是更大的.NET空间的一部分。它包含的内容有通用语言运行环境,许多部分接口化和基于类的框架(被打包成配件),以及许多工具。CLR是通用语言基础设施规范的实现,它增加了COM+互操作和Windows平台访问服务。特别地,CLR提供了动态载入和卸载、垃圾回收、语境截取、元数据自省、远程化、持久性,以及其他完全和语言无关的运行时刻服务。目前,微软在CLR上支持4种语言:C#、JScript、ManagedC++和Visual Basic .NET。

配件(Assemblies)是.NET中部署、版本控制和管理的单元,也就是说,它们是.NET的软件构件。“并排”使用同一配件的多个版本是完全可以的。配件包含元数据、模块和资源,所有这些以平台无关的方式被表达。模块中的代码以CIL(通用中介语言)表达,CIL大致像Java、Smalltalk的字节码,或者Pascal P码。与早期字节码格式不同,配件中使用的语言不重视解释。MSIL(微软中介语言)是与CLI兼容的超集,它带有支持CLR互操作特性的指令,这些特性就在CLI规范之外。CLR在安装或者载入时被编译,执行的始终是本地码。CLR自省和其他基于类型的概念覆盖了很大的类型系统空间,此空间被称做CTS(通用类型系统)。

下面涵盖了各种与.NET框架相关的技术细节。

.NET大图景

微软公司的.NET计划的目标是,将范围广泛的微软产品和服务组织起来,置于各种互联设备共同的视野范围内,这些设备包括服务器、固定和移动PC及特殊设备。在技术层次,.NET瞄准如下三个层面。

(1)Web服务。

(2)部署平台(服务器和客户机)。

(3)开发平台。

Web服务想达到因特网的传递式可编程性(这就不仅仅是传统意义上瞄准人类客户的Web了,它应包括支持Web服务的构造、定位和使用的因特网和Web的标准和建议)。为启动Web服务空间,微软公司计划推出许多基础的核心服务。第一个这样的服务已推出一段时间,它就是用于验证用户的.NET护照。另一个是.NET警报,它在2002年早期被应用。它是通用警报服务,在引入时是通过Windows信使发送警报的。作为.NET MyServices和其他计划的一部分,微软公司公布了更多的服务,例如用于存储的服务。从各式服务器产品和Windows.NET服务器开始,微软平台正在经过一系列步骤被转变,以便以本地和有效的方式支持、使用Web服务和处理XML。

最后,也是本章的焦点,会有新的开发平台,它包含CLR、框架和工具。CLR提供了新的构件基础设施,可以(但不是必须)为构件屏蔽底层硬件平台的细节。类似JVM,CLR定义了一套脱离具体处理器的指令集。与JVM不同的是,CLR还支持需要和特定底层平台紧密集成的构件。

通用语言基础设施

通用语言基础设施规范由微软公司、英特尔公司和惠普公司联合提交给ECMA,它建立了类似CORBA的语言中性平台。可是与CORBA不同,CLI也定义了中介语言(Intermediate Language, IL)和部署文件格式(配件),例如Java字节码、类和JAR文件。与CORBA和Java不同,CLI支持可扩展元数据。通用语言运行环境是微软.NET框架的一部分,它是微软公司对CLI规范的实现。CLR超出了CLI兼容的范围,它包括对COM和平台互操作的支持(细节参见下个小节)。CLI包括了执行引擎服务的规范(例如载入器、JIT编译器、起垃圾回收作用的内存管理器)、通用类型系统(Common Type System, CTS)和通用语言规范(Common Language Specification, CLS)。CTS和CLS起着互补的作用,CTS范畴是许多语言在类型空间的核心概念的超集。与CLI兼容的代码能够在整个CTS空间运行。可是,没有哪两种语言能精确覆盖相同的CTS子集。以不同语言实现的代码要互操作,CLS空间就显得有用。CLS是CTS的严格子集,它被构建的方式使许多语言都完全覆盖它。特别地,若某个定义是与CLS兼容的,那么任何被归为CLS消费者的语言均能使用该定义,这是CLI目标语言中最简单而有用的一类。能在CLS空间引入新定义的语言称做CLS生产者,能扩展CLS空间已有定义的语言称做CLS扩展者。CLS扩展者也总是CLS生产者,CLS生产者也总是CLS消费者。CTS为所有类型定义了单根类型——System.Object。Object之下,CTS区分了值类型和引用类型。所有值类型是System.ValueType的单态子类型,它本身又是System.Object子类型。引用类型被分成接口、类、数组和代理(技术上,接口被建模为CTS中特殊的类),其中的类被分成按值排置(Marshal-by-value)和按引用排置(Marshal-by-reference)两种。按引用排置又被分成随环境变化和与环境绑定两种。从图10-10可看到CTS类型体系的概况。

图10-10 顶层CTS类型层次找不到图片(Image not found)

原始类型是没有的,因此,诸如整型或浮点等类型只是预定义的值类型。多个接口和单个类继承关系是被支持的,甚至值类型也能继承(实现)多个接口。访问权限从两个方面被控制,即定义点和使用点是否位置相同,以及定义点和使用点是否通过类继承而相关。为了前者,区分了三种位置范畴:类、配件和全局。因此,访问权限关系有6种可能的约束组合,但大多数语言支持的只是其子集。例如,C#不支持把protected访问权限定义在小于全局的范畴上。某些语言,像Managed C++,则支持所有组合。

方法可以是静态的、与实例绑定的或者虚拟的(虚拟也暗示着是与实例绑定)。对重载的支持要依靠方法名、签名,但没有返回类型。重载的解析策略依语言而定(因此,CLI自省机制引入了自己的重载解析策略)。

类能实现多个接口,并可以用引入的接口名修饰方法名。因此,能够在相同的类上实现两个接口,即使它们包含具有相同名字和特征的方法,但这两个方法应以不同的方式实现。例如,C#完全支持显式地实现接口方法的概念。

图片详情找不到图片(Image not found)

尽管上面的cowboy/shape例子被广为使用,但实际上它没有包含一个重要情况:偶然的名字冲突会发生这个问题。然而,重要得多的是这样一个例子:发布新的接口版本时还希望能并排支持多个构件。图10-11显示了类C应该如何实现版本1的接口I和版本2的接口I,以便和类A和B正确交互,此二者都需要接口I,只是版本不同。CTS把所有的名字定义锚定在包含它们的配件的名字里。因为配件的名字包含了版本信息,接口I的两个版本实际上可以被区分开,不过它们的方法名还有可能冲突。CTS支持在同样的类上实现接口的两个版本,这是支持并排使用配件多个版本的重要步骤。

图10-11 一个接口不同版本的并行实现找不到图片(Image not found)

各种方法命名规范,例如属性和索引器的取值—设值方法,也是CTS的一部分。定义这些规范的目的是支持跨语言互操作,而没有考虑对各种语言中传统命名的特性的显式支持。例如,对于C#属性Foo,对应的访问方法被称做get_Foo和set_Foo。C#不允许直接使用这些方法名,但其他不直接支持属性的语言就可以调用这些方法来访问属性。

可抛出异常不是CTS方法特征的一部分。与Java和C++不同,CTS没有规定调用方法时要静态检查可抛出的异常。语言仍可以在它们自己领域内自由执行这类检查。没有哪两种语言对声明和检查的语义能达成完全一致,这使得跨语言检查没什么用处,即便CTS曾支持注释。

COM和平台的互操作

CLR能真正支持与COM的互操作,并能直接访问底层平台(即Win32和其他基于DLL的API)。通过整合对COM互操作和平台调用的支持,CLR执行引擎能提供几乎最优的性能。例如,平台调用通过JIT被编译为本地代码序列,它实际上和传统编译的代码是等同的。COM互操作是通过提供两类自动合成的包装达到的。一种是COM可调用包装,它通过COM接口来呈现CLR对象;另一种是运行环境可调用包装,它通过CLR接口呈现COM对象。

为了和COM互操作,CLR工具可被用来创建互操作配件,这些配件定义的类型和COM类型库定义的类型相匹配。被多个.NET应用共享的CLR配件必须有唯一的强名字,这对COM互操作有微妙的影响。正是这点使多方可能为相同的COM接口(相同的IID)产生互操作配件。然而,得到的互操作配件会公布相互不兼容的类型,尽管所有这些类型对应着有相同IID的相同COM接口。为避免此种情况,定义了主互操作配件(Primary Interop Assemblies, PIA)。PIA应由COM接口(IID“所有者”)的发布者产生。如果得不到PIA,可以产生替代的互操作配件,只是它的类型只能在配件内部使用。把这些类型公布在新的配件特征中,最终会导致与其他配件的不兼容,它们或者依赖PIA,或者将自己的依赖公布于另一个替代的互操作配件。

尽管COM表面简单,但是COM互操作却是微妙而复杂的。原因是,COM调用规范的细节(包括分派接口),排置规范(包括由谁分配或释放的规则),以及对底层不安全类型的支持(包括指向大小未知的数组的指针)。远程化接口(DCOM代理能自动生成的接口)更容易处理,因为代理需要的是和CLR包装所需大致相同的信息。不幸的是,DCOM给自己带来了麻烦——类似[call_as()]属性的IDL注释只有过程意义,不能被自动解释,以产生合适的CLR包装。

奇怪的是,若接口被限制为“同构类型”(此类型在穿过CLR/COM边界时无需变换),调用代码中的COM方法时,其开销大约只有50个指令周期。

战略比较

迄今为止,讨论过的每个方法都已给出了丰富的技术细节,那么它们有什么显著的区别和基本的共同点呢?战略上的后果又是什么呢?

共性

很明显,所讨论过方法的共性不能帮助决定应该遵循哪一种方法。然而不管具体的方法是什么,这些方法的共性有助于我们做出使用构件化软件技术的决定。对这些共性的理解还可以避免一些没有意义的争论,这些争论的产生是因为把一些次要问题简单地误解为主要的不同点了。

所有的方法都依赖于延后绑定机制、封装和动态多态性(也被称为包含多态性或者子类型化)。除了COM之外,其他方法都支持接口继承(COM中的多态性来源于接口及类的分离及每个类的多接口支持)。换一个说法就是,所有的方法都依赖于某种类型的对象模型。

另外,随着时间的推移,这些方法互相取长补短。大多数的方法目前支持:

(1)一种构件传输格式——JavaJAR文件、COMcab文件、CCM:-、CLI配件。

(2)统一方式的数据传输。

(3)事件和事件连接或者信道、单播和多播。

(4)元信息——自省、反射。

(5)某种形式的持久化、序列化或者外部化。

(6)基于属性的编程或部署描述符。

(7)适合于应用服务器的特定构件模型——EJB、COM+、CCM和CLR:COM+。

(8)适合于Web服务器的特定构件模型——JSP/servlets、COM:-、CCM:-和ASP.NET。

一个通常被忽视的事实是,非COM方法慢慢地汇聚到同样支持COM方法已经拥有的功能上来,这个功能就是构件对象通过多个截然不同的对象将自身展现给客户的构件对象。这样做使得动态配置成为可能,这已经得到了认可。CORBA构件模型的等价接口的概念几乎等同于COM的QueryInterface方法。JavaBean引入了间接的库java.beans.Beans,来代替Java语言的类型测试(instanceof)和类型检查(检查转换)。通过这种方法,将来的bean可以将它们自己作为一组Java对象表示给客户,而不是一个单独的对象。有趣的是,CLI/CLR在第一个版本中没有追随COM,不提供对处理拥有多个实现体的实例的支持和转换,尽管提供了通用的设计模式,使用C#的属性从主对象中获得子对象。

不同点

一旦做出使用软件构件的决定,下一步就要选择使用哪一种方法。鉴于很多方法具有较多的共性,可以同时遵循几种不同的方法。特别地,支持者们和第三方或许会提供主要方法之间的互连的解决方案。这里有若干例子。IONA公司的Orbix 2000 COMet是一个CORBA/COM集成工具。Sun公司的ActiveX bridge允许JavaBean实例被嵌入到ActiveX容器中。IONA公司的技术总监Annrai说:“我们的座右铭是,不兼容就意味着商业机会,对于我们而言是巨大的机遇。”

以下是一个这些方法间明显区别的(不全面的)列表。

每个平台的二进制接口标准

构件交互的二进制标准是COM的核心(值得注意的是,虽然从技术上是可行的,也曾经做过尝试,但是COM从来没有脱离过Windows领域。从而,有一个平台就有一个二进制标准)。Java通过标准化字节码来避免实际的二进制标准。Java为二进制接口定义了Java本地接口(JNI),JNI的设计是基于COM的,不过是特定于Java。特别的是,其设计为支持现代的垃圾搜集器提供了空间。CORBA仍然没有定义二进制标准。二进制标准是Direct-to-*编译器需要的,这些编译器将一种特定编程语言的语言构造直接映射到二进制接口。CLR(CLI的超集)和Java类似,采用标准化MSIL(CIL的超集)来代替二进制标准。CLR支持了领先时代的编译,提供了对平台API调用进行转换的有效支持和与COM的互操作。

兼容性和可移植性的源代码级的标准

CORBA在标准化语言绑定方面做得相当好,语言绑定保证了跨ORB实现的源代码的兼容性。大量的标准化的服务接口增强了它在这方面的地位。目前在对象服务器上对ORB特定的功能进行存取的实践,降低了基于CORBA服务器的可移植性。对于Java,对Java语言规范达成一致,即只要没有其他语言在Java平台上被使用,就解决了这个问题。于是语言绑定的标准化成为一个问题,不然由多种源语言产生的字节码之间的互操作将受到危害。Java包含了越来越多的(事实上是Sun的)标准,特别是其中的J2EE标准受到很多厂商的追随并提供了实现。COM没有任何源代码级标准或标准语言绑定的概念。COM接口市场的标准也没有超出微软事实上的标准。.NET CLR提供了通用语言规范(Common Language Specification, CLS)来指导语言绑定,它在没有规定单个语言绑定的情况下达到了很高程度的互操作性。位于CLR之下的公共语言基础设施(Common Language Infrastructure, CLI)规范,以及一组基框架和C#语言,都已经由ECMA进行了标准化

逐渐形成的和仓促造就的标准

在被制定成“标准”之前,COM、CORBA、Java和CLI标准(按照这个次序)经历了越来越短的演化期。COM(具有OLE1的)与CORBA(1.2)已经经过了不少实质性的修订,已经没有了向后的兼容性。COM/OLE/ActiveX有许多冗余的机制——例如外出接口和可连接对象(又被称为变化通知接口,也被称为advice接口)与分派接口(又叫做verb接口,在ActiveX出现以后,还被称为command target接口)。不同演化期长度的一个结果就是这些方法在市场上产品数量的不同。市场上有成千上万的ActiveX对象,而只有很少的Bean。然而,EJB构件已经在工业界获得了实质性的支持,虽然目前大多数的EJB构件只是在内部被开发和使用。对于CLI/CLR标准的构件,报告其市场的接受程度还为时过早。

内存管理、生命周期和垃圾回收

目前CORBA尚未提供解决分布式对象系统中的全局内存管理问题的一般方法。COM和DCOM完全依赖于引用计数——这在所有构件都遵循特定规则的情况下是可行的,但是在大的开放的分布式系统中存在伸缩问题。Java完全依赖于垃圾回收机制,JDK1.1以后通过使用Java RMI也定义了分布式对象模型并支持分布式垃圾回收,这个概念基于“租期”——预先指定远程引用的生命周期。CLR也采用垃圾回收机制并融入基于“租期”的对远程引用的生命周期控制。另外,CLR支持其他的通信和列集协议,诸如HTTP之上的SOAP。

容器管理的持久性和关系

EJB创新性地引入了容器管理的持久性技术,并且从EJB2.0开始,还引入了容器管理的关系。CCM也有类似的技术,因为它可以算是EJB的超集。迄今为止,COM+和CLR都尚未提供这样的支持。这些机制仍然需要进行改进,例如一个J2EE服务器中过度热心地装载一个关系中的所有实体,导致了很多应用程序的性能低下。OLEDB(及COM+和CLR)支持可插拔的持久性映射,允许将数据保存到除数据库外的其他多种外部存储的持久性。2.0版本的EJB不包括对可插拔映射的支持,使得纯数据库应用程序外的容器管理的持久性和关系功能很弱。同样地,当映射需要复杂的连结或存储过程时,EJB 2.0的局限性也很大。

演化和版本的概念

COM坚持一旦接口和它们的接口ID被公布之后,就必须冻结接口和接口的规约。这样可以解决版本和移植问题,但在某些特定的部署场所使用受控的版本兼容策略时会暴露出一些问题。CORBA没有直接处理这个问题,而是选择支持主、次版本号这样一个较弱的概念。CORBA的解决方案是有问题的,因为它允许某个版本的对象的引用被传递给另一个希望接收不同版本对象引用的对象——版本检查只在对象创建的时候进行。Java只在二进制兼容性级别上考虑版本,为此给出了令人头痛的规则列表。有些规则的实际意义可能不大。例如,一个版本中某个常量的值在另一个版本中被改变,这对于以前编译过的客户程序不会有影响,它们只需简单地保持以前的值就可以了。虽然客户程序使用了不同版本,但是原有的客户程序仍然是可用的(尽管可能会出现一些功能失常),而不用声称该客户程序被破坏。构件Pascal的实现使用指纹标注每个接口的算法来保持小粒度上的兼容性(Crelier, 1994)。CLI拥有最完整的版本控制方法。CLI构件,被称为配件,都标记它们自己的版本信息,以及它们依赖的构件集合的所有版本信息。策略可以用来建立匹配版本的可容忍的范围。通过支持并行运行来允许一个构件的多个版本同时存在,这使得滑动窗口方式的移植成为可能——不是每个构件都需要立刻更新到一个新的版本。然而,最初的.NET框架和面向CLR的语言都没有利用CLI版本支持的全部优势。

分类的概念

COM中的分类通常被忽视,因为这个概念比较新,并且看起来没有什么坏处,不过实际上,它引入了合约绑定到包含任意多个接口的规约这样的概念。一个构件可以属于任意数目的分类,一个框架或者其他构件可以使用分类成员资格作为高层的断言。Java和CORBA没有任何类似的概念,虽然Java中的空标记接口按照类似的目的被使用。CLI提供定制属性来扩展构件的元数据,因此分类和其他元信息可以使用定制属性来获取。

产业界的实现支持及应用状况

这里,所有的方法有它们自己的领地。COM在客户机/桌面系统方面最强。J2EE和COM+则在基于非PC和基于PC的服务器的解决方案中占主导地位。Web服务器主要使用JSP或者ASP(现在还有ASP.NET)。CORBA在商业计算层次上对传统的遗留系统的集成是最强的。COM和CLR很大程度被限制在微软所提供的实现上。很多厂商提供了CORBA和J2EE的实现。从一个J2EE服务器移植到另一个J2EE服务器并不是一件容易的事情,但是当然要比J2EE和.NET间的移植简单很多。

开发环境

支持COM的开发环境相当多。Java的开发环境也比较多。支持CORBA的开发环境非常少,几乎没有。对于CLR——微软对CLI的实现,其开发环境是与之一起发布的Visual Studio.NET,该环境包括对Visual Basic、JScript、C#和Managed C++的支持。

服务

CORBA目前拥有全套的标准化服务,不过其中的大部分缺少商业实现。COM+用一组丰富的关键服务对COM进行了补充,其中包括事务和消息。包括EJB的J2EE也拥有相对丰富的服务。CLR用COM+提供高度的互操作性支持,包括所有的COM+服务(现在被称为企业服务)。然而,这些服务没有被CLI规范所涵盖。在未来,一些COM+服务可能发展成为真正的基于CLR的服务。在CORBA和COM+中提供了对分布式事务协作的支持(对于CLR也是同样的),不过并不包括在EJB 2.0标准的范围中。J2EE服务器的支持则会相应的不同。

部署

J2EE、COM+、CCM和CLR全都遵循基于属性编程的MTS概念。EJB将属性分离出来并将它们放置在单独的基于XML的部署描述符中,使得在特定的部署步骤中可以拥有清晰的操作对象。J2EE将部署描述符的概念扩大到若干个构件模型。CLR将基于XML的配置和基于CLI的定制属性组合到一起。定制属性简化了代码的排列,作为属性的元数据被直接存放在相应的源代码中。这样划分了开发者(放置定制属性)和部署者(处理配置文件)两者的任务。

网络服务构件

CORBA和COM在这方面没有特定的构件模型。J2EE有JSP和servlet构件。.NET框架有ASP.NET的页面构件类。JSP在某些方面遵循以前的ASP模型,但是在JSP页面与servlet方面有所改进。ASP.NET在某些方面遵循JSP模型,不过使用目标独立的方式,从而作为取代提供生成HTML的构件的方式,ASP.NET鼓励使用已有的生成界面显示的构件。因此,很多ASP.NET构件连一行HTML也不直接生成,使得它们独立于特定目标设备的要求,例如为移动设备生成WML显示。

传输

CORBA支持IIOP,用来作为ORB之间互操作的标准的传输协议。另外,OMG采用XML和XML Schema规范作为应用程序层的传输格式描述。Java支持IIOP绑定,不过也自然支持它自己的RMI协议。Java对XML的支持正在改进中。COM使用DCOM作为自身的传输协议,COM+增加了对多种消息格式的支持。CLR延续了COM和COM+所支持的所有格式,并添加了对XML schema定义和SOAP调用协议的支持。