如何获得更多有关LDAP的RFC 以及更多信息

小小豆叮

XML和数据库

作者:Ronald

摘要:本论文简要的探讨了XML和数据库之间的关系,同时列出一些可以使用数据库处理XML文档的软件。虽然这里不打算详尽地介绍这些软件,但是笔者希望它能够描述使用数据库处理XML文档中的主要部分。

内容:

目录

1.0 简介
2.0 XML是数据库吗?
3.0 为什么使用数据库?
4.0 数据和文档的对比
    4.1 以数据为中心的文件
    4.2 以文档为中心的文件
    4.3 数据、文档和数据库
5.0 存储和检索数据
    5.1 转移数据
    5.2 将文档结构映射为数据库结构
       5.2.1 模板驱动的映射
       5.2.2 模型驱动的映射
          5.2.2.1 表格模型
          5.2.2.2 特定数据对象模型
    5.3 数据类型、空值、字符集和其他
       5.3.1 数据类型
       5.3.2 二进制数据
       5.3.3 空值
       5.3.4 字符集
       5.3.5 处理指令
       5.3.6 存储标记
    5.4 从数据库的结构生成DTD及其互逆过程

1.0 简介

本论文简要的探讨了XML和数据库之间的关系,同时列出一些可以使用数据库处理XML文档的软件。虽然这里不打算详尽地介绍这些软件,但是笔者希望它能够描述使用数据库处理XML文档中的主要部分。这里有点偏向与关系数据库,因为我的经验如此。

2.0 XML是数据库吗?

在开始讨论XML和数据库之前,我们需要回答一个萦绕在很多心头的问题: "XML是数据库吗?"在严格意义上将,如果"XML"是指XML文档时,答案是"否"。尽管XML文档包含了数据,但是如果没有其他的软件来处理这些数据,它对于数据库的意义和其他文本文件没有什么区别。

如果在更为宽泛一些的意义上将,当"XML是指XML文档以及所有相关的XML的工具和技术时,答案则是"是"。 之所以肯定是由于XML提供了许多数据库中所需要的部分:存储(XML文档),结构(DTD,

XML schema语言),查询语言(XQL, XML-QL, QUILT等), 编程接口(SAX, DOM),等等。不过...XML还缺少很多在真实的数据库中所必备的内容: 有效的存储、索引、安全、交易、数据完备性、多用户访问、触发、多文档查询等。

因此如果在数据量一般、用户较少、性能要求不高的环境下可以把XML当作数据库来使用;而在大多产品的环境中,要求有许多的用户使用、需要严格的数据完整性并且对性能有很高的要求,XML就不能胜任了。而且,考虑到象dBase和Access等数据库既便宜又十分易用,因此甚至在第一种情况下XML都很少有理由充当数据库的角色。

3.0 为什么使用数据库?

当在考虑使用XML和数据库时,第一个要问自己的问题应该是:为什么我需要使用数据库?你需要将原有的数据导出?你需要保存你的Web主页?你是要在一个电子商务应用中使用数据库,而且其中XML当做传输的数据格式?这些问题的答案都将直接影响到你对数据库和中间件(如果有的话)的选择。

例如,假设你在电子商务应用程序中使用XML来进行数据传输。这是很好的方案,因为你的数据具有高度规范的结构,而XML中的那些实体和编码对你而言并不重要了。毕竟样你关心的仅仅是数据而不在于这些数据如何在文档中进行物理的存储。如果你的应用程序相对比较简单的话,关系数据库和数据传输中间件将可以满足你的需求;如果应用程序庞大而且复杂,那么你就需要一个完全支持XML的开发环境了。

从另一方面来说,假设你有一个从零散的XML文件创建的网站。你不仅需要管理这个网站,你还要提供方法让用户可以查询其中的内容。这时你的文件将非常的不规范,而实体的使用对你来说将变得很重要,因为这些文件的结构是网站的根本。在这个例子中,你就需要某类"原生

XML"数据库可以执行版本化、跟踪实体的使用并且支持如XQL这样的查询语言。

4.0 数据和文档的对比

笔者认为,在选择数据库时,最重要的判断因素可能是你是利用数据库来保存数据还是保存文档。如果你想保存数据,你需要的数据库主要是面向数据存储(例如关系型数据库或者面向对象型数据库)以及在数据库和XML文档之间相互转换。从另一个角度来将,如果你想存储文档,你需要一个专门设计用来存储文件的内容管理系统。

虽然你可以自己把文件保存在关系数据库或面向对象数据库中,可是你常会发现你的工作是在重复内容管理系统的功能。类似的,虽然一个内容管理系统通常是建立在面向对象数据库或关系数据库之上,但要是把一个内容管理系统当做数据库来使用就可能非常的令人困绕。

你需要存储数据还是文档,答案常常取决于你的XML文档。原因是XML文件分为两大类:以数据为中心和以文档为中心。.

4.1 以数据为中心的文件

以数据为中心的文件的特点是结构相当规范、数据颗粒度好(也就是说,数据中最小的独立单元是PCDATA元素或者是属性)、很少或者没有混合内容。其中同层次元素和PCDATA的出现顺序并不重要。典型的例子是,XML文档包含了销售定单、飞行安排、餐馆菜单等等。数据为中心的文档常被用于机器的使用,这时XML可能是多余的---它仅仅是数据传输的手段而已。

例如,下面的销售定单的文档就是以数据为中心的:

   

ABC Industries

123 Main St.

Chicago

IL

60609

981215

Turkey wrench:

Stainless steel, one-piece construction,

lifetime guarantee.

9.95

10

Stuffing separator:

Aluminum, one-year guarantee.

13.27

5

在XML的世界中,许多内容丰富的文档实际上都是数据为中心的。我们以显示图书信息的Amazon.com网站为例。虽然这个页面是相当巨大的文本,但是这个文本的结构是高度规范的,其中许多的部分对任何的书本描述页面都是相同的,并且特点页面中的各部分的大小都是有限的。也就是说,该页面可以通过一个简单的、数据为中心的XML文档来建立,其中包含了从数据库中检索得到的文本信息以及一个XSL样式表。通常,目前任何通过在模板中填充数据库数据而动态构造HTML页面的网站都可以被上面介绍的用以数据为中心的XML文档和一个或者多个的XSL样式表方式替代。

例如,我们来看下面的租房(Lease)文档:

   

ABC Industries agrees to lease the property at

123 Main St., Chicago, IL
from XYZ

Properties for a term of not less than

TimeUnit="Months">18 at a cost of

Currency="USD" TimeUnit="Months">1000.

可以从下面的XML文档和简单的样式表得到:

   

ABC Industries

123 Main St., Chicago, IL

XYZ Properties

18

1000

4.2 以文档为中心的文件

以文档为中心的文档的特点是:结构不规范、数据颗粒度更大(即,最小的独立数据单元是包含有混合内容的元素或者就是整个XML文档)以及含有大量的混合内容。其中相同层次的元素和PCDATA出现顺序是非常重要的。典型的例子是书、电子邮件、广告以及大多数XHTML文档。以文档为中心的文档是用于人的使用。

例如,下面的产品描述文档就是以文档为中心:  

   

Turkey Wrench

Full Fabrication Labs, Inc.

Like a monkey wrench, but not as big.

The turkey wrench, which comes in both right- and

left-handed versions (skyhook optional), is made of the finest

stainless steel. The Readi-grip rubberized handle quickly adapts

to your hands, even in the greasiest situations. Adjustment is

possible through a variety of custom dials.

You can:

Order your own turkey wrench

Read more about wrenches

Download the catalog

The turkey wrench costs just $19.99 and, if you

order now, comes with a hand-crafted shrimp hammer as a

bonus gift.

4.3 数据、文档和数据库

在现实情况中,以数据为中心的文件和文档为中心的文件之间的区别并不是很严格。例如,一个以数据为中心的文件(如一张发票),也有可能包含粗颗粒度、不规则的数据(如发票的描述部分)。而一个以文档为中心文件(如用户手册)也可能包含有良好颗粒度、规则的结构化数据(通常是元数据),例如作者和修订日期。除此之外,让你的文档具有以数据为中心或者以文档为中心的特点有助于你判断是关心数据还是文档,这也将决定你需要采用什么样的系统。

要存储或检索数据,你可以使用一个数据库(通常是关系型、面向对象型或者是层次型)和中间件(字带或者是采用第三方),你也可以使用XML服务器(即创建分布式应用的平台,例如利用XML进行数据传输的电子商务应用)。要保存文档,你将需要一个内容管理系统或者是一致性的DOM实现系统。有关各类系统的探讨在5.0

"存储和检索数据" 小节和6.0 "

href="#storingretrievingdocs">存储和检索文档 " 小节。你也能够在

href="http://www.rpbourret.com/xml/XMLDatabaseProds.htm">

XML数据库产品 中了解详细的相关产品列表。

5.0 存储和检索数据

在以数据为中心的文档中的数据内容可能来自数据库(此时你想把数据导出为XML格式),也可能是XML文档(此时你想把数据存储在数据库中)。前者的例子是在关系型数据库中存储的大量现有数据(或称遗产数据);后者的例子是将数据作为XML发布在Web中,而且你想要在你的数据库中进行存储以进行更多的处理。如此,根据你的需求,你可能需要将XML文档转移到数据库的软件,也可能需要从数据库转移到XML文档的软件,或者两者都支持。

5.1 转移数据

将数据存储在数据库中时,经常需要丢弃大量与文档有关的信息,例如文档名称和DTD,同时还有其物理结构,例如实体的定义和使用、属性值和同层元素的顺序、二进制数据的存储方式(是Base64编码、是未析实体或他方式)、字符数据段和其他的编码信息。类似的,当从数据库中检索数据时,生成的XML文档结果除了非预定义实体lt(<"),gt(">"), amp("&"), apos("’"),  quot(""")不包含任何CDATA或实体引用。而同层元素和属性的出现顺序也常常就是从数据库中返回的数据的次序。

尽管一开始有些让你吃惊,但是这常常是合理的。例如,假设你需要用XML作为数据格式把一张销售从一个数据库中转移到另一个数据库中。在这种情况下,在XML文档中并不关心销售单的编号是保存在销售单的日期的前面还是后面,也不用关心是否将顾客的名称保存在字符数据(CDATA)段还是作为一个外部实体,或者直接当成一个PCDATA。最重要的在于相关的数据是从第一个数据库转移到第二个数据库中。这样,这个数据传输软件就需要考虑数据的层次结构(该结构将销售单的有关进行进行了分组),而其他则不必过多考虑。   

忽略文档信息以及其物理结构的后果之一是

文档的"逆反回归"的不一致效应,即将一个文档的数据存储在数据库中,然后根据这些数据重新组织成新的文档。而即便是根据标准格式处理,得到的也常常是和前面不同的文档。这是否可以接受要取决于你的需求,而且也将影响到你对数据库和数据传输中间件的选择。

5.2

从文档结构到数据库结构的映射

为了在XML和数据库之间传输数据,需要在文档结构和数据库结构之间进行相互的映射。这样的映射通常分为两大类: 模板驱动和模式驱动。

5.2.1 模板驱动的映射

在以模板驱动的映射中,没有预先定义文档结构和数据库结构之间的映射关系

,而是使用将命令语句内嵌入模板的方法,让数据传输中间件来处理该模板。例如,考虑下面的模板(注意该模板并不适用任何实际的产品),在<SelectStmt>元素中内嵌了SELECT语句:

   <?xml version="1.0"?>

<FlightInfo>

<Intro>The following flights have available seats:</Intro>

<SelectStmt>SELECT Airline, FltNumber, Depart, Arrive FROM Flights</SelectStmt>

<Conclude>We hope one of these meets your needs</Conclude>

</FlightInfo>

当数据传输中间件处理到该文档时,每个SELECT语句都将被各自的执行结果所替换,得到下面的XML格式:

   <?xml version="1.0"?>

<FlightInfo>

<Intro>The following flights have available seats:</Intro>

<Flights>

<Row>

<Airline>ACME</Airline>

<FltNumber>123</FltNumber>

<Depart>Dec 12, 1998 13:43</Depart>

<Arrive>Dec 13, 1998 01:21</Arrive>

</Row>

...

</Flights>

<Conclude>We hope one of these meets your needs</Conclude>

</FlightInfo>

这种以模板驱动的映射可以相当的灵活。例如,有些产品可以允许你在任何结果集合中替换你想要的内容(包括在SELECT中使用参数),而不是象上面的例子中简单地格式化结果。另外它还支持使用编程来进行构造,例如循环和条件判断结构。还有一些还支持SELECT语句的参数化,例如通过HTTP来传递参数。

目前,以模板驱动的映射只支持从一个关系型数据库转换成XML文档的情况。

5.2.2 模型驱动的映射

在以模型驱动的映射中,利用XML文档结构对应的数据模型显式或隐式地将映射成数据库的结构,而且反之亦然。它的缺点是灵活性不够,但是却简单易用,这是因为它是基于具体的数据模型来进行映射的,通常能够为用户实现很多地转换工作。由于将数据从数据库转换成XML的结果依照了单个模型,

因此通常在这种方式下通常结合XSL来提供模板驱动的系统中所具有的灵活性。

在XML文档中的数据视图通常有两种模型:表格模型特定数据对象模型。有时候也可能会出现其他的模型。例如,通过采用ID和IDREF属性,一个XML文档可以用来一个指定的图形。不过,很多现有的中间件并不支持这些模型。

5.2.2.1 表格模型

许多中间件软件包都采用表格模型在XML和关系型数据库之间进行转换。它把XML的模型看成是一个单独的表格或者是一系列的表格。也就是说,XML的文档的结构和下面的例子相类似,其中在单个表格的情况下,<database>并不出现:

   <database>

<table>

<row>

<column1>...</column1>

<column2>...</column2>

...

</row>

...

</table>

...

</database>

其中的术语"table"可理解为单个的结果集(当从数据库向XML中转换数据时),或者是一个单独的表格或可更新的视图(当从XML向数据库转换数据时)。如果数据需要来自多个结果集(当数据来自数据库中时)或者与仅仅表达成一系列表格的集合(当转换数据到数据库时)相比,XML的文档包含有更深层次的嵌套元素,那么类似的转换几乎是不可能的。

5.2.2.2 特定数据对象模型

XML文档中第二种普遍的数据模型是特定数据对象的树型结构。在该模型中,元素类型通常对应对象,而XML中的内容模型、属性和PCDATA则对应对象的属性。这种模型直接映射成面向对象的数据库和层次型数据库,当然借助于传统的对象-关系映射技术和SQL

3对象视图也可以映射成关系数据库。要注意的是,这种模型并不是文档对象模型(DOM)。DOM是对文档本身进行建模,而不是对文档中的数据。如

href="#writeyourown">6.1.2小节所述,DOM用来在关系型数据库的基础上建立内容管理系统。

例如,上面的销售定单文档就可以看作是由五个类所组成的树型结构。如下面的视图所示,包括Orders, SalesOrder, Customer,

Line和Part类:

                       Orders

|

SalesOrder

/ | Customer Line Line

| |

Part Part

当把一个XML文档建模为一棵特定数据对象树时,就没有必要要求元素一定要对应于对象。例如,如果一个元素只包含PCDATA,如销售定单文档中的CustName元素,它可以当作一个属性进行处理,因此属性只包含单一的、标量型数值。类似的,有时将混合元素或元素内容模型化成属性也是非常有用的。一个现成的例子就是在销售定单文档中对Description元素的处理:尽管它在XHTML的格式中有混合内容,但是将Description元素看作单个的属性会更有用些,因为它的组成部分本身并没有什么意义。

5.3 数据类型、空值、字符集合和其它

本节将探讨一些有关来自数据库的XML文档的存储问题。通常,你决定不了你选择的中间件是如何解决这些问题的,但是你最好应该意识到这些问题的存在,因为这有助于你正确选择你的中间件。

5.3.1 数据类型

XML不支持任何有实际意义的数据类型。除了未析实体,所有XML文档中的数据都被当成文本来对待,即便它能够用其他的数据类型(如日期或者整数)来表示。通常,数据转换中间件将把XML文档中的文本转换成其它数据库中的数据类型,反之亦然。然而,特定的数据类型所识别的文本格式是有限制的,例如受到提供的JDBC

Driver所支持的数据类型的限制。在这些众多的数据类型中,日期类型通常会导致麻烦。不同国际地区的数字格式的差异也可能产生问题。

5.3.2 二进制数据

通常有两种方法将二进制数据保存到XML文档中的:未析实体和Base64编码处理(一种MIME编码方法,可以将二进制数据映射成US-ASCII的子集)。

对于关系型数据库,这两种方法都可能存在问题,因为从数据库中保存和检索二进制数据的规则非常的严格,这样对会导致中间件出现问题。

另外,并没有一种标准的符号用来说明一个XML文档中的元素包含有Base64编码数据,从而使得中间件可能根本就不能够识别这种编码。最后,在存储数据到数据库时,可能会忽略与未析实体或Base64编码元素相关的符号。所以,如果对你而言二进制数据非常重要的话,请务必要确认你的中间件是否支持二进制数据。

5.3.3 空值

在数据库世界中,空值(null)数据意味着数据不存在值。但是这与一个值为0的数字或长度为0的字符串有很大的区别。例如,假设你的数据来自一个气象站,

如果气象站的温度计出了毛病读不出温度值,那么你的数据库中将存储一个null值而不是一个0。显然,值为0完全是另外一回事了

XML中空值概念的支持可以通过设置可选的元素类型或属性来实现。如果元素类型或属性值为null,XML只要在文档不包含该元素或者属性就可以了。但是对数据库而言,空的元素或包含0长度字符串的属性并不是空值null:它们的值为长度为0的字符串。

当在XML文档和数据库结构之间相互映射过程中,你必须特别注意那些可选的元素类型或属性是否对应于数据库中的空值项。如果不这么做的话,很可能出现插入错误(当将数据转换到数据库中时)或者无效文档错误(当将数据从数据库读出时)。


因为同样要用符号空值,XML中相对与数据库而言更为灵活。具体来讲,许多XML用户很可能包含空字符串的空元素或属性是空值。这个时候你必须考虑如何选择合适的中间件来解决这个问题。一些中间件可以让用户选择在XML文档中定义用什么来组成空值。

5.3.4 字符集

根据定义,除了一些控制字符,XML文档能够包含任何的Unicode字符。但是不幸的是,许多数据库都限制或则不支持Unicode,而且需要一些特殊的配置才能够处理非ASCII编码的字符数据。如果你的数据包含了非ASCII字符,那么务必要核实你的数据库和中间件是否能够处理这些字符。

5.3.5 处理指令

处理指令并不属于XML文档中的“数据”部分,因此目前许多中间件可能不能正常的处理。问题是,尤其是在将XML文档结构严格映射成数据库结构时,处理指令通常是很难处理的,因为它们可以虚拟地出现在文档的任何位置。因此,中间件就很难判断将它们保存到什么位置以及在什么时候检索读取出来。如果处理指令和文档的循环回复("round-tripping")对你而言是非常重要的话,就务必检查你的中间件是如解决这个问题的。

5.3.6 存储标记

href="#markup">4

href="#markup">.2.2

小节中提到,有时候将包含元素或者混合内容的元素不作进一步的解析而直接保存到数据库中是非常有用的。最常见的方法是简单的把这个标记本身直接保存到数据库中。不幸的是,当从数据库中检索数据时将产生问题:不可能判断数据库中的标记到底是真的标记还是代表了标记字符的实体,如由lt和gt转义的字符。

例如,下面的description元素:

   <description>

<b>Confusing example:</b> &lt;foo/&gt;

</description>

在数据库中存储为:

   <b>Confusing example:</b> <foo/>

这时数据库就不能判断<b>和<foo>是标记还是文本。有几种可能的解决方法,如以一定的方式来标志标记或者对非标记的标记字符使用实体。但是这时你要格外注意这样的方式是否和使用这些的数据的其它应用兼容。例如,如果你想查询数据库中的小于号("<")和

lt实体("<")时就要特别留心。

5.4 从数据库的结构生成DTD及其互逆过程

在XML文档和数据库之间转换数据时,一个普遍问题是:如何从数据库的结构(Schema)生成XML的DTD,如果从XML的DTD产生数据库的结构。简而言之,这是非常直接的操作,但是产生的结果通常离许多用户的期望值还有一些距离。

(还要注意这通常是一次性操作,而大多数应用,尤其是所有的垂直性应用都结合了已知的DTD和关系型Schema的集合。显而易见的特例是在关系数据库中存储随机XML文档或者将关系型数据发布为XML文档的工具;而在后面的情况中,DTD的作用并不明显。)

对于元素类型中每个有单一数值的属性和只包含有PCDATA内容的子元素类型在该ta

ble中新建立一列(字段)。如果子元素类型或则属性是可选的,让该字段允许为空。

对于每个有多值的属性或则多仅含有PCDATA内容的子元素类型,再建立一个分开的

table来保存他们的值,通过它们的父表的主关键字连接到父表。

对于每个子元素,这些子元素本身还有元素或则混合内容,使用父表中的关键字将

父元素表连接到子元素表中。

而下面则是一个从关系数据库的结构生成XML文档的过程(简化过的):

对每个table,新建一个元素。

对表中的每列,建立一个属性或则只含PCDATA的子元素

对每个包含有在主键/外键关键字关系中主键值的列,新建一个子元素。

 

例如,下面的过程(经简化)说明了如何从一个DTD生成一个关系型结构:

  1. 对于每种包含元素或者混合内容的元素类型,新建一个表格和一个主键字段。

  2. 对于每个包含混合内容的元素类型,创建一个单独的表格,其中存放未析数据,通过父元素主键链接到父表格。

  3. 对于此元素类型的每个单值属性和只包含未析数据内容、只出现一次的子元素,在该表格中创建一个字段。如果元素类型或者属性是可选的,可以让设置该字段为空值。

  4. 对于每个多值属性和多次出现的子元素,创建一个单独的表格来存储数值,并且通过父元素主键链接到父表格。

  5. 对每个有元素或者混合内容的子元素,通过父元素主键将父元素表格和子元素表格相连接。

下面的过程(经简化)说明了如何从一个关系型的结构生成一个DTD:

  1. 对于每个表格,新建一个元素;

  2. 对于表格中的每个字段,新建一个属性或者是只包含未析数据的子元素;

  3. 对于每个表格字段中提供主键的主键/外键的关系都新建一个子元素。

不幸的是,这些过程还存在着一些缺陷。例如,DTD中没有方法预先准确地规定数据类型或者字段长度。

因为任何的预先定义(例如通过读取一个示例文档)在读取其它“类型”的文档或者其他文档中包含有超过字长内容的文档时就会产生错误。(长久之策是使用XML schema文档的数据类型。)简单来说,当从一关系型结构生成DTD时,是没有办法预先判断子元素“应该”出现的顺序或者字段(如数据库内部的行标识)是否该进行完全转换。

在以上两种情况中都可能产生命名的冲突。

尽管有这样那样的缺陷,但是这些方法仍然能够很好地奠定在关系型结构和DTD之间互相转换的起点。

<淘宝热门商品:
 

3.80 元  

幸福生活 联盟津沽 种子蔬菜种子、花卉种子、园艺用品

【天津商盟】【皇冠信誉】食用草莓种子-观赏草莓种子-花种

 

3.20 元  

Lily's 园艺-万元赠品大派送!种球“买就送”!朱顶红已到货!

Lily's-现货!PT-01葡萄风信子album-荷兰进口种球/球根-皇冠


来源:程序员网

小小豆叮

JGenerator Version 2.3

<淘宝热门商品:
 

136.00 元  

达人公社 专柜正品 纽巴伦 newbalance 匡威 crocs折扣 店

正品 日版纽巴伦new balance 型号:TK02 深灰黑

 

198.00 元  

野葛根丰胸胶囊 植物萃取 天然健康


来源:程序员网

小小豆叮

JPool

小小豆叮

Swinglets Version1.2

<淘宝热门商品:
 

 

【杭州商盟】快美影像中心

 

52.00 元  

【卡盟在线】

皇冠在线充|QQ黑钻腾讯黑钻地下城与勇士(DNF)黑钻3月


来源:程序员网

小小豆叮

用UML设计Java应用程序

本文的案例学习提供了一个例子,说明如何将UML用在现实中。一个处理图书馆借阅和预定图书和杂志的应用程序,可以大到足够检验UML解决现实问题能力的程度。但是如果太大的话,则不适合在杂志上发表。 在分析模型中,用用例和域分析描述了应用程序。我们进一步把它扩展成设计模型。在设计模型中,我们描述了典型的技术解决方案细节。最后,我们编写了一段Java代码(代码连同完整的分析和设计模型放在网上,以一种包括评估版在内的Rational Rose能够识别的格式在线提供。) 必须注意,这里只是一个可行的解决方案。可能会有许多其他的解决方案。没有绝对正确的方案。当然,有的方案更好一些,但只有不断的实践和努力的工作才能掌握相应的技能。 1.需求(Requirements) 典型地,由系统最终用户的代表写出文本形式的需求规范文档。对于该图书馆应用程序来说,需求规范文档应该类似于这样: 1. 这是一个图书馆支持系统; 2. 图书馆将图书和杂志借给借书者。借书者已经预先注册,图书和杂志也预先注册; 3. 图书馆负责新书的购买。每一本图书都购进多本书。当旧书超期或破旧不堪时,从图书馆中去掉。 4. 图书管理员是图书馆的员工。他们的工作就是和读者打交道并在软件系统的支持下工作。 5. 借阅人可以预定当前没有的图书和杂志。这样,当他所预定的图书和杂志归还回来或购进时,就通知预定人。当预定了某书的借书者借阅了该书后,预定就取消。或者通过显式的取消过程强行取消预定。 6. 图书馆能够容易地建立、修改和删除标题、借书者、借阅信息和预定信息。 7. 系统能够运行在所有流行的技术环境中,包括Unix, Windows和OS/2,并应有一个现代的图形用户界面 (GUI)。 8. 系统容易扩展新功能。 系统的第一版不必考虑预定的图书到达后通知预定人的功能,也不必检查借书过期的情况。 2.分析(Analysis) 系统分析的目的是捕获和描述所有的系统需求,并且建立一个模型来定义系统中主要的域类。通过系统分析达到开发者和需求者的理解和沟通。因此,分析一般都是分析员和用户协作的产物。 在这个阶段,程序开发者不应该考虑代码或程序的问题;它只是理解需求和实现系统的第一步。 2.1需求分析(Requirements Analysis) 分析的第一步是确定系统能够做什么?谁来使用这个系统?这些分别叫角色(actors)和用例(use cases)。用例描述了系统提供什么样的功能。通过阅读和分析文档,以及和潜在的用户讨论系统来分析用例。 图书馆的角色定为图书管理员和借书人。图书管理员是软件系统的用户;而借书者则是来借阅或预定图书杂志的客户。偶尔,图书管理员或图书馆的其他工作人员也可能是一个借书者。借书者不直接和系统交互,借书人的功能由图书管理员代为执行。 图书馆系统中的用例有: 1. 借书 2. 还书 3. 预定 4. 取消预定 5. 增加标题 6. 修改或删除标题 7. 增加书目 8. 删除书目 9. 增加借书者 10. 修改或删除借书者 由于一本书通常有多个备份,因此系统必须将书的标题和书目的概念区分开。 图书馆系统分析的结果写在UML 用例图中,如图1所示。每一个用例都附带有文本文档,描述用例和客户交互的细节。文本是通过与客户讨论得到的。用例“借书”描述如下: 1.如果借阅者没有预定: 确定标题 确定该标题下有效的书目 确定借书者 图书馆将书借出 登记一个新的借阅 2.如果借阅者有预定: 确定借书人 确定标题 确定该标题下有效的书目 图书馆将相应的书目借出 登记一个新的借阅 取消预定 除了定义系统的功能需求之外,在分析过程中用例用于检查是否有相应的域类已经被定义,然后他们可以被用在设计阶段,确保解决方案可以有效地处理系统功能。可以在顺序图中可视化实现细节。 图1:角色和用例。分析中的第一步就是指出系统能被用来做什么,谁将去使用它。它们分别就是用例和角色。所有的用例必须始于角色,而且有些用例也结束于角色。角色是位于你所工作的系统外部的人或其他系统。一台打印机或一个数据库都可能是一个角色。本系统有两个角色:借阅者和图书管理员。通过与用户或客户的讨论,可以将每一个用例用文字进行说明。 2.2域分析(Domain Analysis) 系统分析也详细地列出了域(系统中的关键类)。为了导出一个域分析,可以阅读规范文档(specifications)和用例,查找哪一些概念应该被系统处理。或者组织一个集体讨论,在用户及领域专家共同的参与下指出系统中必须处理的关键概念,以及它们之间的关系。 图书馆系统中的域类如下:borrowerinformation(如此命名是为了与用例图中的角色borrower区分开来),title,book title, magazine title, item, reservation和loan。这些类以及它们之间的关系记录在类图文档中,如图2所示。域类定义为Business object版型,Business object版型是一个用户自定义的版型,指定该类的对象是关键域的一部分,并且应该在系统中持久存储。 其中有些类有UML状态图,用来显示这些类的对象可能具有的不同状态,以及触发他们的状态发生改变的事件。该例子中有状态图的类是item 和title类。 用例lend item(借阅者没有预定的情况)的顺序图显示在图3中。所有用例的顺序图都可从在线模型中查到。 图2:域类结构。域分析详细说明了系统中的关键类。对每一个对象而言,如果它调用了其他对象的方法,那么在他们之间就用一条直线连结起来,以显示他们之间的关系。每一个代表类的四边形被分成了三部分,最顶层包括类的名称,中间一层是类的属性,最底层是类的方法。类之间的直线是关联,用来指出一个对象调用另一个对象的方法。如果再仔细看,将会发现在Loan和Item之间的关联关系中靠近Loan的一端有“0..1”,这代表关联的重数。重数“0..1表示Item可以感知0个到1个loan。其他可能出现的重数还有:“0..*”表示0或多;“1”表示就是1;“0”表示就是0,“1..*”表示1或多。 当对顺序图建模时,必须提供窗体和对话框作为人机交互的界面。在本分析当中,只要知道借书、预定和还 书需要窗体就可以了。在此,详细的界面不必考虑。为了把系统中的窗体类和域类分开,所有的窗体类组织在一起放在GUI Package包中。域类组织在一起放在Business Package包中。 图3:Lend item场景的顺序图。场景是从头到尾实现一个用例的一次特定的过程。场景总是始于角色,而角色是属于系统外部的。场景描绘了从所有角色的观点出发,完成一次系统动作的完整过程。UML在用顺序图来图示场景。本用例图显示了在借阅者没有预定图书的情况下的Lend用例。横在图的顶部的是参与交互的对象。自上而下表示时间的流逝。首先,图书管理员尝试去查找标题。标有“Lending Window”的是用户界面,在分析阶段作为一个粗略的对象。横在顺序图中的每一个箭头都是一次方法的调用,箭头的首端是调用的对象,箭头的末端是被调用的对象。 3.设计(Design) 设计阶段对分析模型进行扩展并将模型进一步细化,并考虑技术细节和限制条件。设计的目的是指定一个可行的解决方案,以便能很容易地转变成为编程代码。 设计可以分成两个阶段: 体系结构设计阶段(Architecture Design)。这是一个从较高层次的进行的设计,用来定义包(子系统),描述包之间的依赖性及通信机制。很自然,目的是要设计一个清晰简单的体系结构,有很少的依赖性,而且尽可能避免双向依赖。详细设计阶段(Detailed Design)。在此阶段,所有的类都详尽地进行描述,给编写代码的程序员一个清晰的规范说明。UML中的动态模型用来说明类的对象如何在特定的情况下做出相应的表现。 3.1体系结构设计 一个良好的体系结构设计是一个可扩展的和可改变的系统的基础。包可能关注特定的功能领域或关注特定的技术领域。把应用程序逻辑(域类)和技术逻辑分开是至关重要的,这样不管哪一部分的改变都不会影响其他的部分。 本案例的包或叫子系统如下: User-Interface Package包。该包中的类基于Java AWT包,java AWT是一个用来书写用户界面应用程序的Java的标准库。该包和Business-objects Package包协作。Business-objects Package包包含那些实际存储数据的类。UI包调用Business 对象的操作,对他们进行取出或插入数据操作。 Business-object Package。该包包括域类,这些域类(如borrowerinformation,title,item,loan等)来自于分析模型。设计阶段完整地定义了这些类的操作,并增加了一些其他细节来支持持续存储。Business-object包与Database Package进行协作。所有的Business-object类必须继承Database Package中的persistent类。 Database Package。Database Package向Business-object包中的类提供服务,以便他们能够持续地存储。在当前版本中,persistent类将把它的子类的对象存储到文件系统的文件中。 图4:图书馆应用程序体系结构设计总览。本类图显示了应用程序包以及它们之间的依赖性。Database包提供了persistence类。Utility包提供了Object ID类。Business-Object包包含了域类(详细情况参见图5)最后,UI包(在本例中它是基于标准Jaa AWT库)调用business对象中的操作来实现对他们的数据存取操作。, 3.2详细设计 细节设计描述了新的技术性的类,如User-Interface和Database 包中的类,并且丰富了分析阶段所形成的Business-Object类。类图、状态图和动态图用的还是分析阶段所形成的图,但对他们定义的更加详细,具有了更高的技术水平。在分析阶段对用例进行的文字性描述在此用来证明用例在设计阶段也能被处理。顺序图就是用来说明用例如何在系统中被实现的。 Database Package。应用程序必须有持续存储的对象。因此,必须增加数据层来提供这样的服务。为简单起见,我们将对象以文件的形式保存在磁盘上。存储的细节被应用程序隐藏起来,只需调用诸如store(), update(),delete()和find()这样的公共操作即可。这些都是persistent类的一部分,所有需要持续对象的类必须继承它。 对类进行持续处理的一个重要因子就是ObjId类。它的对象用来引用系统中的任何持续对象(不管这个对象是在磁盘上还是已经被读进了应用程序之中)。ObjId是Object Identity的简写,它是一个广为应用的技术,用来有效地处理应用程序中的对象引用。通过使用object identifiers,一个对象ID能被传递到普通的persistent.getobject()操作中,进而该对象将被从持续的存储体中取出或存储。通常情况下,这些都是通过每个持续类的一个getobject操作完成的。当然,持续类同时也作一些检查或格式转换的操作。一个对象标识符也能作为一个参数很容易地在两个操作之间传递(例如,一个查找特定对象的查询窗口可以将它的查询结果通过object id传递给另一个窗口 )。 ObjId是一个系统中所有的包(User Interface , Business Object和Database)都能使用的通用类,所以在设计阶段它被放置在Utility包中,而不是放在Database包中。 当前对persistent类的实现还能改进。为此,定义persistent类的接口,方便持续存储的改变。一些备选的方案可能是:将对象存储在一个关系数据库中或存储在面向对象的数据库中,或使用Java 1.1所支持的持续对象来存储他们。 Business-Object Package。设计阶段的Business-Object包基于相应的分析阶段的放置域类的包。类和类之间的关系以及他们的行为继续保留,只是被描述的更为详细,包括他们的关系和行为如何被实现。 分析模型中的一些操作中被翻译成设计模型的操作,另一些改了名字。这是很正常的事,因为分析阶段得到的是每一个类的草图,而设计阶段是对系统的详细描述。因此,设计模型的操作必须有设计良好的特征值和返回值(由于空间限制,图5没有显示,但他们在在线模型中都有)。注意以下所列的设计和分析阶段的变化: 1. 系统的当前版本不要求检查书目是否按时归还,也不要求处理预定的次序。因此没有在loan 和reservation类中设置日期属性。 2. 除了最长借阅期外,对杂志和书标题的处理方式是一样的。因此分析阶段的子类magazine title和book title被认为在设计阶段是不必要的,而是在title类中增加type属性来指出该标题引用的是一本书还是一本杂志。在面向对象的设计中不存在设计不能简化分析的说法。 如果认为有必要的话,在将来的版本中这些简化都可以很容易地被取消。 分析阶段的状态图也在设计阶段细化了。状态图显示了如何表示状态及如何在系统中处理状态。设计阶段的title 类的状态图如图6所示。其他的对象可以通过调用如图所示的操作addreservation()和removereservation()来改变title对象的状态。 User-Interface Package。User-Interface Package位于其他包的“上面”。在系统中它为用户提供输出信息和服务。正如上面曾经提到的,该包是基于标准Java AWT(abstract windows toolkit)类的。 设计模型中的动态模型放置在GUI包中,因为所有和用户的交互都从用户界面开始。在此声明,顺序图用来显示动态模型。用例在设计模型中的实现通过顺序图被详细地显示出来,包括类中的实际操作。 顺序图由一系列的交互构成。在实现阶段(编码),考虑到具体情况,可能会有更多的交互。图7显示了add title用例的顺序图。实际的操作和特征值从在线模型代码中可以看到。 图5:商业对象设计(Business-Object design)。本图描述了在Business-Object包中的不同类的设计。设计包括定型模型,更完全地定制界面,给属性选择数据类型等等。 6:Title的状态图。Title具有预定和非预定状态,在设计中,通过称为“reservations”的矢量来实现。 7:Add Title的顺序图。本图中所涉及到的用户界面问题的详细情况已经超出了本文的讨论范围。 协作图可以作为顺序图的替代,如图8所示: 图8:Add Title的协作图。本图中涉及到的用户界面问题的详细情况已经超出了本文讨论的范围 3.3 用户界面 设计(User-lnterface Design) 设计阶段的一个特定的活动是创建用户界面。 图书馆系统的用户界面基于用例,分为以下几部分,每一部分都在主窗体菜单上给出一个单独的菜单项。 Functions:实现系统基本功能的窗体,通过它可以实现借阅、归还和对图书的预定。 Information:查看系统信息的窗体,收集了借阅者和图书的信息。 Maintenance:维护系统的窗体,添加、修改和删除标题、借阅者和书目。 图9显示了一个User-Interface Package中类图的例子。其中包含了典型的AWT事件句柄。按钮(button)、标签(label)和编辑(edit)等的属性没有显示。 典型地,每一个窗体都给出了系统的一个服务,并且映射一个最初的用例(尽管并非所有的用户界面都必须从用例中映射)。创建成功的用户界面已经超出了本文所讨论的范围。在此邀请读者来考虑用symantec visual cafe 环境开发的本应用程序的UI代码(已经放在网上)。 图9:功能类图模型。功能菜单中的用户界面类一般都有1对1的关联关系,表示需要建立关联的窗口类,或者需要访问关联的商业对象类。 4.实现(Implementation) 在构造或称实现阶段进行程序编写。该应用程序要求能运行在几个不同的处理器和不同的操作系统上,因此选择Java来实现系统。Java可以轻松地将逻辑类映射为代码组件,因为在类和Java代码文件之间有1对1的映射。 图10:组件图显示了依赖性。源代码组件实现了域类,他们之间的关联显示为双向依赖性 图10显示,设计模型的组件视图简单地将逻辑视图中的类映射为组件视图中的组件。每个逻辑视图包含了一个指向逻辑视图中类的连接,因此可以很容易地在不同的视图之间导航(即便象本例只是简单地使用了文件名)。由于依赖性可以从逻辑视图的类图中得到,因此组件图中没有显示组件之间的依赖性。 编写代码时,从下面的设计模型的图中取出规范说明: 1.类规范:每一个类的规范详细地显示了必要的属性和操作。 2.类图:类图由类构成,显示了类的结构以及类之间的关系。 3.状态图:类的状态图显示了类可能具有的状态以及需要处理的状态转移(以及触发转移的操作)。 4.动态图(顺序图、协作图和活动图):涉及到类的对象,显示了类中的特定方法如何实现,或对象之间如何使用其它类的对象进行交互。 5.用例图和规范:当开发者需要了解更多的关于系统如何被使用的信息时(当开发者感到他或她已经迷失在一片细节中),他们显示了系统被使用的结果。 很自然,设计中的某些缺陷可以在编码阶段发现。可能需要一些新的操作或修改某些操作,这意味着开发者必须改变设计模型。这在所有的工程中都会发生。重要的是将设计模型和代码同步,以便使模型能够成为系统的最终文档。 这里给出了loan 类和部分titleframe类的Java代码。整个应用程序的Java代码可以从网上查到。当学习这些代码时,注意结合UML模型,考虑UML结构是如何被转变为代码的。注意以下几点: 1.Java包规范是与类所属的逻辑视图或组件视图中相应的包等值的代码。 2.私有属性对应于模型中指定的属性;并且,很自然Java 方法对应于模型中的操作。 3.ObjId类(对象标识符)被调用来实现关联。也就是说,关联通常和类一起保存(因为ObjId类是持续的)。 程序清单1的代码示例来自于loan类,loan类是一个Business-Object类,用来存放借阅信息。由于该类主要是信息的存放处,因此程序的实现是直接的,代码也简单。大多数的功能继承了Database 包中的Persistent类。该类的唯一属性是对象标识符,将item和borrowerinformation类关联起来。并且这些关联属性也在Write()和Read()操作中被保存。 试着在Add Title顺序图(图7)上下文中检验一下程序清单2中显示的Addbutton_Clicked()操作。结合顺序图阅读代码,可以看出:它就是另一个对顺序图所表达的协作关系的更为详细的描述。 所有设计模型中的顺序图的编码都包含在源代码当中(操作名和类名显示在顺序图中) 5.测试和部署(Test and Deployment) 编码结束后,UML的使用还没有停止。例如,可以检验用例能否在已完成的应用程序中得到很好的支持。对于系统的部署来说,利用模型和本文可以做一份得心应手的文档。 6.总结 本学习案例的不同部分由一组人分别来设计完成。他们以做实际工程的方式努力完成该工作。尽管不同的阶段和活动似乎是独立的,而且以严格的顺序来管理,但在实际工作中仍然有很多反复。设计中的经验和教训反馈到分析模型,实现阶段所发现的新情况在设计模型中更改或更新。这就是建立面向对象系统的一般方法。 本文摘录自UML Toolkit,New York:Wiley & Sons,1998. Hans-Erik Erikkson是一个有名的C++和OO技术方面的作者。Magnus Penker是Astrakan培训副主席,Astrankan是一个瑞典专攻OO建模和设计的公司。 原文链接:http://www.umlchina.com/Indepth/DesignJava.htm 模型及源码:http://www.umlchina.com/zippdf/libraryjava.zip 附:主要术语中英文对照 actor:角色 use case:用例 domain:域 domain analysis:域分析 specification:规范文档 sequence diagram:顺序图 collaboration diagram:协作图 component diagram:组件图 state diagram:状态图 dependency:依赖性 attribute:属性 method:方法 operation:操作 association:关联 multiplicity:重数 class:类 object:对象 package:包 implementation:实现 deployment:部署
 
附:源代码 
Listing 1. Loan class. Loan is a Business-object class used for storing information about a loan.

Most of the functionality is inherited from the Persistent class in the Database Package.

  

//  Loan.java: represents a loan. The loan refer to one
//  title and one borrower.
//

Package bo;
import util.ObjId;
import db.*;
import java.io.*;
import java.util.*;

public class Loan extends Persistent
{   private ObjId item;
   private ObjId borrower;
   public Loan(){   
   }
   public Loan(ObjId it, ObjId b)
   {
   item = it;
   borrower = b;
   }

   public BorrowerInformation getBorrower()
   {

   BorrowerInformation ret =
      (BorrowerInformation) Persistent.getObject(borrower);
   return ret;
   }

   public String getTitleName()
   {
   Item it = (Item) Persistent.getObject(item);
   return it.getTitleName();
   }

   public Item getItem()
   {
   Item it =
      (Item) Persistent.getObject(item);
   return it;
   }

   public int getItemId()
   {
   Item it = (Item) Persistent.getObject(item);
   return it.getId();
   }

   public void write(RandomAccessFile out)
   throws IOException
   {
   item.write(out);
   borrower.write(out);
   }

   public void read(RandomAccessFile in)
   throws IOException
   {
   item = new ObjId();
   item.read(in);
   borrower = new ObjId();
   borrower.read(in);
   }

}

Listing 2. TitleFrame Class. This is another, more detailed description of the collaboration described by the diagram in Figure 7.

//  TitleFrame.java
//
Package ui;

import bo.*;
import util.*;
import java.awt.*;

public class TitleFrame extends Frame {
   private Title current;
   void addButton_Clicked(Event event) {
   if (Title.findOnName(titleField.getText()) != null)
   {
      new MessageBox(
      this,"A Title with that name already exists!");
      return;
   }
   if (Title.findOnISBN(isbnField.getText()) != null)
   {
      new MessageBox(
      this,"A title with the
      same isbn/nr field already exists!");
      return;
   }

   int type = 0;
   if (bookButton.getState() == true)
      type = Title.TYPE_BOOK;
   else if (magazineButton.getState() == true)
      type = Title.TYPE_MAGAZINE;
   else
   {
      new MessageBox(this,"Please give type of title!");
      return;
   }
   current =
      new Title(
      titleField.getText(),
      authorField.getText(),
      isbnField.getText(),
      type);
   int itemno;
   if (itemsField.getText().equals(""))
      itemno = 0;
   else
      itemno = Integer.valueOf(
      itemsField.getText()).intValue();
   if (itemno > 25)
   {
      new MessageBox(this, "Maximum number of items is 25!");
      return;
   }
   for (int i = 0; i <\<> itemno; i++)
   {
      Item it = new Item(current.getObjId(),i+1);
      it.store();
      current.addItem(it.getObjId());
   }
   current.store();
   titleField.setText("");
   authorField.setText("");
   isbnField.setText("");
   itemsField.setText("");
   bookButton.setState(false);
   magazineButton.setState(false);
   }

   void cancelButton_Clicked(Event event) {
   dispose();
   }
   public TitleFrame() {
   //{{INIT_CONTROLS
   setLayout(null);
   addNotify();
   resize(
      insets().left + insets().right + 430,insets().top +
      insets().bottom + 229);
   titleLabel = new java.awt.Label("Title Name");
   titleLabel.reshape(
      insets().left + 12,insets().top + 24,84,24);
   add(titleLabel);
   titleField = new java.awt.TextField();
   titleField.reshape(
      insets().left + 132,insets().top + 24,183,24);
   add(titleField);
   authorField = new java.awt.TextField();
   authorField.reshape(
      insets().left + 132,insets().top + 60,183,24);
   add(authorField);
   isbnField = new java.awt.TextField();
   isbnField.reshape(
      insets().left + 132,insets().top + 96,183,24);
   add(isbnField);
   label1 = new java.awt.Label("ISBN / Nr");
   label1.reshape(
      insets().left + 12,insets().top + 96,84,24);
   add(label1);
   label2 = new java.awt.Label("Author");
   label2.reshape(
      insets().left + 12,insets().top + 60,84,24);
   add(label2);
   addButton = new java.awt.Button("Insert");
   addButton.reshape(
      insets().left + 348,insets().top + 24,60,24);
   add(addButton);
   cancelButton = new java.awt.Button("Close");
   cancelButton.reshape(
      insets().left + 348,insets().top + 192,60,24);
   add(cancelButton);
   label3 = new java.awt.Label("Items available");
   label3.reshape(
      insets().left + 12,insets().top + 192,108,24);
   add(label3);
   itemsField = new java.awt.TextField();
   itemsField.reshape(
      insets().left + 132,insets().top + 192,36,23);
   add(itemsField);
   Group1 = new CheckboxGroup();
   bookButton =
      new java.awt.Checkbox("Book", Group1, false);
   bookButton.reshape(
      insets().left + 132,insets().top + 132,108,24);
   add(bookButton);
   magazineButton =
      new java.awt.Checkbox("Magazine", Group1, false);
   magazineButton.reshape(
      insets().left + 132,insets().top + 156,108,24);
   add(magazineButton);
   label4 = new java.awt.Label("Type");
   label4.reshape(

     insets().left + 12,insets().top + 132,108,24);
   add(label4);
   setTitle("Insert Title Window");
   //}}
   bookButton.setState(true);
   titleField.requestFocus();
 
   //{{INIT_MENUS
   //}}
   }
  

   public TitleFrame(String title) {
   this();
  setTitle(title);
   }

   public synchronized void show() {
   move(50, 50);
   super.show();
   }
  
   public boolean handleEvent(Event event) {
   if (event.id == Event.WINDOW_DESTROY) {
      dispose();
      return true;
   }
   if (event.target == addButton && event.id ==
      Event.ACTION_EVENT) {
      addButton_Clicked(event);
      return true;
   }
   if (event.target == cancelButton && event.id ==
      Event.ACTION_EVENT) {
      cancelButton_Clicked(event);
      return true;
   }
   return super.handleEvent(event);
   }
  
   //{{DECLARE_CONTROLS
   java.awt.Label titleLabel;
   java.awt.TextField titleField;
   java.awt.TextField authorField;
   java.awt.TextField isbnField;
   java.awt.Label label1;
   java.awt.Label label2;
   java.awt.Button addButton;
   java.awt.Button cancelButton;
   java.awt.Label label3;
   java.awt.TextField itemsField;
   java.awt.Checkbox bookButton;
   CheckboxGroup Group1;
   java.awt.Checkbox magazineButton;
   java.awt.Label label4;
   //}}
  
   //{{DECLARE_MENUS
   //}}
}
<淘宝热门商品:
 

 

【华佗天然居】天然花草茶花茶中草药中药材

 

 

时尚前线-袜子、打底裤、时尚女包 时尚从这里开始


来源:程序员网

小小豆叮

简单对象访问协议(SOAP)初级指南

(本文假设读者对COM和XML技术已经很熟悉。) SOAP(Simple Object Access Protocal) 技术有助于实现大量异构程序和平台之间的互操作性,从而使存在的应用能够被广泛的用户所访问。SOAP是把成熟的基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起。 这篇文章带你全面回顾对象远程进程调用(ORPC)技术的历程,以帮助你理解SOAP技术的基础,以及它克服存在技术(如CORBA和DCOM)的许多缺陷的方法。随后讲述详细的SOAP编码规则,并把焦点放在SOAP是怎样映射到存在的ORPC概念上的。 引言: 当我在1984年开始把计算作为我的职业的时候,大多数程序员并不关心网络协议。但是在九十年代网络变得无所不在,现在如果有谁使用计算机却不使用某种形式网络连接是很难以想象的。今天,一般的程序员对建立可扩展的分布式应用表现出更大的兴趣,而不再只是关注于用MFC实现个性化的可浮动半透明非矩形的Coolbars了。 程序员通常喜欢用编程模型来思考问题,而很少考虑网络协议。尽管这样做通常是很好的,但在这篇文章中我将讨论的SOAP是一个没有明显的编程模型的网络协议。这并不意味着SOAP的体系结构从根本上会改变你编程的方式。相反,SOAP的一个主要目标是使存在的应用能被更广泛的用户所使用。为了实现这个目的,没有任何SOAP API或SOAP 对象请求代理(SOAP ORB),SOAP是假设你将使用尽可能多的存在的技术。几个主要的CORBA厂商已经承诺在他们的ORB产品中支持SOAP协议。微软也承诺在将来的COM版本中支持SOAP。 DevelopMentor已经开发了参考实现,它使得在任何平台上的任何Java或Perl程序员都可以使用SOAP。 在SOAP后面的指导理念是“它是第一个没有发明任何新技术的技术”。SOAP采用了已经广泛使用的两个协议:HTTP和XML。HTTP用于实现SOAP的RPC风格的传输,而XML是它的编码模式。采用几行代码和一个XML解析器,HTTP服务器(如MS的IIS或Apache)立刻成为了SOAP的ORBs。 因为目前超过一半的Web服务器采用IIS或Apache, SOAP将会从这两个产品的广泛而可靠的使用中获取利益。这并不意味着所有的SOAP请求必须通过Web服务器来路由,传统的Web 服务器只是分派SOAP请求的一种方式。因此Web服务如IIS或Apache对建立SOAP使能的应用是充分的,但决不是必要的。 正如这篇文章将要描述的,SOAP简单地用XML来编码HTTP的传输内容。SOAP最常用的应用是作为一个RPC协议。为了理解SOAP怎样工作,有必要简要回顾一下RPC协议的历史。 RPCs的历史 建立分布式应用的两个主要通信模型是消息传送(经常与队列组合在一起)和请求/响应。消息传递系统允许通信任何一方在任何时间发送消息。请求/响应协议把通信模式限制在请求/响应的双方。基于消息的应用强烈地意识到它们正在与外部的并行进程进行通信,并且需要一个显式的设计风格。基于请求/响应的应用更象一个单进程的应用,因为发送请求的应用或多或少被阻塞直至收到来自另一个进程的响应。这使得请求/响应通信自然地适合于RPC应用。 尽管消息通信和请求/响应各有他们的优点,他们都是可以用对方来实现的。消息系统可以用较底层的请求/响应协议来建立。如微软的Message Queue Server (MSMQ)内部采用了DCE RPC来建立大多数的控制逻辑。RPC系统也可以采用较底层的消息系统来建立。MSMQ提供的关联 ID正是为了这个目的。不管评价如何,大多数的应用仍趋向于使用RPC协议,因为它们广泛的使用,它们更简单的设计,以及更自然的到传统的编程技术的映射。 在八十年代,两个主要的RPC协议是Sun RPC 和DCE RPC。最流行的Sun RPC应用是大多数UNIX系统所使用的Network File System (NFS)。最流行的DCE RPC应用则是Windows NT?,它采用DCE RPC 协议来实现许多系统服务。这两个协议被证明适用于很大范围的应用。但是,在八十年代末期,面向对象技术的风靡使软件界沉迷于在面向对象语言和基于RPC的通信之间建立一个纽带。 在九十年代产生的对象RPC (ORPC) 协议正是试图把面向对象和网络协议联系起来。ORPC 和 RPC 协议的主要不同是ORPC代码化了从通信终端到语言级对象的映射。在每个ORPC请求的头中都有一个cookie,服务器端的程序能用它来定位在服务器进程中的目标对象。通常这个cookie只是一个对数组的索引,但其它技术也经常被使用,如用符号名作为Hash表的键。 图1 ORPC请求与响应 图1表示一个典型的ORPC请求和响应。有几个请求头组件被服务器端的处理程序用于分发调用。对象端点ID被用于定位在服务器进程中目标对象。接口标识符和方法标识符用于决定在目标对象中哪一个方法被调用。传输体用于传递请求中的[in]和[in,out]参数的值(在响应中是[out]和[in,out])。要注意的是任选的协议扩展可以出现在头文件和传输体之间。这是在协议设计中的惯例,因为它允许新的服务搭载在ORPC的请求和服务上。大多数ORPC系统用这个区域传递附加的上下文信息(如事务信息和因果关系标识符)。 目前两个主要的OPRC协议是DCOM 和 CORBA的 Internet Inter-ORB Protocol (IIOP) 或更一般的General Inter-ORB Protocol (GIOP)。DCOM和IIOP/GIOP的请求格式非常相似。两个协议都用一个对象端点ID来确定目标对象,用方法标识符来决定调用哪个方法。 这两个协议主要有两点不同:主要的一点不同是采用IIOP/GIOP时,接口标识符是隐含的,因为一个给定的CORBA对象只实现一个接口(尽管OMG当前正在进行每个对象有多个接口支持的标准化工作)。DCOM与IIOP/GIOP请求的另一个细微差别是在传输体中参数值的格式。在DCOM中,传输体用网络数据表达(NDR)的格式来写,在IIOP/GIOP中,传输体用公共数据表达(CDR)的格式来写。NDR和 CDR分别处理在各种平台上的不同的数据表达。但是在这两种格式之间有一些小的差别,这使它们相互之间并不兼容。 在ORPC与RPC协议之间的另一个重要的不同是通信端点的命名方式。在ORPC协议中,对于ORPC端点的一些可传递的表达方式被要求在网络之间传递对象引用。在CORBA/IIOP,这个表达方式被称为可交互的对象引用(IOR)。IORs包含用紧凑格式表达的寻址信息,使用了它任何CORBA产品都可以决定一个对象端点。在DCOM中,这种表达方式被称为OBJREF,它组合了分布的引用计算和端点/对象标识。CORBA和DCOM都提供了在网络上寻找对象端点的高级机制,但最终这些机制都映射回到了IORs或OBJREFs。图3是表示一个 IOR/OBJREF 怎样与在IIOP/DCOM请求消息中的寻址信息关联起来的。 目前的技术存在的问题? 尽管DCOM和IIOP都是固定的协议,业界还没有完全转向其中任何一个协议。没有融合的部分原因是文化的问题所致。而且在当一些组织试图标准化一个或另一个协议的时候,两个协议的技术适用性就被提出质疑。传统上认为DCOM和CORBA都是合理服务器到服务器端的通信协议。但是,二者对客户到服务器端的通信都存在明显的弱点,尤其是客户机被散布在Internet上的时候。 DCOM 和 CORBA/IIOP都是依赖于单个厂商的解决方案来最大优势地使用协议。尽管两个协议都在各种平台和产品上被实现了,但现实是选定的发布需要采用单一厂商的实现。在DCOM的情况下,这意味着每个机器要运行在Windows NT。(尽管DCOM已经被转移到其它平台,但它只在Windows?上获得了广泛的延伸)。在CORBA情况下,这意味着每个机器要运行同样的ORB产品。的确让两个CORBA产品用IIOP相互调用是有可能的,但是许多高级的服务(如安全和事务)此时通常不是可交互的。而且,任何专门厂商为同样的机器的通信所作的优化很难起作用,除非所有的应用被建立在同一个ORB产品上。 DCOM 和CORBA/IIOP都依赖于周密管理的环境。两个任意的计算机使得DCOM或IIOP 在环境之外被成功调用(calls out of the box)的几率是很低的。特别是在考虑安全性的时候尤其是这样。尽管写一个能成功地运用DCOM或IIOP的紧缩包(shrink-wrap)应用是可能的,但这样做要比基于socket的应用要更多地关注细节。这对于乏味但必需的配置和安装管理任务特别适用。 DCOM 和 CORBA/IIOP都依赖于相当高技术的运行环境。尽管进程内的COM似乎特别简单,但COM/DCOM远程处理程序绝对不只是几天就解决的事情。IIOP 是一个比DCOM更容易实现的协议,但两个协议都有相当多的深奥的规则来处理数据排列、类型信息和位操作。这使得一般的程序员在没有领会ORB产品或OLE32.DLL的情况下去构造一个简单的CORBA或DCOM调用也变得很困难。 也许对DCOM和CORBA/IIOP来说,最令人难以忍受的一点是它们不能在Internet 上发挥作用。对DCOM来说,一般用户的iMac 或廉价的运行Windows 95的PC 兼容机要想使用你的服务器执行基于领域认证几乎是不可能的。更糟的是,如果防火墙或代理服务器分隔开了客户和服务器的机器,任何IIOP或DCOM包要通过的可能性是很低的,主要是由于大多数Internet连接技术对HTTP协议的偏爱所致。尽管一些厂商如Microsoft, Iona和Visigenic都已经建立了通道技术,但这些产品很容易对配置错误敏感而且它们是不可交互的。 在一个服务器群落中这些问题并不能影响DCOM或IIOP的使用。因为在服务器群落中主机的数量很少(一般是成百上千,而不是成千上万),这就抵消了DCOM基于ping的生命周期管理的成本。在服务器群落中,所有主机被一个公共管理域管理的机率很大,使得统一的配置变得可能。相对少量的机器也能保持商业ORB产品可控制使用的成本,因为只需要更少量的ORB许可权。如果只有IIOP在服务器群落中被使用,就只需要少量的ORB许可权。最后,在服务器群落中所有主机有直接的IP连接也是可能的,这就消除了与防火墙相关的DCOM和 IIOP问题。   HTTP作为一个更好的RPC 在服务器群落中使用DCOM 和CORBA 是通用的做法,但客户机则使用HTTP进入服务器群落。HTTP与RPC的协议很相似,它简单、配置广泛,并且对防火墙比其它协议更容易发挥作用。HTTP请求一般由Web服务器软件(如IIS和Apache)来处理,但越来越多的应用服务器产品正在支持HTTP作为除DCOM和IIOP外的又一个协议。 象DCOM和IIOP一样,HTTP层通过TCP/IP进行请求/响应通信。一个HTTP的客户端用TCP连接到HTTP服务器。在HTTP中使用的标准端口号是80,但任何其它端口也能被使用。在建立TCP连接后,客户端可以发送一个请求消息到服务器端。服务器在处理请求后发回一个HTTP响应消息到客户端。请求和响应消息都可以包含任意的传输体的信息,通常用Content-Length和Content-Type的 HTTP 头来标记。下面是一个合法的HTTP请求消息: POST /foobar HTTP/1.1 Host: 209.110.197.12 Content-Type: text/plain Content-Length: 12 Hello, World 你可能已经注意到HTTP头只是一般文本。这使得用包检查程序或基于文本的Internet工具(如telnet)来诊断HTTP问题变得更容易。HTTP基于文本的属性也使得HTTP更容易适用于在Web开发中流行的低技术水平的编程环境。 HTTP请求的第一行包含三个组件:HTTP方法,请求-URI,协议版本。在前面的例子中,这些分别对应于POST, /foobar, 和 HTTP/1.1。Internet工程任务组(IETF)已经标准化了数量固定的HTTP方法。GET是HTTP用来访问Web的方法。 POST是建立应用程序的最常用的HTTP方法。和GET不一样,POST允许任意数据从客户端发送到服务器端。请求URI (Uniform Resource Identifier)是一个HTTP服务器端软件,它用来识别请求的目标的简单的标识符(它更象一个IIOP/GIOP object_key 或一个DCOM IPID)。关于URIs更多的信息请参照"URIs, URLs, and URNs"。在这个例子中协议的版本是HTTP/1.1, 它表示遵守RFC 2616的规则。HTTP/1.1比HTTP/1.0多增加了几个特性,包括对大块数据传输的支持以及对在几个HTTP请求之间保持TCP连接的支持。 请求的第三行和第四行指定了请求体的尺寸和类型。Content-Length 头指定了体信息的比特数。Content-Type类型标识符指定MIME类型为体信息的语法。HTTP (象 DCE一样) 允许服务器和客户端协商用于编制信息的传输语法。大多数DCE应用采用NDR.。大多数Web应用采用text/html 或其它基于文本的语法。 注意在上面样例中Content-Length头与请求体之间的空行。不同的HTTP头被carriage-return/行码序列划定界限。这些头与体之间用另外的carriage-return/行码序列来划定界限。请求接着包括原始字节,这些字节的语法和长度由Content-Length和Content-Type HTTP 头来识别。在这个例子中,内容是十二字节的普通文本字符串"Hello, World"。 在处理了请求之后,HTTP服务器被期望发回一个HTTP响应到客户端。响应必须包括一个状态代码来表示请求的结果。响应也可以包含任意的体信息。下面是一个HTTP响应消息: 200 OK Content-Type: text/plain Content-Length: 12 dlroW ,olleH 在这个例子中,服务器返回状态代码200,它是HTTP中标准的成功代码。如果服务器端不能破解请求代码,它将返回下列的响应: 400 Bad Request Content-Length: 0 如果HTTP服务器决定到目标URI的请求应该临时转向另外的一个不同的URI,下列响将被返回: 307 Temporarily Moved Location: http://209.110.197.44/foobar Content-Length: 0 这个响应告知客户,请求将能够通过重新传递它到在Location头中指定的地址来被满足。 所有的标准状态码和头都在RFC 2616中被描述。它们中很少的内容与SOAP用户直接相关,但有一个明显的例外。在HTTP/1.1,底层的TCP连接在多个请求/响应对之间重用。HTTP Connection头允许客户端或服务器中任何一方关闭底层的连接。通过增加下列HTTP头到请求或响应中,双方都会要求在处理请求后关闭它们的TCP连接: Connection: close 当与HTTP/1.0软件交互时,为了保持TCP连接,建议发送方加入下列HTTP头到每个请求或响应中: Connection: Keep-Alive 这个头使缺省的HTTP/1.0协议在每次响应后重新开始TCP连接的行为无法使用。 HTTP的一个优点是它正被广泛的使用和接受。图4表示了一个简单的Java程序,它发送前面表示的请求并从响应中解析出结果字符串。 下面则是一个简单的C程序用CGI来读取来自HTTP请求的字符串并通过HTTP响应把它的逆序串返回。 #include <stdio.h> int main(int argc, char **argv) { char buf[4096]; int cb = read(0, buf, sizeof(buf)); buf[cb] = 0; strrev(buf); printf("200 OK\r\n");p> printf("Content-Type: text/plain\r\n"); printf("Content-Length: %d\r\n", cb); printf("\r\n"); printf(buf); return 0; 图5表示了一个更流行的版本,服务器的实现是用Java servlet,以避免CGI的每个请求一个进程的开销。 一般来说CGI是花费代价最小的写HTTP服务器端代码的方法。实际上,每一个HTTP服务器端产品都提供了一个更有效的机制来让你的代码处理一个HTTP请求。IIS提供了ASP和ISAPI作为写HTTP代码的机制。Apache允许你用运行在Apache后台程序中的 C或Perl来写模块。大多数应用服务器软件允许你写Java servlet,COM组件,EJB Session beans或基于可携带对象适配器(POA)接口的CORBA servants。 XML 作为一个更好的网络数据表达方式(NDR) HTTP是一个相当有用的RPC协议,它提供了IIOP或DCOM在组帧、连接管理以及序列化对象应用等方面大部分功能的支持。( 而且URLs与IORs和OBJREFs在功能上令人惊叹的接近)。HTTP所缺少的是用单一的标准格式来表达一个RPC调用中的参数。这则正是XML的用武之地。 象NDR和CDR,XML是一个与平台无关的中性的数据表达协议。XML允许数据被序列化成一个可以传递的形式,使得它容易地在任何平台上被解码。XML有以下不同于NDR和CDR的特点: 有大量XML编码和解码软件存在于每个编程环境和平台上 XML基于文本,相当容易用低技术水平的编程环境来处理 XML是特别灵活的格式,它容易用一致的方式来被扩展 为支持可扩展性,在XML中每一个元素和属性有一个名域URI与它相联系,这个URI用xmlns属性来指定。 考虑下面的XML文档: <reverse_string xmlns="urn:schemas-develop-com:StringProcs"> <string1>Hello, World</string1> <comment xmlns=‘http://foo.com/documentation‘> This is a comment!! </comment> </reverse_string> 元素<reverse_string>和<string1>的名域URI是urn:schemas-develop-com:StringProcs。元素<comment>的名域URI是http://foo.com/documentation。第二个URI也是一个URL的事实是不重要的。在这两种情况下,URI简单地被用来消除元素<reverse_string>,<string1>,<comment>和任何碰巧有同样标记名的其它元素间的歧义。 为了方便,XML允许名域URIs被映射为局部唯一的前缀。这意味着下面的XML文档在语义上等同于上面的文档: <sp:reverse_string xmlns:sp="urn:schemas-develop-com:StringProcs" xmlns:doc=‘http://foo.com/documentation‘ > <sp:string1>Hello, World</sp:string1> <doc:comment> This is a comment!! </doc:comment> </sp:reverse_string> 后面的形式对作者来说更容易,尤其是如果有许多名域URIs在使用时。 XML也支持带类型的数据表达。正在推出的XML Schema规范为描述XML数据类型标准化了一个词汇集。下面是一个元素<reverse_string>的XML Schema的描述: <schema xmlns=‘http://www.w3.org/1999/XMLSchema‘ targetNamespace=‘urn:schemas-develop-com:StringProcs‘ > <element name=‘reverse_string‘> <type> <element name=‘string1‘ type=‘string‘ /> <any minOccurs=‘0‘ maxOccurs=‘*‘/> </type> </element> </schema> 这个XML Schema定义阐述了XML名域urn:schemas-develop-com:StringProcs包含了一个名为<reverse_string>的元素,这个元素包含了一个名为string1的子元素(类型为string),它被0个或更多没有指定的元素所遵守。 XML Schema 规范还定义了一组内置的原始数据类型和建立一个XML文档中元素的类型的机制。下面的XML文档用XML Schema类型属性来把元素和类型名联系在一起: <customer xmlns=‘http://customer.is.king.com‘ xmlns:xsd=‘http://www.w3.org/1999/XMLSchema‘ > <name xsd:type=‘string‘>Don Box</name> <age xsd:type=‘float‘>23.5</name> </customer> 连接XML文档事例到XML Schema描述的新的一个机制在本文写作的时候正在标准化过程中。 HTTP + XML = SOAP SOAP把XML的使用代码化为请求和响应参数编码模式,并用HTTP作传输。这似乎有点抽象。具体地讲,一个SOAP方法可以简单地看作遵循SOAP编码规则的HTTP请求和响应。一个SOAP终端则可以看作一个基于HTTP的URL,它用来识别方法调用的目标。象CORBA/IIOP一样,SOAP不需要具体的对象被绑定到一个给定的终端,而是由具体实现程序来决定怎样把对象终端标识符映射到服务器端的对象。 SOAP请求是一个HTTP POST请求。SOAP请求的content-type必须用text/xml。而且它必须包含一个请求-URI。服务器怎样解释这个请求-URI是与实现相关的,但是许多实现中可能用它来映射到一个类或者一个对象。一个SOAP请求也必须用SOAPMethodName HTTP头来指明将被调用的方法。简单地讲,SOAPMethodName头是被URI指定范围的应用相关的方法名,它是用#符作为分隔符将方法名与URI分割开: SOAPMethodName: urn:strings-com:IString#reverse 这个头表明方法名是reverse,范围URI是urn:strings-com:Istring。 在SOAP中,规定方法名范围的名域URI在功能上等同于在DCOM 或 IIOP中规定方法名范围的接口ID。 简单的说,一个SOAP请求的HTTP体是一个XML文档,它包含方法中[in]和[in,out]参数的值。这些值被编码成为一个显著的调用元素的子元素,这个调用元素具有SOAPMethodName HTTP头的方法名和名域URI。调用元素必须出现在标准的SOAP <Envelope>和<Body>元素内(后面会更多讨论这两个元素)。下面是一个最简单的SOAP方法请求: POST /string_server/Object17 HTTP/1.1 Host: 209.110.197.2 Content-Type: text/xml Content-Length: 152 SOAPMethodName: urn:strings-com:IString#reverse <Envelope> <Body> <m:reverse xmlns:m=‘urn:strings-com:IString‘> <theString>Hello, World</theString> </m:reverse> </Body> </Envelope> SOAPMethodName头必须与<Body>下的第一个子元素相匹配,否则调用将被拒绝。这允许防火墙管理员在不解析XML的情况下有效地过滤对一个具体方法的调用。 SOAP响应的格式类似于请求格式。响应体包含方法的[out]和 [in,out]参数,这个方法被编码为一个显著的响应元素的子元素。这个元素的名字与请求的调用元素的名字相同,但以Response后缀来连接。下面是对前面的SOAP请求的SOAP响应: 200 OK Content-Type: text/xml Content-Length: 162 <Envelope> <Body> <m:reverseResponse xmlns:m=‘urn:strings-com:IString‘> <result>dlroW ,olleH</result> </m:reverseResponse> </Body> </Envelope> 这里响应元素被命名为reverseResponse,它是方法名紧跟Response后缀。要注意的是这里是没有SOAPMethodName HTTP头的。这个头只在请求消息中需要,在响应消息中并不需要。 图6和图7表明SOAP是怎样与以前讨论的ORPC协议相互对应的。让许多SOAP新手困惑的是SOAP中没有关于SOAP服务器怎样使用请求头来分发请求的要求;这被留为一个实现上的细节。一些SOAP服务器将映射请求-URIs到类名,并分派调用到静态方法或到在请求持续期内存活的类的实例。其它SOAP服务器则将请求-URIs映射到始终存活的对象,经常是用查询字符串来编码一个用来定位在服务器进程中的对象关键字。还有一些其它的SOAP服务器用HTTP cookies来编码一个对象关键字,这个关键字可被用来在每次方法请求中恢复对象的状态。重要的是客户对这些区别并不知道。客户软件只是简单遵循HTTP和XML的规则来形成SOAP请求,让服务器自由以它认为最合适的方式来为请求服务。 SOAP体的核心 SOAP的XML特性是为把数据类型的实例序列化成XML的编码模式。为了达到这个目的,SOAP不要求使用传统的RPC风格的代理。而是一个SOAP方法调用包含至少两个数据类型:请求和响应。考虑这下面个COM IDL代码: [ uuid(DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA) ] interface IBank : IUnknown { HRESULT withdraw([in] long account, [out] float *newBalance, [in, out] float *amount [out, retval] VARIANT_BOOL *overdrawn); } 在任何RPC协议下,account和amount参数的值将出现在请求消息中,newBalance,overdrawn参数的值,还有amount参数的更新值将出现在响应消息中。 SOAP把方法请求和方法响应提升到了一流状态。在SOAP中,请求和响应实际上类型的实例。为了理解一个方法比如IBank::withdraw怎样映射一个SOAP请求和响应类型,考虑下列的数据类型: struct withdraw { long account; float amount; };   这是一个所有的请求参数被打包成为一个单一的数据类型。同样下面的数据表示打包所有响应参数到一个单一的数据类型。 struct withdrawResponse { float newBalance; float amount; VARIANT_BOOL overdrawn; }; 再给出下面的简单的Visual Basic程序,它使用了以前定义的Ibank接口: Dim bank as IBank Dim amount as Single Dim newBal as Single Dim overdrawn as Boolean amount = 100 Set bank = GetObject("soap:http://bofsoap.com/am") overdrawn = bank.withdraw(3512, amount, newBal) 你能够想象底层的代理(可能是一个SOAP,DCOM,或IIOP代理)看上去象图8中所表示的那样。这里,在发送请求消息之前,参数被序列化成为一个请求对象。同样被响应消息接收到的响应对象被反序列化为参数。一个类似的转变同样发生在调用的服务器端。 当通过SOAP调用方法时,请求对象和响应对象被序列化成一种已知的格式。每个SOAP体是一个XML文档,它具有一个显著的称为<Envelope>的根元素。标记名<Envelope>由SOAP URI (urn:schemas-xmlsoap-org:soap.v1)来划定范围,所有SOAP专用的元素和属性都是由这个URI来划定范围的。SOAP Envelope包含一个可选的<Header>元素,紧跟一个必须的<Body>元素。<Body>元素也有一个显著的根元素,它或者是一个请求对象或者是一个响应对象。下面是一个IBank::withdraw请求的编码: <soap:Envelope xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘> <soap:Body> <IBank:withdraw xmlns:IBank= ‘urn:uuid:DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA‘> <account>3512</account> <amount>100</amount> </IBank:withdraw> </soap:Body> </soap:Envelope>   下列响应消息被编码为: <soap:Envelope xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘> <soap:Body> <IBank:withdrawResponse xmlns:IBank= ‘urn:uuid:DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA‘> <newBalance>0</newBalance> <amount>5</amount> <overdrawn>true</overdrawn> </IBank:withdrawResponse> </soap:Body> </soap:Envelope> 注意[in, out]参数出现在两个消息中。   在检查了请求和响应对象的格式后,你可能已经注意到序列化格式通常是: <t:typename xmlns:t=‘namespaceuri‘> ; <fieldname1>field1value</fieldname1> <fieldname2>field2value</fieldname2> </t:typename> 在请求的情况下,类型是隐式的C风格的结构,它由对应方法中的[in]和[in, out]参数组成。对响应来说,类型也是隐式的C风格的结构,它由对应方法中的[out]和[in, out]参数组成。这种每个域对应一个子元素的风格有时被称为元素正规格式(ENF)。一般情况下,SOAP只用XML特性来传达描述包含在元素内容中信息的注释。 象DCOM和IIOP一样,SOAP支持协议头扩展。SOAP用可选的<Header>元素来传载被协议扩展所使用的信息。如果客户端的SOAP软件包含要发送头信息,原始的请求将可能如图9所示。在这种情况下命名causality的头将与请求一起序列化。收到请求后,服务器端软件能查看头的名域URI,并处理它识别出的头扩展。这个头扩展被http://comstuff.com URI识别,并期待一个如下的对象: struct causality { UUID id; }; 在这种情况下的请求,如果头元素的URI不能被识别,头元素可以被安全地忽略。 但你不能安全的忽略所有的SOAP体中的头元素。如果一个特定的SOAP头对正确处理消息是很关键的,这个头元素能被用SOAP属性mustUnderstand=’true’标记为必须的。这个属性告诉接收者头元素必须被识别并被处理以确保正确的使用。为了强迫前面causality头成为一个必须的头,消息将被写成如下形式: <soap:Envelope xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘> <soap:Header> <causality soap:mustUnderstand=‘true‘ xmlns="http://comstuff.com"> <id>362099cc-aa46-bae2-5110-99aac9823bff</id> </causality> </soap:Header> <!— soap:Body element elided for clarity —> </soap:Envelope> SOAP软件遇到不能识别必须的头元素情况时,必须拒绝这个消息并出示一个错误。如果服务器在一个SOAP请求中发现一个不能识别的必须的头元素,它必须返回一个错误响应并且不发送任何调用到目标对象。如果客户端在一个SOAP请求中发现一个不能识别出的必须的头元素,它必须向调用者返回一个运行时错误。(在COM情况下,这将映射为一个明显的HRESULT) SOAP数据类型 在SOAP消息中,每个元素可能是一个SOAP结构元素,一个根元素,一个存取元素或一个独立的元素。在SOAP中,soap:Envelope, soap:Body和 soap:Header 是唯一的三个结构元素。它们的基本关系由下列XML Schema所描述: <schema targetNamespace=‘urn:schemas-xmlsoap-org:soap.v1‘> <element name=‘Envelope‘> <type> <element name=‘Header‘ type=‘Header‘ minOccurs=‘0‘ /> <element name=‘Body‘ type=‘Body‘ minOccurs=‘1‘ /> </type> </element> </schema> 在SOAP元素的四种类型中,除了结构元素外都被用作表达类型的实例或对一个类型实例的引用。 根元素是显著的元素,它是soap:Body 或是 soap:Header的直接的子元素。其中soap: Body只有一个根元素,它表达调用、响应或错误对象。这个根元素必须是soap:Body的第一个子元素,它的标记名和域名URI必须与HTTP SOAPMethodName头或在错误消息情况下的soap:Fault相对应。而soap:Header元素有多个根元素,与消息相联系的每个头扩展对应一个。这些根元素必须是soap:Header的直接子元素,它们的标记名和名域URI表示当前存在扩展数据的类型。 存取元素被用作表达类型的域、属性或数据成员。一个给定类型的域在它的SOAP表达将只有一个存取元素。存取元素的标记名对应于类型的域名。考虑下列Java 类定义: package com.bofsoap.IBank; public class adjustment { public int account ; public float amount ; } 在一个SOAP消息中被序列化的实例如下所示: <t:adjustment xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘> <account>3514</account> <amount>100.0</amount> </t:adjustment> 在这个例子中,存取元素account和amount被称着简单存取元素,因为它们访问对应于在W3C XML Schema规范 (见 http://www.w3.org/TR/XMLSchema-2) 的Part 2中定义的原始数据类型的值。这个规范指定了字符串,数值,日期等数据类型的名字和表达方式以及使用一个新的模式定义中的<datatype>结构来定义新的原始类型的机制。 对引用简单类型的存取元素,元素值被简单地编码为直接在存取元素下的字符数据,如上所示。对引用组合类型的存取元素(就是那些自身用子存取元素来构造的存取元素),有两个技术来对存取元素进行编码。最简单的方法是把被结构化的值直接嵌入在存取元素下。考虑下面的Java类定义: package com.bofsoap.IBank; public class transfer { public adjustment from; public adjustment to; } 如果用嵌入值编码存取元素,在SOAP中一个序列化的transfer对象如下所示: <t:transfer xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘ > <from> <account>3514</account> <amount>-100.0</amount> </from> <to> <account>3518</account> <amount>100.0</amount> </to> </t:transfer> 在这种情况下,adjustment对象的值被直接编码在它们的存取元素下。 在考虑组合存取元素时,需要说明几个问题。先考虑上面的transfer类。类的from和to的域是对象引用,它可能为空。SOAP用XML Schemas的null属性来表示空值或引用。下面例子表示一个序列化的transfer对象,它的from域是空的: <t:transfer xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘ xmlns:xsd=‘http://www.w3.org/1999/XMLSchema/instance‘ > <from xsd:null=‘true‘ /> <to> <account>3518</account> <amount>100.0</amount> </to> </t:transfer> 在不存在的情况下, xsd:null属性的隐含值是false。给定元素的能否为空的属性是由XML Schema定义来控制的。例如下列XML Schema将只允许from存取元素为空: <type name=‘transfer‘ > <element name=‘from‘ type=‘adjustment‘ nullable=‘true‘ /> <element name=‘to‘ type=‘adjustment‘ nullable=‘false‘ <!— false is the default —> /> </type> 在一个元素的Schema声明中如果没有nullable属性,就意味着在一个XML文档中的元素是不能为空的。Null存取元素的精确格式当前还在修订中�要了解用更多信息参考最新版本的SOAP规范。 与存取元素相关的另一个问题是由于类型关系引起的可代换性。由于前面的adjustment类不是一个final类型的类,transfer对象的from和to域实际引用继承类型的实例是可能的。为了支持这种类型兼容的替换,SOAP使用一个名域限定的类型属性的XML Schema约定。这种类型属性的值是一个对元素具体的类型的限制的名字。考虑下面的adjustment扩展类: package com.bofsoap.IBank; public class auditedadjustment extends adjustment { public int auditlevel; } 给出下面Java语言: transfer xfer = new transfer(); xfer.from = new auditedadjustment(); xfer.from.account = 3514; xfer.from.amount = -100; xfer.from.auditlevel = 3; xfer.to = new adjustment(); xfer.to.account = 3518; xfer.from.amount = 100; 在SOAP中transfer对象的序列化形式如下所示: <t:transfer xmlns:xsd=‘http://www.w3.org/1999/XMLSchema‘ xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘ > <from xsd:type=‘t:auditedadjustment‘ > <account>3514</account> <amount>-100.0</amount> <auditlevel>3</auditlevel > </from> <to> <account>3518</account> <amount>100.0</amount> </to> </t:transfer> 在这里xsd:type属性引用一个名域限定的类型名,它能被反序列化程序用于实例化对象的正确类型。因为to存取元素引用到一个被预料的类型的实例(而不是一个可代替的继承类型),xsd:type属性是不需要的。 刚才的transfer类设法回避了一个关键问题。如果正被序列化的transfer对象用下面这种方式初始化将会发生什么情况: transfer xfer = new transfer(); xfer.from = new adjustment(); xfer.from.account = 3514; xfer.from.amount = -100; xfer.to = xfer.from; 基于以前的议论,在SOAP 中transfer对象的序列化形式如下所示: <t:transfer xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘> <from> <account>3514</account> <amount>-100.0</amount> </from> <to> <account>3514</account> <amount>-100.0</amount> </to> </t:transfer> 这个表达有两个问题。首先最容易理解的问题是同样的信息被发送了两次,这导致了一个比实际所需要消息的更大的消息。一个更微妙的但是更重要的问题是由于反序列化程序不能分辨两个带有同样值的adjustment对象与在两个地方被引用的一个单一的adjustment对象的区别,两个存取元素间的身份关系就被丢失。如果这个消息接收者已经在结果对象上执行了下面的测试,(xfer.to == xfer.from)将不会返回true。 void processTransfer(transfer xfer) { if (xfer.to == xfer.from) handleDoubleAdjustment(xfer.to); else handleAdjustments(xfer.to, xfer.from); } (xfer.to.equals(xfer.from))可能返回true的事实只是比较了两个存取元素的值而不是它们身份。 为了支持必须保持身份关系的类型的序列化,SOAP支持多引用存取元素。目前我们接触到的存取元素是单引用存取元素,也就是说,元素值是嵌入在存取元素下面的,而且其它存取元素被允许引用那个值(这很类似于在NDR中的[unique]的概念)。多引用存取元素总是被编码为只包含已知的soap:href属性的空元素。soap:href属性总是包含一个代码片段标识符,它对应于存取元素引用到的实例。如果to和from存取元素已经被编码为多引用存取元素,序列化的transfer对象如下所示: <t:transfer xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘> <from soap:href=‘#id1‘ /> <to soap:href=‘#id1‘ /> </t:transfer> 这个编码假设与adjustment类兼容的一个类型的实例已经在envelope中的其它地方被序列化,而且这个实例已经被用soap:id属性标记,如下所示: <t:adjustment soap:id=‘id1‘ xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘> <account>3514</account> <amount>-100.0</amount> </t:adjustment> 对多引用存取元素,把代码段的标识符(例如#id1)分解到正确的实例是反序列化程序的工作。 前面的讨论解释了多引用存取元素怎样与它的目标实例相关联。下面要讨论的是目标实例在哪里被序列化。这就关系到独立元素和包的概念。 独立元素 在SOAP中,一个独立元素表示至少被一个多引用存取元素引用的类型的实例。所有的独立元素用soap:id属性作标记,而且这个属性的值在整个SOAP envelope中必须是唯一的。独立的元素被编码就好象是它们被一个存取元素打包,这个存取元素的标记名是实例的名域限制的类型名。在上面的例子中,实例的名域限制的类型名是t:adjustment。 SOAP限制独立元素能被编码的场所。SOAP定义了一个能适用于任何元素的属性:(soap:Package)。这个属性被用于控制独立元素能在哪里被解码。SOAP序列化规则指出独立元素必须编码为soap:Header元素或soap:Body元素的直接子元素,或者是任何其它标记为soap:Package=‘true’的元素。通过把一个元素注释为包,你能保证编码那个实例的XML元素是完全自包含的,并且在这个包以外没有任何引用到这个元素的多引用存取元素。 假设transfer 类对应于一个方法请求。如果transfer类型不是一个包,被to和from存取元素引用的独立元素将作为soap:Body元素的直接子元素出现,如图10所示。如果transfer类型是一个合法的SOAP包类型,编码可能象图11所示。注意,因为transfer元素是一个包,所有多引用存取器元素都引用被包含的元素。这使得把transfer元素看成一个能从它的父辈元素中分离出的独立的XML代码段变得更为容易。 多引用存取元素总是引用独立元素的模型是有一个例外的。SOAP允许包含字符串和二进值数据的存取元素是多引用存取元素的目标。这意味着下面的代码是合法的: <t:mytype> <field1 soap:href="#id1" /> <field2 soap:id="id1">Hello, SOAP</field2> </t:mytype> 尽管事实是存取元素2有一个soap:id属性,它实际上是一个存取元素而不是独立元素。 SOAP数组 数组被编码为组合类型的一个特殊的例子。在SOAP中,一个数组必须有一个秩(维数)和一个容量。一个数组被编码为一个组合类型,其中每一个数组元素被编码为一个子元素,这个子元素的名字是元素的名域限制的类型名。 假设有下面的COM IDL类型定义: struct POINTLIST { long cElems; [size_is(cElems)] POINT points[]; };   这个类型的实例将被序列化为: <t:POINTLIST xmlns:t=‘uri for POINTLIST‘> <cElems>3</cElems> <points xsd:type=‘t:POINT[3]‘ > <POINT>lt;x>3</x>lt;y>4</y>lt;/POINT> <POINT>lt;x>7</x>lt;y>5</y>lt;/POINT> <POINT>lt;x>1</x>lt;y>9</y>lt;/POINT> </points> <t:POINTLIST> 如果points域被标记为[ptr]属性,这个编码将用一个多引用存取元素,如下所示:   <t:POINTLIST xmlns:t=‘uri for POINTLIST‘> <cElems>3</cElems> <points soap:href="#x9" /> </t:POINTLIST> <t:ArrayOfPOINT soap:id=‘x9‘ xsd:type=‘t:POINT[3]‘> <POINT>lt;x>3</x>lt;y>4</y>lt;/POINT> <POINT>lt;x>7</x>lt;y>5</y>lt;/POINT> <POINT>lt;x>1</x>lt;y>9</y>lt;/POINT> </t:ArrayOfPOINT> 当把一个数组编码为一个独立元素时,标记名是带前缀ArrayOf的类型名。 象NDR和CDR一样,SOAP支持部分转换的数组。如果子元素的数量少于所声明的容量,这些元素被假设正从数组的末尾丢失。这能够通过在正包含的数组元素上使用soap:offset属性来被忽略。 <t:ArrayOfPOINT soap:id=‘x9‘ xsd:type=‘t:POINT[5]‘ soap:offset=‘[1]‘> <POINT>lt;x>1</x>lt;y>9</y>lt;/POINT> </t:ArrayOfPOINT> soap:offset属性表示出现在数组中的第一个元素的索引。在上面的例子中,元素0,2到4都是不被转换的。SOAP也支持稀疏数组,这是通过使用soap:position属性来把每个元素用它的绝对索引来注释而实现的: <t:ArrayOfPOINT soap:id=‘x9‘ xsd:type=‘t:POINT[9]‘> <POINT soap:position=‘[3]‘>lt;x>3</x>lt;y>4</y>lt;/POINT> <POINT soap:position=‘[7]‘>lt;x>4</x>lt;y>5</y>lt;/POINT> </t:ArrayOfPOINT> 在这个例子中,元素0到2,4到6,以及8到9都不是被转换的。 请注意,在SOAP中数组的精确语法在这篇文章写作时还在被重新审查以调整到即将推出的W3C XML Schema规范中。要不断了解SOAP规范的最新版本来获得更多的细节。 错误处理 一个服务器有时将不能正确地为一个方法请求提供服务。这可能是由于一般的HTTP错误造成的(如请求-URI不能被映射到本地的资源或一个HTTP级的安全违反)。也可能是在SOAP翻译软件中的问题,如马歇尔打包错误或一个必须的头不能被认出。其它可能的原因包括一个请求不能正确地被服务,或者应用/对象代码决定要返回一个应用级的错误给调用者。这些情况在SOAP规范中都被清楚地加以处理。 如果在分发对任何SOAP代码的调用之前一个错误发生在HTTP层,一个纯HTTP响应必须被返回。标准的HTTP状态代码编号将被采用,400级的代码表示一个客户引发的错误,500级的代码表示服务器引发的错误。这通常在代码执行前由Web服务器软件自动处理。 假设在HTTP层一切正常,错误发生的下一个地方是在那些翻译和分发对应用代码(如COM对象和CORBA伺服对象)的SOAP调用。如果错误发生在这一层,服务器必须返回一个错误消息来代替一个标准的响应消息。一个错误消息是下列被编码为soap:Body的根元素的类型的实例。 <schema targetNamespace=‘urn:schemas-xmlsoap-org:soap.v1‘ > <element name=‘Fault‘> <type> <element name=‘faultcode‘ type=‘string‘ /> <element name=‘faultstring‘ type=‘string‘ /> <element name=‘runcode‘ type=‘string‘ /> <element name=‘detail‘ /> </type> </element>   </schema> faultcode存取元素必须包含一个用已知的整数表示的SOAP错误代码或者一个专门应用的名域限制的值。当前的SOAP 错误代码如图12所示。Faultstring存取元素包含对发生的错误的可读性的描述。runcode 存取元素包含一个字符串,它的值必须是Yes, No或 Maybe,表明被请求的操作实际上是否在错误产生之前被执行。Detail存取元素是可选的,用于包含一个专门应用的异常对象。 下面是一个对应于一个包含无法识别的必须的头元素的请求的SOAP错误的例子: <soap:Envelope xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘ > <soap:Body> <soap:Fault> ; <faultcode>200</faultcode> <faultstring> Unrecognized ‘causality‘ header </faultstring> <runcode>No</runcode> </soap:Fault> </soap:Body> </soap:Envelope> 假设具体应用的错误需要被返回,你可能看到如图13所示的代码。在应用定义的错误的情况下,考虑应用的异常/错误对象时detail存取元素起到了soap:Body 元素的作用。 奥秘 一个遗留的HTTP问题还需要进一步阐明。SOAP支持(但不需要)HTTP扩展框架约定来指定必须的HTTP头扩展。这些约定主要有两个目的。首先,它们允许任意的URI被用于限定给定的HTTP头的范围(象XML名域一样)。第二,这些约定允许把必须的头与可选的头区分开来(象soap:mustUnderstand)。下面是一个使用HTTP扩展框架来把SOAPMethodName头定义成为一个必须的头扩展: M-POST /foobar HTTP/1.1 Host: 209.110.197.2 Man: "urn:schemas-xmlsoap-org:soap.v1; ns=42" 42-SOAPMethodName: urn:bobnsid:IFoo#DoIt Man头映射SOAP URI到前缀为42的头,并表示没有认出SOAP的服务器必须返回一个HTTP错误,状态代码为501 (没有被实现) 或 510 (没有被扩展)。HTTP方法必须是M-POST,表明目前是必须的头扩展。 结论 SOAP是一个被类型化的序列化格式,它恰巧用HTTP 作为请求/响应消息传输协议。SOAP被设计为与正将出现的XML Schema规范密切配合,并支持在Internet的任何地方运行的COM, CORBA, Perl, Tcl, 和 Java-language, C, Python, 或 PHP 等程序间的互操作性。 希望本文给了你一个对这个协议具体细节的更清晰的理解。我鼓励你用SOAP进行实验,或者试着使用SOAP使能的系统之一(列在http://www.develop.com/soap/),或者自己做一些工作。我本人发现采用脚本语言(Jscript),使一个基本的SOAP客户与服务器建立并运行只花费了不到一个小时。针对你对HTTP和XML的熟悉程度,以及你的目标平台的成熟度,你所花费的时间会有所不同。 <淘宝热门商品:
 

149.00 元 

专柜正品八心八箭瑞士钻石1.2克拉恬美吊坠项链

 

288.00 元 

【19shop 超值正品】英国TOPMAN经典修


来源:程序员网

小小豆叮

XML Web Service 基础

摘要:本文概述了 XML Web Service 对于开发人员的价值,同时还介绍了 SOAP、WSDL 和 UDDI。   什么是 XML Web Service?   XML Web Service 是在 Internet 上进行分布式计算的基本构造块。开放的标准以及对用户和应用程序之间的通信和协作的关注产生了这样一种环境,在这种环境下,XML Web Service 成为应用程序集成的平台。应用程序是通过使用多个不同来源的 XML Web Service 构造而成的,这些服务相互协同工作,而不管它们位于何处或者如何实现。   有多少个构建 XML Web Service 的公司,就可能有多少种 XML Web Service 定义。不过几乎所有定义都具有以下共同点:   XML Web Service 通过标准的 Web 协议向 Web 用户提供有用的功能。多数情况下使用 SOAP 协议。   XML Web Service 可以非常详细地说明其接口,这使用户能够创建客户端应用程序与它们进行通信。这种说明通常包含在称为 Web 服务说明语言 (WSDL) 文档的 XML 文档中。   XML Web Service 已经过注册,以便潜在用户能够轻易地找到这些服务,这是通过通用发现、说明和集成 (UDDI) 来完成的。   本文将介绍这三种技术,但首先需要解释一下为什么要关注 XML Web Service。   XML Web Service 体系结构的主要优点之一是:允许在不同平台上、以不同语言编写的各种程序以基于标准的方式相互通信。对这一行业有所了解的用户可能马上会说:“等一等,CORBA 和之前的 DCE 不是都做过相同的承诺吗?这和它们有什么区别?”最重要的区别在于:SOAP 比以前的方法要简单得多,因此要实现与标准兼容的 SOAP,障碍也要少得多。Paul Kulchenko 在 http://www.soapware.org/directory/4/implementations(英文)上提供了一个 SOAP 实现方案的列表。上次统计时,该列表已经包含了 79 项。正如您所预料,多数大的软件公司都提供 SOAP 实现方案,但也有许多实现方案是由个别开发人员创建和维护的。相对以前的方案而言,XML Web Service 的另一大优点是使用标准的 Web 协议 - XML、HTTP 和 TCP/IP。许多公司都已经建立了 Web 基础结构,同时它们的员工在维护方面也都具备相应的知识和经验。因此,引入 XML Web Service 与引入以前的技术相比,其成本要低得多。   我们将 XML Web Service 定义为:通过 SOAP 在 Web 上提供的软件服务,使用 WSDL 文件进行说明,并通过 UDDI 进行注册。那么,您也许要问:“使用 XML Web Service 能够做什么?”最初的 XML Web Service 通常是可以方便地并入应用程序的信息来源,如股票价格、天气预报、体育成绩等等。我们很容易想到,可以构建一整类应用程序以分析和汇总所关心的信息,并以各种方式提供这些信息;例如,您可以使用 Microsoft? Excel 电子表格来汇总所有的财务信息 - 股票、401K、银行存款、贷款等等。如果能够通过 XML Web Service 获得这些信息,Excel 就可以不断对其进行更新。这些信息中有些是免费的,有些则可能需要订阅才能获得相应服务。大部分这种信息现在已经可以在 Web 上找到了,但是 XML Web Service 可以使编程访问更简单,也更可靠。   以 XML Web Service 方式提供现有应用程序,可以构建新的、更强大的应用程序,并利用 XML Web Service 作为构造块。例如,用户可以开发一个采购应用程序,以自动获取来自不同供应商的价格信息,从而使用户可以选择供应商,提交订单,然后跟踪货物的运输,直至收到货物。而供应商的应用程序除了在 Web 上提供服务外,还可以使用 XML Web Service 检查客户的信用、收取货款,并与货运公司办理货运手续。   将来,某些最有趣的 XML Web Service 所支持的应用程序还可以利用 Web 完成目前无法完成的任务。例如,日历服务就是 Microsoft .NET My Services(英文)项目即将支持的服务之一。如果您的牙医和机械师通过这一 XML Web Service 提供其日程安排,您就可以通过网络与他们安排约会;如果您愿意,他们也可以直接在您的日历上约定清洁和日常保养的日期。不难想象,只要能够对 Web 进行编程,您就可以创建数以百计的应用程序。   有关 XML Web Service 及其可以构建的应用程序的详细信息,请参阅 MSDN Web 服务(英文)主页。   SOAP   Soap 是 XML Web Service 的通信协议。当把 SOAP 描述为一种通信协议时,多数人都会想到 DCOM 或 CORBA,并且会问“SOAP 如何激活对象?”或“SOAP 使用什么样的命名服务?”等问题。虽然 SOAP 实现方案可能会包含上述内容,但 SOAP 标准并未对其进行规定。SOAP 一种规范,用来定义消息的 XML 格式 - 这是规范中所必需的部分。包含在一对 SOAP 元素中的、结构正确的 XML 段就是 SOAP 消息。这是不是很简单?   SOAP 规范的其他部分介绍如何将程序数据表示为 XML,以及如何使用 SOAP 进行远程过程调用 (RPC)。这些可选的规范部分用于实现 RPC 形式的应用程序,其中客户端将发出一条 SOAP 消息(包含可调用函数,以及要传送到该函数的参数),然后服务器将返回包含函数执行结果的消息。目前,多数 SOAP 实现方案都支持 RPC 应用程序,这是因为习惯于开发 COM 或 CORBA 应用程序的编程人员熟悉 RPC 形式。SOAP 还支持文档形式的应用程序,在这类应用程序中,SOAP 消息只是 XML 文档的一个包装。文档形式的 SOAP 应用程序非常灵活,许多新的 XML Web Service 都利用这一特点来构建使用 RPC 难以实现的服务。   SOAP 规范的最后一个可选部分定义了包含 SOAP 消息的 HTTP 消息的样式。此 HTTP 绑定非常重要,因为几乎所有当前的 OS(以及许多以前的 OS)都支持 HTTP。HTTP 绑定虽然是可选的,但几乎所有 SOAP 实现方案都支持 HTTP 绑定,因为它是 SOAP 的唯一标准协议。由于这一原因,人们通常误认为 SOAP 必须使用 HTTP。其实,有些实现方案也支持 MSMQ、MQ 系列、SMTP 或 TCP/IP 传输,但由于 HTTP 非常普遍,几乎所有当前的 XML Web Service 都使用它。由于 HTTP 是 Web 的核心协议,因此大多数组织的网络基础结构都支持 HTTP,并且员工已经了解了如何对其进行管理。如今,已经建立了用于 HTTP 的安全保护、监视和负载平衡的基础结构。   开始使用 SOAP 时,最容易混淆的是 SOAP 规范及其许多实现方案之间的差异。多数使用 SOAP 的用户并不直接编写 SOAP 消息,而是使用 SOAP 工具包来创建和分析 SOAP 消息。这些工具包通常将函数调用从某种语言转换为 SOAP 消息。例如,Microsoft SOAP Toolkit 2.0 将 COM 函数调用转换为 SOAP,而 Apache Toolkit 将 JAVA 函数调用转换为 SOAP。函数调用的类型和支持的参数的数据类型随每个 SOAP 实现方案的不同而不同,因此适用于一个工具包的函数可能并不适用于另一个工具包。这并不是 SOAP 的限制,而是所使用的特定实现方案的限制。   到目前为止,SOAP 最引人注目的特征是它可以在许多不同的软件和硬件平台上实现。这意味着 SOAP 可用于链接企业内部和外部的不同系统。过去曾试过多种方法以提出一个可用于系统集成的通用通信协议,但它们都没有象 SOAP 一样获得广泛的认可。为什么呢?因为与许多早期的协议相比,SOAP 更小巧,而且更易于实现。例如,DCE 和 CORBA 的实现需要数年时间,所以只发布了很少几个实现方案。而 SOAP 可以利用现有的 XML 分析器和 HTTP 库完成大部分艰苦的工作,因此 SOAP 实现方案在数月内便可完成。这就是为什么现在已经有 70 多个 SOAP 实现方案的原因。当然,SOAP 并不具备 DCE 或 CORBA 的全部功能,虽然功能减少了,但由于其复杂程度大大降低了,因此 SOAP 更易于应用。   HTTP 的普及和 SOAP 的简单性使您几乎可以从任何环境调用它们,因此成为 XML Web Service 的理想基础。有关 SOAP 的详细信息,请参阅 MSDN SOAP(英文)主页。   安全性如何?   通常,刚接触 SOAP 的用户提出的第一个问题就是 SOAP 如何解决安全性问题。在其早期开发阶段,SOAP 被看作是基于 HTTP 的协议,所以认为 HTTP 的安全性对于 SOAP 已经足够了。毕竟目前有数以千计的 Web 应用程序都在使用 HTTP 安全性,所以这对于 SOAP 确实已经足够。因此,当前的 SOAP 标准假定安全性属于传输问题,而并不作为安全性问题处理。   当 SOAP 扩展至更为通用的协议,并运行于众多传输之上时,安全性问题就变得突出了。例如,HTTP 提供若干种方法对进行 SOAP 调用的用户进行身份验证,但是当消息从 HTTP 路由到 SMTP 传输时,怎样传播该身份标识呢?SOAP 是作为构造块协议进行设计的,所以幸运的是,已经有了相应的规范以基于 SOAP 为 Web 服务提供额外的安全保护功能。WS-Security 规范(英文)定义了一套完整的加密系统,而 WS-License 规范(英文)定义了相应的技术,以保证调用者的身份标识,并确保只有授权用户才可以使用 Web 服务。   WSDL   WSDL (Web Services Description Language) 表示 Web 服务说明语言。在本文中,我们可以认为 WSDL 文件是一个 XML 文档,用于说明一组 SOAP 消息以及如何交换这些消息。换句话说,WSDL 对于 SOAP 的作用就象 IDL 对于 CORBA 或 COM 的作用。由于 WSDL 是 XML 文档,因此很容易进行阅读和编辑;但大多数情况下,它由软件生成和使用。   要查看 WSDL 的值,可以假设您要调用由您的一位业务伙伴提供的 SOAP 方法。您可以要求对方提供一些 SOAP 消息示例,然后编写您的应用程序以生成并使用与示例类似的消息,但这样很容易出错。例如,您可能看到一个 2837 的客户 ID,并假设它为整数,而实际上它是一个字符串。WSDL 通过明确的表示法指定请求消息必须包含的内容以及响应消息的样式。   WSDL 文件用于说明消息格式的表示法以 XML 架构标准为基础,这意味着它与编程语言无关,而且以标准为基础,因此适用于说明可从不同平台、以不同编程语言访问的 XML Web Service 接口。除说明消息内容外,WSDL 还定义了服务的位置,以及使用什么通信协议与服务进行通信。也就是说,WSDL 文件定义了编写使用 XML Web Service 的程序所需的全部内容。有几种工具可以读取 WSDL 文件,并生成与 XML Web Service 通信所需的代码。其中一些最强大的工具可在 Microsoft Visual Studio? .NET 中找到。   当前,许多 SOAP 工具包都包括从现有程序接口生成 WSDL 文件的工具,但却几乎没有直接用于编写 WSDL 的工具,而且 WSDL 的工具支持也很不完整。但不久就会出现编写 WSDL 文件的工具,接着还会有生成代理和存根的工具(与 COM IDL 工具很相似),这些工具将成为多数 SOAP 实现方案的一部分。到那时,WSDL 将成为创建 XML Web Service 的 SOAP 接口的首选方法。   这里有一个非常好的 WSDL 说明(英文),您还可以在 http://www.w3.org/TR/wsdl(英文)找到 WSDL 规范。   UDDI   通用发现、说明和集成 (UDDI) 是 Web 服务的黄页。与传统黄页一样,您可以搜索提供所需服务的公司,阅读以了解所提供的服务,然后与某人联系以获得更多信息。当然,您也可以提供 Web 服务而不在 UDDI 中注册,就象在地下室开展业务,依靠的是口头吆喝;但是如果您希望拓展市场,则需要 UDDI 以便能被客户发现。   UDDI 目录条目是介绍所提供的业务和服务的 XML 文件。UDDI 目录条目包括三个部分。“白页”介绍提供服务的公司:名称、地址、联系方式等等;“黄页”包括基于标准分类法(例如 North American Industry Classification System 和 Standard Industrial Classification)的行业类别;“绿页”详细介绍了访问服务的接口,以便用户能够编写应用程序以使用 Web 服务。服务的定义是通过一个称为类型模型(或 tModel)的 UDDI 文档来完成的。多数情况下,tModel 包含一个 WSDL 文件,用于说明访问 XML Web Service 的 SOAP 接口,但是 tModel 非常灵活,可以说明几乎所有类型的服务。   UDDI 目录还包含若干种方法,可用于搜索构建您的应用程序所需的服务。例如,您可以搜索特定地理位置的服务提供商或者搜索特定的业务类型。之后,UDDI 目录将提供信息、联系方式、链接和技术数据,以便您确定能满足需要的服务。   UDDI 允许您查找提供所需的 Web 服务的公司。如果您已经知道要与谁进行业务合作,但尚不了解它还能提供哪些服务,这时该如何处理呢?WS-Inspection 规范(英文)允许您浏览特定服务器上提供的 XML Web Service 的集合,从中查找所需的服务。   其他内容   到现在为止,我们已经讨论了如何与 XML Web Service 通信 (SOAP),XML Web Service 是怎样进行说明的 (WSDL),以及如何查找 XML Web Service (UDDI)。这些内容构成了一套基本规范,为应用程序的集成和聚合提供了基础。根据这些基本规范,公司可以构建实际的解决方案,并从中获益。   为实现 XML Web Service,我们已经做了许多工作,但仍有大量工作需要完成。今天,人们已经使用 XML Web Service 取得了成功,但对于开发人员来说,仍有许多环节需要完善。例如,安全性、运营管理、事务处理以及可靠的消息传递等。Global XML Web Services Architecture 将通过以下方式帮助 XML Web Service 进入下一个发展阶段:提供一个一致的通用模型,以模块化和可扩展的方式向 XML Web Service 添加新的高级功能。   上面提到的安全模块(WS-Security [英文] 和 WS-License [英文])就是 Global Web Services Architecture 规范的一部分。运营管理的需要(例如在多个服务器之间路由消息,以及动态配置这些服务器以便进行处理)也是 Global Web Services Architecture 的一部分,它们是通过 WS-Routing 规范(英文)和 WS-Referral 规范(英文)来实现的。随着 Global Web Services Architecture 的发展,还将进一步介绍满足上述需要以及其他需要的规范。 <淘宝热门商品:
 

105.00 元 

Levi's 复古铜系列情侣款女装牛仔裤

 

鲜花速递/蛋糕配送/园艺花艺 

瑞锦记锦缎喜糖袋〓婚礼特制●上等材质●流行韩


来源:程序员网

小小豆叮

SQL语句性能调整原则

一、问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。 在多数情况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提高性能。但是,如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器删去索引而使用全表扫描,一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的SQL语句。 二、SQL语句编写注意问题 下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍。在这些where子句中,即使某些列存在索引,但是由于编写了劣质的SQL,系统在运行该SQL语句时也不能使用该索引,而同样使用全表扫描,这就造成了响应速度的极大降低。 1. IS NULL 与 IS NOT NULL 不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。 任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。 2. 联接列 对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。我们一起来看一个例子,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(Bill Cliton)的职工。 下面是一个采用联接查询的SQL语句, select * from employss where first_name||‘‘||last_name =‘Beill Cliton‘; 上面这条语句完全可以查询出是否有Bill Cliton这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。 当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。 Select * from employee where first_name =‘Beill‘ and last_name =‘Cliton‘; 遇到下面这种情况又如何处理呢?如果一个变量(name)中存放着Bill Cliton这个员工的姓名,对于这种情况我们又如何避免全程遍历,使用索引呢?可以使用一个函数,将变量name中的姓和名分开就可以了,但是有一点需要注意,这个函数是不能作用在索引列上。下面是SQL查询脚本: select * from employee where first_name = SUBSTR(‘&&name‘,1,INSTR(‘&&name‘,‘ ‘)-1) and last_name = SUBSTR(‘&&name‘,INSTR(‘&&name’,‘ ‘)+1) 3. 带通配符(%)的like语句 同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton的人。可以采用如下的查询SQL语句: select * from employee where last_name like ‘%cliton%‘; 这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用: select * from employee where last_name like ‘c%‘; 4. Order by语句 ORDER BY语句决定了Oracle如何将返回的查询结果排序。Order by语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。 仔细检查order by语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写order by语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在order by子句中使用表达式。 5. NOT 我们在查询时经常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算符号取反。下面是一个NOT子句的例子: ... where not (status =‘VALID‘) 如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于(<>)运算符。换句话说,即使不在查询where子句中显式地加入NOT词,NOT仍在运算符中,见下例: ... where status <>‘INVALID‘; 再看下面这个例子: select * from employee where salary<>3000; 对这个查询,可以改写为不使用NOT: select * from employee where salary<3000 or salary>3000; 虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。 6. IN和EXISTS 有时候会将一列和一系列值相比较。最简单的办法就是在where子句中使用子查询。在where子句中可以使用两种格式的子查询。 第一种格式是使用IN操作符: ... where column in(select * from ... where ...); 第二种格式是使用EXIST操作符: ... where exists (select ‘X‘ from ...where ...); 我相信绝大多数人会使用第一种格式,因为它比较容易编写,而实际上第二种格式要远比第一种格式的效率高。在Oracle中可以几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。 第二种格式中,子查询以‘select ‘X‘开始。运用EXISTS子句不管子查询从表中抽取什么数据它只查看where子句。这样优化器就不必遍历整个表而仅根据索引就可完成工作(这里假定在where语句中使用的列存在索引)。相对于IN子句来说,EXISTS使用相连子查询,构造起来要比IN子查询困难一些。 通过使用EXIST,Oracle系统会首先检查主查询,然后运行子查询直到它找到第一个匹配项,这就节省了时间。Oracle系统在执行IN子查询时,首先执行子查询,并将获得的结果列表存放在在一个加了索引的临时表中。在执行子查询之前,系统先将主查询挂起,待子查询执行完毕,存放在临时表中以后再执行主查询。这也就是使用EXISTS比使用IN通常查询速度快的原因。 同时应尽可能使用NOT EXISTS来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),NOT EXISTS要比NOT IN查询效率更高。 <淘宝热门商品:
 

350.00 元  

【收藏本店天天有奖】 家家康 淘宝第一品牌家电商城!

亚都净化型加湿器 YZ-D231W 水公主 亚都十大经销商

 

198.00 元 

08秋款欧洲正版G-star双排扣夹克外套 超好


来源:程序员网

小小豆叮

JSP与Servlets的区别

JSP和SERVLET到底在应用上有什么区别,很多人搞不清楚。我来胡扯几句吧。简单的说,SUN首先发展出SERVLET,其功能比较强劲,体系设计也很先进,只是,它输出HTML语句还是采用了老的CGI方式,是一句一句输出,所以,编写和修改HTML非常不方便。 后来SUN推出了类似于ASP的镶嵌型的JSP,把JSP TAG镶嵌到HTML语句中,这样,就大大简化和方便了网页的设计和修改。新型的网络语言如ASP,PHP,JSP都是镶嵌型的SCRIPT语言。 从网络三层结构的角度看,一个网络项目最少分三层:data layer,business layer, presentation layer。当然也可以更复杂。SERVLET用来写business layer是很强大的,但是对于写presentation layer就很不方便。JSP则主要是为了方便写presentation layer而设计的。当然也可以写business layer。写惯了ASP,PHP,CGI的朋友,经常会不自觉的把presentation layer和business layer混在一起。就象前面那个朋友,把数据库处理信息放到JSP中,其实,它应该放在business layer中。 根据SUN自己的推荐,JSP中应该仅仅存放与presentation layer有关的东东,也就是说,只放输出HTML网页的部份。而所有的数据计算,数据分析,数据库联结处理,统统是属于business layer,应该放在JAVA BEANS中。通过JSP调用JAVA BEANS,实现两层的整合。 实际上,微软前不久推出的DNA技术,简单说,就是ASP+COM/DCOM技术。与JSP+BEANS完全类似,所有的presentation layer由ASP完成,所有的business layer由COM/DCOM完成。通过调用,实现整合。 为什么要采用这些组件技术呢?因为单纯的ASP/JSP语言是非常低效率执行的,如果出现大量用户点击,纯SCRIPT语言很快就到达了他的功能上限,而组件技术就能大幅度提高功能上限,加快执行速度。 另外一方面,纯SCRIPT语言将presentation layer和business layer混在一起,造成修改不方便,并且代码不能重复利用。如果想修改一个地方,经常会牵涉到十几页CODE,采用组件技术就只改组件就可以了。 综上所述,SERVLET是一个早期的不完善的产品,写business layer很好,写presentation layer就很臭,并且两层混杂。 所以,推出JSP+BAEN,用JSP写presentation layer,用BAEN写business layer。SUN自己的意思也是将来用JSP替代SERVLET。 可是,这不是说,学了SERVLET没用,实际上,你还是应该从SERVLET入门,再上JSP,再上JSP+BEAN。 强调的是:学了JSP,不会用JAVA BEAN并进行整合,等于没学。大家多花点力气在JSP+BEAN上。 在补充几句: 我们可以看到,当ASP+COM和JSP+BEAN都采用组件技术后,所有的组件都是先进行编译,并驻留内存,然后快速执行。所以,大家经常吹的SERVLET/JSP先编译驻内存后执行的速度优势就没有了。 反之,ASP+COM+IIS+NT紧密整合,应该会有较大的速度优势呈现。而且,ASP+COM+IIS+NT开发效率非常高,虽然BUG很多。 那么,为什么还用JSP+BEAN?因为JAVA实在前途远大。微软分拆后,操作系统将群雄并起,应用软件的开发商必定要找一个通用开发语言进行开发,JAVA一统天下的时机就到了。如果微软分拆顺利,从中分出的应用软件公司将成为JAVA的新领导者。目前的JAVA大头SUN和IBM都死气沉沉,令人失望。希望新公司能注入新活力。不过,新公司很有可能和旧SUN展开JAVA标准大战,双方各自制定标准,影响JAVA夸平台。 另外,现在的机器速度越来越快,JAVA的速度劣势很快就可以被克服。 <淘宝热门商品:
 

¥:21.00 

【义乌绘美家居】【收藏本店送礼品】

 

68.00 元 

超人气神奇魔水 柔嫩细滑肌肤一喷即成


来源:程序员网

小小豆叮

在JSP编译的时候,服务器内部做了什么?

在JSP第一次获得请求时,不管请求来自于客户端浏览器还是服务器上的servlet, JSP文件将被JSP引擎(JSP engine)转换成为一个servlet。而这个引擎本身也是一个servlet,在JSWDK,它就是 JspServlet。 在编译时候如果发现jsp文件有任何语法错误,转换过程将中断,并向客户端发出出错信息;而如果编译成功了,则所转换产生的servlet代码被编译,然后该servlet被JSP引擎加载到内存中。此时JSP引擎还请求了jspInit()方法的执行,并对此servlet做初始化。jspInit()方法在servlet的生命周期中只被请求一次。然后jspService()方法被调用来处理客户端的请求和回复操作。对于所有的随后而来的对该JSP文件的请求,服务器将检查该.jsp文件自最后一次被存取后是否经过修改。如果没有修改则请求将交给还在内存中的servlet的jspService()方法以一种同时发生的方式加以处理。注意,由于 servlet始终驻于内存,所以响应是非常快的。 如果.jsp文件被修改了,服务器将自动地对文件重新编译,并将结果取代内存中的servlet,并继续上述处理过程。 虽然JSP效率很高,但在第一次调用时由于需要转换和编译而有一些轻微的延迟。 此外,如果在任何时候如果由于系统资源不足的原因,JSP引擎将以某种不确定的方式将servlet从内存中移去。当这种情况发生时jspDestroy()方法首先被调用, 然后servlet实例便被标记加入“垃圾收集”处理。 补充: jsp编译原理 JSP文件的Scriptlets在编译后将被包含于该JSP servlet的service()方法。当JSP引擎处理客户端请求时,JSP Scriptlets在被请求的时候被执行。如果scriptlet产生输出,输出将在out (JSPWriter)对象中进行缓存然后最终发送到客户端。 .jsp由一个Servlet"解释"执行 这个Servlet就是/servlet/org.git.jsp.JspServlet(假如你把它放在servlet这个区域里) 第一次访问jsp文件的时候这个servlet会把.jsp编译成另一个servlet, 然后以后每次访问这个jsp的时候就直接调用那个servlet了. 相关内容: jsp预编译器: Jakarata Tomcat3.1 JSP服务器 (http://jakarta.apache.org)包含了一个 JSP编译器(JSPC),可以进行预编译,既可以定义一个web应用的文件目录,也可以指定某个特定jsp文件来进行编译。 另外,oracle也有jspc。 <淘宝热门商品:
 

¥:59.00 

冉冉天使屋-外贸童装批发代理

冬季宝宝必备 加厚小老虎造型棉哈衣/可当包被 土黄色(0-1岁)

 

¥:350.00 

艾の".红颜ヤк`专业减肥 瘦身 美体


来源:程序员网

小小豆叮

在J2EE上部署Web服务(Web Services)(1)

Web服务继续从开发者那里得到动力。从2002年9月web服务概念首次引入时笨拙的定义 开始,现在已经发展到在2002年3月JavaOne上有63个会议是完全关于该主题的。(会议 幻灯片链接,见更多信息)。所以,随着Java 2 平台上开发Web服务关键软件包的更新 ,我们决定更新Java 2 平台企业版(J2EE)上部署Web服务的基本指南。 J2EE的灵活性和可伸缩性使得它成为构建多层企业级应用的一个可选择平台。在J2EE平 台上部署Web服务是一个很自然的扩展。Java Web Services Developer Pack(Java WS DP或JWSDP)是一个一体化的下载包,它包含了简化Java平台上构建Web服务的关键技术 。JWSDP是Sun和Java社团的其他成员共同开发的工具和API包。它可以帮助你迅速快捷地 开发Web服务。 你可以在J2EE上运行Java WSDP构件或者将构件与J2EE集成。本文先简略地介绍Web服务 ,然后说明如何: 建立开发Web服务(JWSDP EA2)的J2EE 1.3.1环境 根据Sun Quality Expectations(SQE)组的指南将Web服务与J2EE集成 根据SQE组的指南在J2EE上部署web服务示例。 在J2EE上运行web服务 这些指南覆盖了UNIX和Windows。一般的指导适合于Windows平台上的开发者,对于UNIX 上的例外使用UNIX前缀进行标识。屏幕画面是Windows平台的,但是因为JWSDP使用Swin g,所以UNIX开发者也应该可以看到同样的画面。 注意:该指南是为JWSDP EA2编写的。如果你一直使用JWSDP EA1,那么下载EA2,它在几 个方面都有改进。 Web服务介绍 Web服务是确定从任何连接Internet的具有web服务能力的机器提供的服务。Web服务通过 一组基于XML的开放标准使得互操作性成为可能。商业上使用基于XML的Web服务描述语言 (Web Services Description Language,WSDL)描述它们的Web服务,并用一种基于XM L的注册方式,如统一描述、发现和集成(Universal Description, Discovery and In tegration,UDDI)协议,将它们列出来。 UDDI允许你查找公开的可访问的web服务,如 图1所示。客户向目录发送一个服务请求,目录通知客户符合请求条件的已注册服务。然 后使用SOAP,一种利用HTTP和XML作为交换机制的协议,在不同平台的应用程序之间进行 通信。 图1 Web服务协议 如果想了解更多的有关web服务的背景知识,可以参考更多信息中的介绍性资料。 下载和安装J2EE和JWSDP 如果还没有下载和安装J2EE SDK1.3.1和JWSDP EA2,则下载并安装该指南所介绍的J2EE SDK1.3.1和JWSDP EA2。安装是自解压的(两个包都含有安装和配置手册),最主要的 配置步骤是要确保环境变量J2EE_HOME和JWSDP_HOME指向正确的安装路径,因为许多其他 的环境变量都依赖她们的配置。 JWSDP和J2EE集成 集成过程包括两项主要任务:将JWSDP相关的JAR文件添加到J2EE中,然后进行一些端口 配置并设置安全权限。 注意:如果安装了Ant build工具,则可以运行脚本:%JWSDP_HOME%\bin\jw2sdponj2ee .bat (UNIX:$JWSDP_HOME/bin/jw2sdponj2ee.sh)。这些脚本使用一个安装JWSDP的b in目录下的Ant build文件(jwsdponj2ee.xml)。如果让Ant执行配置过程,可以跳到下 一节了解如何在J2EE平台上打包和部署web服务。如果想了解内部机制,可以按照这里给 出的指南手工进行配置。关于Ant脚本的更多信息,请参看:%JWSDP_HOME%\docs\jwsdp onj2ee.html($JWSDP_HOME/docs/jwsdponj2ee.html)。 为了添加必要的JWSDP相关JAR文件,首先使用几个JWSDP JAR文件建立两个主要的JAR文 件(jwsdp-common.jar和jwsdp-endorsed.jar),然后将这两个新的JAR文件拷贝到正确 位置并进行一些配置。 建立jwsdp-common.jar文件 1、 建立一个临时目录(例如:%JWSDP_HOME%\work\jwsdp_jars)。(UNIX:$JWSDP_H OME/work/jwsdp_jars) 2、 将下列JAR文件从%JWSDP_HOME%\common\lib拷贝到%JWSDP_HOME%\work\jwsdp_jars 。%JWSDP_HOME%环境变量定义了安装JWSDP的绝对路径。例如,在我的Windows机器上, 它是c:\jwsdp。(UNIX:从$JWSDP_HOME/common/lib拷贝到$JWSDP_HOME/work/jwsdp_j ars。$JWSDP_HOME环境变量定义了安装JWSDP的home目录) mail.jar 该JAR文件包含JavaMail API和所有的服务提供者(service provider)。 activation.jar 该JAR文件包含了组成JavaBeans Activiation Framwork(JAF)的类。 dom4j.jar dom4j.jar是一个针对Java的开放源代码XML框架。它允许读、写、定位、新 建和修改XML文档。它与DOM和SAX集成并与完全Xpath支持无缝集成。dom4j.jar文件包含 所有dom4j代码和Xpath引擎,但没有SAX和DOM接口。如果你使用JAXP和crimson.jar或x erces.jar的话,就要使用该JAR文件。 jaxrpc-api.jar和jaxrpc-ri.jar 这两个JAR文件包含JAX-RPC API和基于XML远程过程调 用(Remote Procedure Call,RPC)Java API的参考实现,它们使得Java开发者可以根 据简单对象访问协议(SOAP)规范构建基于XML RPC功能的web应用程序和web服务。 jaxm-api.jar和jaxm-client.jar 这两个JAR文件包含支持XML 消息传输的Java API参考 实现 。JAXM是一个发送和接受SOAP消息的基于XML的消息传输系统。 jaxr-api.jar和jaxr-ri.jar 这两个JAR文件包含了JAXR或者说支持XML注册的Java API 参考实现。JAXR提供了访问不同类型XML注册的统一标准。它包括JAXR信息模型和ebXml 注册及UDDI注册规范之间的详细绑定。 jaxp-api.jar JAXP是支持XML处理的Java API。它支持利用DOM、SAX和XSLT处理XML文档 。 castor-0.9.3.9-xml.jar Castor是一个XML数据绑定框架。不像处理XML文档结构的DOM 和SAX,Castor可以通过代表数据的对象模型处理定义在XML文档中的数据。Castor可以 从XML建立几乎所有的如bean的Java对象,或相反。 commons-logging.jar 该JAR文件包含了一个日志记录库包 fscontext.jar和providerutil.jar 这两个JAR文件包含文件系统服务provider。 3、 将下列JAR文件从%JWSDP_HOME%\tools\jstl拷贝到%JWSDP_HOME%\work\jwsdp_jar( UNIX:从$JWSDP_HOME/tools/jstl拷贝到$JWSDP_HOME/work/jwsdp_jar)。 jstl.jar standard.jar 这两个JAR文件提供了JSP标准标签库的实现。 4、 将provider.jar从%JWSDP_HOME%\services\jaxm-provider\WEB-INF\lib拷贝到%JW SDP_HOME%\work\jwsdp._jars(UNIX:从$JWSDP_HOME/services/jaxm-provider/WEB-I NF\lib拷贝到$JWSDP_HOME/work/jwsdp._jars)。 provider.jar是一个JAXM provider,JAXM provider是路由和传输消息的消息传输服务 。它所做的一切对用户都是透明的。只有当应用程序异步(或单向)通信时才需要JAXM provider。 5、 将目录改变为从%JWSDP_HOME%\work\jwsdp_jars(UNIX:$JWSDP_HOME/work/jwsdp _jars)。 6、 利用目录%JWSDP_HOME%\work\jwsdp_jars(UNIX:$JWSDP_HOME/work/jwsdp_jars) 下的文件建立jwsdp-common.jar。如下: jar -cvf %JWSDP_HOME%\work\jwsdp-common.jar . 注意命令末尾的点。 (UNIX:jar -cvf $JWSDP_HOME/work/jwsdp-common.jars . 注意命令末尾的点。) 现在%JWSDP_HOME%\work下就有了一个jwsdp-common.jar(UNIX:$JWSDP_HOME/work)。 建立jwsdp-endorsed.jar文件 1、 建立一个临时目录(例如:%JWSDP_HOME%\work\endorsed_jars)。(UNIX:$JWSD P_HOME%/work/endorsed_jars) 2、 将下列JAR文件从%JWSDP_HOME%\common\endorsed拷贝到%JWSDP_HOME%\work\endor sed_jars。(UNIX:从%JWSDP_HOME%/common/endorsed拷贝到$JWSDP_HOME%/work/endo rsed_jars。) sax.jar SAX是支持XML的简单API(Simple API for XML)。该JAR文件包含SAX的实现。 dom.jar DOM是文档对象模型。该文件包含DOM的实现。 xercesImpl.jar 该JAR文件包含所有实现解析器支持的标准API的解析器类。 xalan.jar 该JAR文件包含XSLT样式表处理器。它实现了XSLT和Xpath。 xsltc.jar 该JAR文件提供了一个XSLT样式表的编译器和运行时处理器。 3、 将目录改变到%JWSDP_HOME%\work\endorsed_jars(UNIX:$JWSDP_HOME%/work/end orsed_jars)。 4、 利用目录%JWSDP_HOME%\work\endorsed_jars 下的JAR文件建立jwsdp-endorsed.ja r(UNIX:$JWSDP_HOME%/work/endorsed_jars),如下: jar -cvf %JWSDP_HOME%\work\jwsdp-endorsed.jar . 注意命令末尾的点。 (UNIX:jar -cvf $JWSDP_HOME%/work/jwsdp-endorsed.jar .) 现在%JWSDP_HOME%\work目录下就有了一个jwsdp-endorsed.jar文件(UNIX:$JWSDP_HO ME%/work) 将JAR文件拷贝到正确位置并设置类路径: 1、 在%J2EE_HOME%\lib\system目录下建立一个名为endorsed的目录(UNIX:$J2EE_HO ME/lib/system)。这个在J2SDK1.4中引入的系统属性指定了Java运行时系统查找JAR文 件的目录。 2、 将jwsdp-endorsed.jar从%JWSDP_HOME%\work拷贝到%J2EE_HOME%\lib\system\endo rsed(UNIX:$J2EE_HOME/lib/system/endorsed) 3、 打开J2EE环境配置脚本文件%J2EE_HOME%\lib\setenv.bat(UNIX:$J2EE_HOME/lib /setevn.sh),在这里设置J2EE环境变量,将jwsdp-endorsed.jar插入CPATH的开始位置 : set CAPTH= %SYSTEM_LIB_DIR%\endorsed\jwsdp-endorsed.jar;%CLOUDJARS%;%CLASSES DIR% CPATH的其他部分应该保持不变。在UNIX下:CPATH=$SYSTEM_LIB_DIR/endorsed/jwsdp- endorsed.jar 等等。 4、 打开用户配置脚本文件 %J2EE_HOME%\bin\userconfig.bat(UNIX:$J2EE_HOME/bi n/userconfig.sh)并将jwsdp-common.jar通过下列操作添加到J2EE_CLASSPATH中: 通过去掉J2EE_CLASSPATH前面的rem使J2EE_CLASSPATH生效。 在J2EE_CLASSPATH前新增加定义SYSTEM_LIB_DIR的一行,如下: set SYSTEM_LIB_DIR= %J2EE_HOME%\lib\system (UNIX: SYSTEM_LIB_DIR=$J2EE_HOME/lib/system) 设置J2EE_CLASSPATH如下: set J2EE_CLASSPATH=%SYSTEM_LIB_DIR%\jwsdp-common.jar (UNIX: J2EE_CLASSPATH=$ SYSTEM_LIB_DIR/jwsdp-common.jar) 5、 打开J2EE服务器脚本文件%J2EE_HOME%\bin\j2ee.bat(UNIX: $J2EE_HOME/bin/j2e e.sh),在文件的最后一行找到the %JAVA_COMMAND% (UNIX: JAVACMD),然后指定系统 属性java.endorsed.dirs如下: %JAVA_COMMAND%-Djava.endorsed.dirs=%J2EE_HOME%\lib\system\endorsed (其余行保 持不变) -classpath %CPATH% com.sun.enterprise.server.J2EEServer and so on) (UNIX: $JAVACMD -Djava.endorsed.dirs=$J2EE_HOME/lib/system/endorsed 等等) 注意:系统属性java.endorsed.dirs指定了Java运行时环境查找JAR文件的一个或多个目 录。只有当在J2SDK1.4上运行J2EE SDK1.3.x时才设置该系统属性。 配置web服务器端口和设置安全权限 1、 打开文件%J2EE_HOME%\config\web.properties (UNIX: $J2EE_HOME/config/web. properties)将http.port号码从8000改成8080,因为JWSDP使用的Tomcat web服务器缺 省情况下使用8080端口,一些JWSDP示例程序也使用这个端口。web.properties文件用来 设置web属性(如端口号、HTML根目录、日志文件等等)。因此http.port=8080是HTTP服 务用来服务请求的端口。 2、 打开文件%J2EE_HOME%\lib\security\server.policy (UNIX: $J2EE_HOME/lib/sec urity/server.policy),然后在文件的末尾缺省域中指定下列权限: permission java.io.FilePermission "", "read,write,delete"; permission java.util.PropertyPermission "*", "read,write"; permission java.lang.RuntimePermission "modifyThreadGroup"; server.policy是J2EE服务器的Java 安全策略文件。它包含J2EE引擎的Java 安全策略设 置和授权给一个程序特定访问的权限。 添加到安全策略文件中的第一行授权在所有文件上有读、写和删除的权限。第二行允许 读和写任何系统属性。读权限允许调用System.getProperty。写权限允许调用System.s etProperty。第三行是运行时权限。在本例中,这个属性运行通过调用ThreadGroup的d estroy, getParent, resume, setDaemon, setMaxPriority, stop 和 suspend方法修改 线程组。你需要所有这些权限运行JWSDP附带的JAXM Provider Admin Tool,jaxm-remot e.jar和jaxm-soaprp例子。 <淘宝热门商品:
 

¥:89.00 

爱相依外贸童装.精品低价.15天无理由退货

特价 贺年款 精美棉大衣外套/棉衣/棉袄 帽子可拆卸 3971

 

58.00 元  

淘宝生活 运动鞋专卖/正品ZIPPO热卖//淘宝职业信誉卖家

冲双钻 ADIDAS三叶草08夏季新款特价 漫画小子个性 休闲板鞋


来源:程序员网

小小豆叮

步入J2EE架构和过程2

4、 对象设计 在架构规范的指导下,设计从技术上扩展和修改了分析结果。虽然分析阶段的领域对象 建模应该与技术细节无关,但是对象设计完全依赖于技术因素,包括平台、语言的类型 和架构开发阶段选择的供应商。分析时,抬头望着星星,但在设计阶段,则要脚踏实地 。理论上,为了维持业务对象的基本属性和行为,除非绝对必要,不应该破坏它们。 在架构结果的指导下,详细设计工作应该说明所有类的规格,包括必须实现的属性、它 们的详细接口和伪代码或操作的纯文本描述。规格说明应该足够详细使得和模型图结合 时,它可以提供所有必须的编码信息。在许多自动化软件生产过程中,我们可以从面向 对象图生成代码框架。图5和6 说明了对一些领域对象的高层和详细设计对象。注意桩( stub)和框架(skeleton)在图中经常是不可见的,因为它们对设计人员和编程员来说 是透明的。我将它们包括在图6中以说明EJB的基础部分。 图6 对象设计模型:订单EJB详细设计 在完成了详细对象设计后,还需要完成领域对象的对象-关系映射。原因是虽然面向对象 方法学现在非常流行,但是大多数流行且成熟的持续性存储却是关系型的。另外,在许 多情况下,客户的IT基础设施已经反映了对商业RDBMS供应商的投资和偏爱。所以,将领 域对象转换成关系模型或数据库表是非常重要的。虽然有许多容器管理的持续性工具, 但它们不能取代好的关系数据库设计。 5、 实现 在良好的架构和详细设计条件下,实现应该是一个明确的任务。另外,因为我们设计和 实现架构原型阶段的纵向联合部分,所以实现阶段应该更没有什么值得惊讶的。在许多 组织中,开发者经常过早地到达实现阶段。尤其当管理者盯着开发人员确保在编码,而 不是做他们认为在浪费公司时间的其他事情时,这种情况变得更加严重。 结果,不再花数小时或数天绘出UML草图,而是通常在发费数周或数月编码的同时测试自 己的想法。由于在这种情况下,所有地架构决定和设计都是在编码阶段做出来的,所以 经常过了数月后才发现开发的方向出错了。 6、 验证 验证包括测试验证系统按设计要求运行并满足需求。验证过程发生在整个开发生命周期 的开发和产品环境中。单元测试、集成测试和用户测试本身就是非常重要的主题。 7、 装配和部署 构件装配和解决方案部署在J2EE开发中特别重要。开发和产品环境可能非常不同。如果 EJB在系统中,你需要使用供应商特定的工具得到容器自动生成的类,因为,正如我以前 指出的,Web和应用程序构件配置对不同的供应商来说是不同的。你也必须考虑要部署的 系统是否含有供应商特定代码实现。在可扩展架构中,系统结构应该是稳定的但也应该 在不影响整个系统的条件下支持新或老构件的增量部署。 8、 运行和维护 在最后阶段,应用程序到了用户手中,你必须给他们提供培训和文档。用户会发现错误 并可能要求新特性。你必须适当地改变管理过程来处理这些情况。你不必为了部署一个 新构件或取代老构件而关闭一个正在运行的系统。 架构开发过程 知道了必须做出许多架构决定,因此我们必须为架构开发描绘一个过程。对于一个企业 来说通常有许多应用项目,它们中的一些可能跨越数年,结果是系统演化包含许多周期 。在你的领域里存在着许多跨越多个项目的通用需求。你应该不费力地在它的生命周期 或其他项目中使用以前项目周期的可扩展且可重用的架构。为一系列软件应用提供同属 结构和行为的通用框架和可重用软件架构是非常需要的。 如果是第一个J2EE项目,架构必须做原型、测试、度量、分析并在迭代中进行推敲。蓝 图提供了许多好的设计指导和实践,宠物店示例程序可以作为一个很好的参考架构。最 有效地快速、高质量发布好的解决方案的方法是接受和扩展蓝图参考架构并插入你自己 的业务构件。你最后要做的就是改造车轮。 接受一个参考架构 就我的理解,宠物店架构的精华是模型-视图-控制和命令模式。你可以将这些模式应用 到以Web为中心和以EJB为中心的系统中。对于每个领域对象,视图用嵌套的JSP表示。控 制器处理相关的业务事件,领域对象封装业务逻辑、事物和安全。我们使用门户servle t作为中心控制器接受和截获所有用户的动作。它将业务事件分发给特定的调用领域对象 改变持续状态的领域对象控制器。依靠事件处理结果,控制器选择下一个要展现的视图 。下面是我们可以修改并在大多数J2EE应用程序中使用的主要构件: a、 MainServlet:门户构件,Web容器和框架之间的接口 b、 ModelUpdateListener:获得模型更新事件对象的接口 c、 ModelUpdateNotifier:当更新模型事件发生时通知侦听器 d、 RequestProcessor:处理所有从MainServlet来的请求。 e、 RequestHandler:即插即用请求处理构件接口 f、 RequestHandlerMapping:包含请求处理映射规则 g、 RequestToEventTranslator:中心请求处理器根据请求处理映射规则代理即插即用 请求处理构件的请求。将http请求转换为业务事件 h、 EstoreEvent:业务事件 i、 ShoppingClientControllerWebImpl:代理EJB层门户控制器 j、 ScreenflowManager:控制屏幕流,选择视图 k、 ModelUpdateManager:EJB层模型更新管理器,通知什么模型由于事件发生了改变 l、 ShoppingClientControllerEJB:EJB层门户,为EJB客户提供远程服务 m、 StateMachine:中心事件处理器,根据状态处理映射规则代理即插即用处理构件的 事件处理 n、 StateHandler:EJB层状态处理接口 o、 StateHandlerMapping:包含状态处理映射规则 扩展参考架构 虽然蓝图示例程序是一个好的起点,但应该根据每个项目或领域修改它。设计模式是可 重用的微体系结构,可以使用它扩展参考架构。提供了一组有用的J2EE模式目录的蓝图 和23个"四人帮"模式都是非常不错的资源。例如,如果想扩展参考架构支持工作流管理 ,你可以在部署或运行时动态地在中心控制器注册事件处理器。中心控制器会询问每个 注册的事件处理器直到一个处理器返回消息表明到了命令链的末端。 插入你的业务构件 J2EE技术对每个人都是一样的,但是不同的领域,我们要解决的问题是不同的。一旦建 立了一个基本的J2EE框架,必须实现一些用例来说明架构确实可以为你的领域服务。可 以通过选用捕获系统关键功能的场景来实现,这些场景经常使用来展现关键的技术风险 。从领域分析模型入手,可以象我们在图5和6中那样将领域对象映射成高层和低层设计 模型。实现低层设计模型并测试是否真正在工作。如果每件事都按计划运行,那么重新 评估风险开始下一个迭代,扩展要考虑的场景并选择更多的场景扩展架构的覆盖范围。 经过几次迭代后,原始的架构原型应该变得稳定。识别要购买的构件,要保留的遗留系 统和怎样将它们对接。下一步是软件设计,你可以使用设计指导中规定好的类似方法和 过程继续开发。 一步一步 我们使用一个过程来将一个复杂问题分解为较小的几个问题,这使得我们可以更容易的 理解和解决它们。在本文中,我们将J2EE开发分解为八个步骤,主要集中在架构和设计 。我已经阐述了重要的架构并为架构决定提供了一个过程。我也讨论了J2EE架构师的角 色和可交付产品。 学习使用这些步骤开发J2EE解决方案就象学习跳舞的步骤一样。首先需要自觉并持之以 恒地练习基本步骤。但是,一旦你对它们相当熟悉后,应该将它们放在一起并将注意力 更多地集中在规模、速度、流和特定上下文中每一步的节奏。但永远不要让一个过程限 制了创造性。而应该接受和扩展过程以满足自己特殊需要。记住,最终目的是提供满足 客户需求的完整的J2EE解决方案。 <淘宝热门商品:
 

185.00 元  

累计销售1000多件 G-STAR 雪纺短款夹克

 

19.00 元  

【广州商盟】靓一族专营匡威运动鞋/秋冬针织帽子

特价19元 09年新款出口韩国时尚个性净版球球针织帽爆款卷边


来源:程序员网

小小豆叮