XML/JSP濠电姷鏁搁崑鐐哄垂閸洖钃熼柕濞炬櫓閺佸嫰鏌涢埄鍐炬婵炲吋鐗犻弻銊╂偆閸屾稑顏�
Hello,XML XML&Java/JSP婵犵數濮甸鏍窗濡ゅ啯鏆滈柟鐑橆殔绾剧懓霉閻樺樊鍎忛梺鍗炴喘閺屻劑鎮ら崒娑橆伓 JSP+XML闂傚倸鍊烽懗鍫曞箠閹剧粯鍊堕柛顐犲劚绾惧鏌熼崜褏甯涢柣鎾寸洴閺屾盯顢曢顫盎闂佽楠忕徊璺ㄦ閹捐閱囬柣鏂款殠濡棝姊虹涵鍛处闁告濞婂顐﹀箻缂佹ɑ娅㈤梺璺ㄥ櫐閹凤拷 JSP+XML闂傚倸鍊风粈渚€骞栭锔绘晞闁告侗鍨崑鎾愁潩閻撳孩鐝濋梺璇″枤閺佸銆佸☉妯锋敠闁诡垎鍌氼棜闂備線娼荤€靛矂宕㈤崗鑲╊浄闁圭儤姊荤壕濂告偣娴g瓔娼愰悗姘炬嫹-Sparks.com Serving XML with JSP XML缂傚倸鍊搁崐鎼佸磹妞嬪海鐭嗗ù锝囧劋瀹曟煡鏌熼悧鍫熺凡閻熸瑱绠撻弻宥堫檨闁告挾鍠庨~蹇旂節濮橆剛锛滈梺闈涚墕閹冲繘鎮橀幘缁樷拺闁绘垟鏅滈~澶愭煙閸戙倖瀚� IBM闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閸曨偄鐎梺鍝勫鐏忥拷/Java闂傚倸鍊峰ù鍥ь浖閵娾晜鍊块柨鏇炲€归崑锛勬喐閺冨牆鏄ラ柕蹇嬪€栭弲鎼佹煥閻曞倹瀚� **闂傚倸鍊峰ù鍥ь浖閵娾晜鍊块柨鏇炲€归崑锛勬喐閺冨牆鏄ラ柕蹇嬪€栭弲鎼佹煥閻曞倹瀚� 缂傚倸鍊搁崐鐑芥倿閿曞倶鈧啴骞囬弶璺唵闂佽法鍣﹂幏锟�1-3缂傚倸鍊搁崐鐑芥倿閿曗偓铻為柟杈剧畱缁狀垶鏌ㄩ悤鍌涘 **闂傚倸鍊峰ù鍥ь浖閵娾晜鍊块柨鏇炲€归崑锛勬喐閺冨牆鏄ラ柕蹇嬪€栭弲鎼佹煥閻曞倹瀚� 缂傚倸鍊搁崐鐑芥倿閿曞倶鈧啴骞囬弶璺唵闂佽法鍣﹂幏锟�4缂傚倸鍊搁崐鐑芥倿閿曗偓铻為柟杈剧畱缁狀垶鏌ㄩ悤鍌涘 **闂傚倸鍊峰ù鍥ь浖閵娾晜鍊块柨鏇炲€归崑锛勬喐閺冨牆鏄ラ柕蹇嬪€栭弲鎼佹煥閻曞倹瀚� 缂傚倸鍊搁崐鐑芥倿閿曞倶鈧啴骞囬弶璺唵闂佽法鍣﹂幏锟�5缂傚倸鍊搁崐鐑芥倿閿曗偓铻為柟杈剧畱缁狀垶鏌ㄩ悤鍌涘 **闂傚倸鍊峰ù鍥ь浖閵娾晜鍊块柨鏇炲€归崑锛勬喐閺冨牆鏄ラ柕蹇嬪€栭弲鎼佹煥閻曞倹瀚� 闂傚倸鍊搁崐鎼佸磹閸濄儮鍋撳鐓庡闁逞屽墯閸戝綊宕滃☉姘潟闁绘劗鍎ら弲鎼佹煥閻曞倹瀚�1 婵犵數濮烽弫鍛婃叏瀹ュ绀嬫い鎰╁焺濞兼洟姊绘担鍝ユ瀮妞ゆ泦鍥цЕ閻庯綆鍠栫粻姘舵煛閸愩劎澧涢柡鍛倐閺屻劑鎮ら崒娑橆伓 **闂傚倸鍊峰ù鍥ь浖閵娾晜鍊块柨鏇炲€归崑锛勬喐閺冨牆鏄ラ柕蹇嬪€栭弲鎼佹煥閻曞倹瀚� 闂傚倸鍊搁崐鎼佸磹閸濄儮鍋撳鐓庡闁逞屽墯閸戝綊宕滃☉姘潟闁绘劗鍎ら弲鎼佹煥閻曞倹瀚�2 婵犵數濮烽弫鍛婃叏瀹ュ绀嬫い鎰╁焺濞兼洟姊绘担鍝ユ瀮妞ゆ泦鍥цЕ閻庯綆鍠栫粻姘舵煛閸愩劎澧涢柡鍛倐閺屻劑鎮ら崒娑橆伓 闂傚倷娴囧畷鍨叏閹惰姤鈷旂€广儱顦壕瑙勭箾閹存瑥鐏柛搴★躬閺岀喐绗熼崹顔诲垔L Server Java濠电姷鏁搁崑鐐哄垂閸洖绠伴柟闂寸劍閻掔粯淇婂Δ鍐炬▼闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閳ь剟鍩€椤掍礁鍤柛鎾跺枛瀵鏁冮崒姘卞幐闂佸憡鍔忛弲娑欑閹烘鐓熼煫鍥ㄦ礀娴犫晠鏌涚€n偄濮嶉柟顔惧厴椤㈡盯鎮欑€电ǹ骞楅梻浣瑰缁诲倻鎹㈤幋鐘亾濮樼偓瀚� JDOM--XML闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閸曨偄鎯炲┑鈩冨劤閸忔獌闂傚倸鍊烽懗鍫曘€佹繝鍥舵晪鐟滃繘骞戦姀鐘栨棃宕ㄩ銏犳暪闂備浇娉曢崳锕傚箯閿燂拷 Apache闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閸曨偄鐎梺鍝勫鐏忓孩绻濈喊澶岀?闁稿鍨垮畷鎰板即閻戝洩鈧潡鏌涘Δ鍐х闯婵炲樊浜濋弲鎼佹煥閻曞倹瀚� Cocoon闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閳ь剟鍩€椤掍礁鍤柛鎾跺枛楠炲啯銈i崘銊庘晝鎲告惔銊ョ柈闁硅揪闄勯悡銉╂煟閺囩偛鈧湱鈧熬鎷� XML DTD濠电姷鏁搁崑娑㈩敋椤撶喐鍙忛柟缁㈠櫘閺佸嫰鏌涢妷銏℃珔闁搞劍绻堥弻銊╂偆閸屾稑顏� XML Schema闂傚倸鍊烽懗鑸电仚缂備浇顕ч悧鎾崇暦閺囥垺顥堟繛纾嬫珪閻╊垶寮幘缁樻櫢闁跨噦鎷� JSP濠电姷鏁搁崑鐐哄垂閸洖绠伴柟闂寸劍閻掔粯淇婂Δ鍐炬▼闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閳ь剟鍩€椤掍礁鍤柛銊ㄤ含缁骞掑Δ鈧柋鍥煏婢跺牆鍔ゆい鎾炽偢濮婅櫣鎹勯妸銉︾彚闂佺懓鍤栭幏锟� XML濠电姷鏁搁崑鐐哄垂閸洖绠伴柛娑橈功閳绘梹绻涢幘鑼暛a闂傚倸鍊烽悞锕傛儑瑜版帒绀夌€光偓閳ь剟鍩€椤掍礁鍤柛娆忓暙椤曪綁骞庨懞銉ヤ簻闂佺粯鎸稿ù鐑筋敊婢跺⿴娓婚柕鍫濇閻撱儱顭胯缁瑩鐛箛娑樜╅柍鍝勫€搁埀顒傛暬閺岋綀顦查柟璇х磿閳ь剚鐔幏锟�
|
从 XML 到 Java
代码的数据绑定 (第1-2篇)
Brett McLaughlin
Enhydra 战略家,Lutris 科技公司
2000 年 7 月
内容:
分析各种选择
约束数据
利用约束
来自 XML 的 Java
总结
参考资料
在这个由四部分组成的系列文章的第一部分,我们将弄清什么是数据绑定,与在 Java 应用程序中处理 XML 数据的其它方法相比它有什么优势,以及如何开始使用它。这一部分将考查为什么使用数据绑定,以及如何为各种约束建立模型,使 XML 文档能转换成 Java 对象。同时还涵盖用于生成数据绑定类的输入和输出。
您希望在您的 Java 应用程序中使用 XML 吗?那么好,同成千上万的其他人一起上这条船吧。当您深入了解 XML 以后,也许您会发现 DOM 和 SAX API(请参阅参考资料)不过是唬人的东西。您可能认为肯定存在某种简单方法可以取得 XML 文档,并通过 Java 应用程序访问它,对吗?不必通过回调或复杂的树状结构,而是使用像 setOwner(Stringowner) 和 int getNumOrders() 这样的方法,对吗?如果您曾经沿着这一思路考虑问题,那么数据绑定就是您要寻找的解决方案。
分析各种选择
当今各种 XML 和 XML 主义正泛滥成灾(XSL、RDF、命名空间、RSS、XML Schema、XSLT...),您可能认为现在会有很多方法去访问 Java 应用程序中的 XML 数据。令人惊讶的是,如果您寻根究底,实际只存在三种访问 XML 数据的方法。没错 -- 只有三种方法,其中的一种还是最近随一种新的 Java API 才出现的。
应该这样来看待这一问题:选择范围小使您更易于选出适合于您的方法。
回调
回调是作为一种事件驱动模型工作的。当分析 XML 文档时,某些事件 -- 如文档的起始和某个元素中的字符数据的起始 -- 将触发回调方法。通过使用执行逻辑所需的数据,您可以实现这些事件的 Java 代码。要弄清这种方法不能全靠直觉;开发人员通常要花费一段时间来理解和掌握回调模型的使用。SAX,用于 XML 的一种简单 API,是这种 XML 使用方法的事实上的标准。
树
更常见、更流行的是这种 API,它们取得一个 XML 文档,然后创建数据的树状结构。XML 文档成为树首,充当一种容器。它有若干子级,如根元素。根元素又有其附加的子级,依此类推,直到(在某种意义上)获得 XML 数据的一幅图为止。因为几乎每个大学生在某个阶段肯定都处理过树状结构,所以这就可用作表示 XML 数据的一种非常直观的方法。
用于 XML 文档树状表示的最流行的 API 就是 W3C 的推荐标准,即文档对象模型 (DOM)。一种更新的 API,JDOM (这不是首字母缩写词)最近也正一直在推广并流行开来。(虽然这个方案是我和 Jason Hunter 建立的,但我还得说实话。)另外,DOM 和 JDOM 都是 Spinnaker 方案设计的基本要求,Spinnaker 是一种新的 XML 分析器,它作为 Apache XML 方案的一部分正在开发之中。
虽然树状 API 看起来比事件驱动的 SAX 更易于使用,但它们并不总是合适的。非常大的文档可能需要大量的内存(尤其是使用 DOM 时);当对树结构执行转换 (XSLT) 时,系统可能停止运转甚至彻底崩溃。虽然更新的 API(如 JDOM)能处理这些问题,但如果您必须处理极大量的数据,它们仍将是一个问题。并且,有时开发人员宁愿将 XML 文档中的数据建模为一个简单的带有值的读写方法的 Java 对象,而不用树状模型工作。例如,开发人员会宁愿不去访问名为 skuNumber 的子节点并设置该节点的文本值,而只想调用 setSkuNumber("mySKU") 并继续进行。
术语解释
数据绑定。从 Java 应用程序内部访问 XML 数据的一种新方法,使用仍在开发中的一种 API,JSR-031。
JSR-031。 Sun 仍在开发中的一种新的 Java 规范请求,设计用于将 XML 文档编译成一个或多个 Java 类,而在 Java 应用程序中可以方便地使用这些 Java 类。
打包。将 Java 对象转换为 XML 表示,拥有当前值。
解包。根据 XML 对象创建 Java 对象,通常是根据打包生成一个 Java 对象。
数据绑定
用 Java 代码访问 XML 数据的最新方法要依赖于一套新的 Java 方法和相关的 API,这些 API 仍在开发之中。数据绑定是由 Sun 构建的一种“Java 规范要求”(JSR-031,见参考资料),它设计用于使 Java 对象绑定到 XML 文档更加方便,这样就使一种格式能够容易地转换为另一种格式,反之亦然。绑定引用一个具有读写方法的 Java 对象,读写方法都会影响到底层的 XML 文档,并且也都直接映射为 XML 文档中的元素及特征的名称。当您进入到本系列文章下一部分中的某些细节时,这一说明会更有意义,但在目前,只说一点就够了:这样做使 XML 文档特征 name 能够通过一个称为 setName() 的方法,来更改它的值,就像我上面暗示的那样。
这种访问方式正在得到普及,并且当在 XML 文档中存储配置信息时特别有用。许多开发人员发现,它非常便于直接访问所需的参数,而无须使用更复杂的树状结构。虽然这种访问对于文档转换或消息传送没有什么用处,但它对于简单数据处理是极其方便的。它是我们在本文及本系列文章中关注的第三种使用 XML 的方法。
(当然,任何方法随后都会引出一系列新的术语,所以请查看术语解释以了解这些新的行话。)
是否任何 XML 文档都可以转换为 Java 对象?还是仅有某些类型的 XML 文档才可以?问得好!您很可能只希望将满足一组约束条件的文档转换为 Java 对象。这与定义 Java 接口的方法类似:您确保只实例化和使用适应该接口的对象,允许就如何操作该对象作出假定。同样,您只允许将满足一组约束条件的 XML 对象转换成 Java 对象;这使您能够按希望的方式来使用所创建的对象。
约束数据
在研究代码之前,您需要回答几个有关如何表示 XML 数据的问题。这是数据绑定的最具挑战性的方面之一。是为每个文档创建一个新类,还是创建某个现有类的一个实例?您要使用哪个现有类?并且最重要的是,您正在处理的文档是否适宜转换为 Java 对象?
那是一大堆问题,但您将在这里找到全部答案。将这些问题看作一系列决策点,一系列选择。首先,您必须确定您能否从该 XML 文档创建 Java 对象(如前面所讨论的那样)。如果能,您就要决定转换应该以新 Java 类的形式出现,还是仅以现有类的一个实例的形式出现。最后,如果选择了现有类,那么使用哪个类呢?结果就是各种各样的决策树。
如果我们考察清单 1 中所示的一个示例 XML 文档,然后再来处理这些问题,则决策树的意义就更加清楚了。此示例文档表示 Enhydra Application Server 中某个服务(具体说就是一个 web 容器)的配置。
清单 1. 一个用于配置 Enhydra 中的 web 容器的 XML 文档 <?xml version="1.0"?>
<webServiceConfiguration
version="1.0"
name="My Web Container"
>
<port number="80"
protocol="http"
protected="false"
/>
<document root="/usr/local/enhydra/html"
index="*.html,*.xml"
error="error.html"
/>
</webServiceConfiguration>
此配置文档包含有关服务本身的版本和名称的信息,以及几个嵌套的项目,每个项目都表示有关该 web 容器服务的一些附加信息。它给出了有关端口的详细信息(包括端口号、协议和安全性),也给出了文档服务信息(包括文档根、用于索引页的默认扩展名以及错误页)。所有这些合在一起,就是配置一个新的 web 容器服务所需的全部信息。
记住这个示例,您就可以开始回答数据表示的各个问题了。
是否适合转换?
绝对适合!只要看一看清单 1 中的 XML 文档就会发现,它表示一个对象(总体配置对象),具有若干特征或变量。其中某些变量又是另外的对象(端口和文档),这些对象又具有它们自己的特征。实际上,这是适合转换为 Java 对象的 XML 文档的一个极好例子。为了进一步保证此对象是可用的,稍后我将向您展示一种方法来约束文档中的数据。但是,还是先让我们继续沿着决策树往下走。
转换成类还是实例?
解决适宜性问题以后,现在就可以作出决定,是将每个 XML 配置文档都变为一个全新的 Java 类呢,还是简单地将其变为某个现有类的一个新实例。换句话说,就是到底应该生成新代码,还是利用现有的代码。照这样来看,这就变成了一个简单的可重用性问题。更容易且更明智的做法是,为每个 XML 文档生成现有类的新实例。如果您一定要尝试一下从每个文档创建一个新的 Java 类,则得到的各个类之间可能没有兼容性 -- 即两个完全相同的文档可能导致两个不同的 Java 类!
不用这个可能引起混乱的方法,您可以采用一组 XML 约束条件(由一个 DTD 或 XML 方案表示,将在下面讲述),并根据这些约束条件来生成一个 Java 类(或多个类,根据需要)。这个生成的类将表示符合这些约束条件的任何 XML 文档;这些 XML 文档中的每一个都将被解包到生成的类的一个实例中。在这种情况下,就可以为表示 web 服务配置的文档定义约束条件。这些约束条件将被映射为一个 Java 类,我们将称之为 WebServiceConfiguration。然后您就可以获得任何一种表示特定 web 服务配置的 XML 文档,并假定此文档符合我们的约束条件,由它而创建出前面生成的类的一个实例。这将允许应用程序将不同的 XML 文档用作相同类型的对象,只要这些文档中的数据对于该对象设计时要达到目的来说是有效的即可。
新类还是现有的类?
现在您也已经有条件回答下一个问题了:您希望创建一个现有类即 WebServiceConfiguration 类的一个实例。剩下需要弄清的全部事情是,这个类是如何预先生成的。所以,现在请将您的注意力集中在这样一个问题上:如何获得一组约束条件,用 XML 实现它们,并保证文档符合这些约束?再一个问题就是,您如何再从这些约束条件生成一个可重用的 Java 类?
利用文档约束条件
既然您知道此文档将转换成一个 Java 实例,这就产生了另一个问题:要考虑到必须以某种方式保证可将此文档正确地解包到一个选定的 Java 类中。缺少变量或数据类型不正确都可能导致在解包过程中出错 -- 或者甚至在客户机访问配置错误的容器时出现运行时异常。
最好的情况是,在实际的解包过程开始之前,文档的作者能够保证,配置文档对于他们选择用来表示数据的类是“合法的”。阅读到这一方案的 XML 人士说不定就会转动他们的眼睛并嘀咕说,“好吧,当然您将使用 XML 文档约束条件。”确认数据对选定类的合法性可以通过引用 DTD (文档类型定义)或 XML 方案来完成。
通过使用一组用外部 DTD 或方案文件表示的约束条件,文档作者就可以在这些数据的“接口”上测试配置数据。换句话说,您可以这样来建立应用程序,使之能够对照所需的数据来检查包含在 XML 实例文档中的数据,而所需数据则是在文档约束条件的外部文件中指定的。这样,您就可以为数据创建一个接口。
在使用 DTD 方案还是使用 XML 方案之间作出决策是一种相当简单的选择:因为 Java 语言是高度类型化的,所以我们希望能在 XML 文档中支持类型化。例如,数据接口应该能够验证,为 web 容器的端口号提供的是整数,而不是字符串,后者在服务启动时将引起错误。DTD 不支持类型检查,所以我们无疑应该选择 XML 方案。虽然 XML 方案在规范的领域在有一点不确定性,但它在很大程度上已趋于稳定,并可望在年内定型。我们可以在一个 XML 方案中编写数据约束条件,然后用这个方案验证可能的实例文档,以确保解包能在这些实例文档上进行。下面的 XML 方案表示我们的 web 容器服务的数据接口。
清单 2. 表示一个 web 容器配置文档的数据接口的 XML 方案 <?xml version="1.0"?>
<schema targetNamespace="http://www.enhydra.org"
xmlns="http://www.w3.org/1999/xmlSchema"
xmlns:enhydra="http://www.enhydra.org"
>
<complexType name="ServiceConfiguration">
<attribute name="name" type="string" />
<attribute name="version" type="float" />
</complexType>
<element name="serviceConfiguration" type="ServiceConfiguration" />
<complexType name="WebServiceConfiguration"
baseType="ServiceConfiguration"
derivedBy="extension">
<element name="port">
<complexType>
<attribute name="protocol" type="string" />
<attribute name="number" type="integer" />
<attribute name="protected" type="string" />
</complexType>
</element>
<element name="document">
<complexType>
<attribute name="root" type="string" />
<attribute name="index" type="string" />
<attribute name="error" type="string" />
</complexType>
</element>
</complexType>
<element name="webServiceConfiguration" type="WebServiceConfiguration" />
</schema>
清单 2 中的 XML 方案定义了几个不同的数据对象,它们合起来表示一个 web 服务的配置对象。首先,定义了一个核心服务配置(serviceConfiguration),它包含版本和名称。这可用作所有服务(如负载均衡服务、EJB 容器,当然还有我们的 web 服务)的基本对象。然后,作为此基本服务的扩展,又定义了 web 服务配置(webServiceConfiguration)。请注意,Java 成型之后,方案就已经为数据接口建立了模型。我们将附加的 web 服务属性 port 和 document 添加到 version 和 name 基本属性中。这些属性本身都是对象,具有它们自己的属性(protocol、root、error 等)。
在此方案的定义方式中,特征代表简单的 Java 类型,通常是原始 (primitive) 类型。这样,name 和 version 就分别成为类型 String 和 float 的 Java 原始类型。port 和 document 这样的元素成为 Java 对象,它们可以有自己的属性,还是用特征来表示。这样就出现了递归现象:元素变成新对象,并对它的每个属性进行检查。如果属性是一个特征,就为此对象创建一个简单的 Java 原始成员变量;如果属性是元素,则创建一个新的对象,并作为一个成员变量将其添加,然后在这个新对象上又开始同样的过程,直到全部类都已创建为止。
从萝卜 ... 嗯 ... XML 获得 Java
一旦创建了 XML 方案,您就需要从这个方案中提取出必需的信息,来确定应该创建哪些 Java 类。这个过程的第一步是查看 XML 方案,并严格确定输出应该是什么。对于简单的 serviceConfiguration 对象,定义了两个 Java 原始属性:name 和 version。对于这样一个简单的对象,确定所需的接口并不难。只需将被定义类型的名称的首字母大写,并将这些 Java 属性添加到接口中即可,如清单 3 所示。
清单 3. 为 ServiceConfiguration 接口而从 XML 方案生成的 Java 代码 public interface ServiceConfiguration {
public void setVersion(float version);
public float getVersion();
public void setName(String name);
public String getName();
}
这是相当明白易懂的;清单 3 中的接口为 XML 方案中定义的属性提供读方法和写方法。另外,您将需要生成一个实现类来定义此接口的各个成员变量,并实现此接口中的每个方法。这种使接口从实现中分离出来的方法使我们能够为特定的需要提中供多种实现。 例如,某个特定的服务可能需要执行计算,而不只是接受从写方法中收到的值。现在考虑那种更复杂的情况还有点为时尚早,但我将在后续文章中重新提到它。然而,一般说来,您仍可以确定实现类应该像什么样子,如清单 4 所示。
清单 4. 为 ServiceConfiguration 实现而从 XML 方案生成的 Java 代码 public class ServiceConfigurationImpl implements ServiceConfiguration {
private String name;
private float version;
public void setVersion(float version) {
this.version = version;
}
public float getVersion() {
return version;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
相同的原则也适用于 XML 方案中定义的其它对象。您可以在下面查看到其它 Java 类(因为它们都是应该生成的):
PortType.java
PortTypeImpl.java
DocumentType.java
DocumentTypeImpl.java
WebServiceConfiguration.java
WebServiceConfigurationImpl.java
总结
到目前为止,您应该对数据绑定的各个方面都比较熟悉了。我已初步介绍了您应该使用数据绑定的原因,尤其是在配置信息的范围内,并概述了为实现此方法您所需要了解的一些基本概念。
此系列文章的下一篇将继续考察数据绑定的过程。您将有机会去检查 org.enhydra.xml.binding.SchemaMapper 类,它将接受这第一部分中创建的 XML 方案作为数据接口,并从它创建出一个 Java 接口和实现类。本系列文章的第二部分将详细说明这一过程的每个步骤,并说明如何确保方案被准确表示,以便 XML 文档能接着被转换为生成的类的实例。
数据绑定系列第二篇
内容:
打造基础
生成代码
组装 SchemaMapper
生成类
完成包
总结
参考资料
本篇是如何从 XML 数据限制中生成一个 Java
语言。
本文通过完整的代码展现了如何生成类和代码,并提供了如何定制您自己版本的建议。还没有看过第一篇吗?第一篇,“对象,无处不在的对象”, 解释了数据绑定是如何将 XML 和 Java 语言对象互为转换。它比较了数据绑定和其它在 Java 程序中处理 XML 的方法,并介绍了一个 XML 配置文档示例。第一部分也介绍了使用 XML Schema 来约束数据。
还没阅读本系列的第一部分吗?第一篇文章,对象,无处不在的对象解释了在将 XML 转换为 Java 语言对象然后又转换回来时,数据绑定所起的作用。它将数据绑定与 Java 应用程序中处理 XML 的其它方法相比较,并查看样本 XML 配置文档。第一部分还阐述了使用 XML schema 的数据约束。
在深入 Java 程序和 XML 代码之前,先快速回顾一下本系列第一部分所打下的基础。
在第一部分中,我们知道只要可以标识文档的一组约束,就可以将文档转换成 Java 对象。那些约束为数据提供了接口。如 Web 服务配置文档示例中所示,XML 文档应当成为现有 Java 类的一个实例,并且从数据约束生成那个类。最后,会看到表示样本 XML 文档约束的 XML schema。
如果对细节还有疑问,请回顾第一篇文章。
打造基础
现在,可以着手从 XML schema 创建 Java 类。该类必须准确表示数据约束,并提供 Java 应用程序将使用的简单读方法和写方法。开始之前,让我们先回顾清单 1,查看为 WebServiceConfiguration 文档定义的 XML schema。
清单 1. 表示 Web 容器配置文档数据接口的 XML schema <?xml version="1.0"?>
<schema targetNamespace="http://www.enhydra.org"
xmlns="http://www.w3.org/1999/XMLSchema"
xmlns:enhydra="http://www.enhydra.org"
>
<complexType name="ServiceConfiguration">
<attribute name="name" type="string" />
<attribute name="version" type="float" />
</complexType>
<element name="serviceConfiguration" type="ServiceConfiguration" />
<complexType name="WebServiceConfiguration"
baseType="ServiceConfiguration"
derivedBy="extension">
<element name="port">
<complexType>
<attribute name="protocol" type="string" />
<attribute name="number" type="integer" />
<attribute name="protected" type="string" />
</complexType>
</element>
<element name="document">
<complexType>
<attribute name="root" type="string" />
<attribute name="index" type="string" />
<attribute name="error" type="string" />
</complexType>
</element>
</complexType>
<element name="webServiceConfiguration" type="WebServiceConfiguration" />
</schema>
术语解释
数据绑定。从 Java 应用程序内部访问 XML 数据的一种新方法,使用仍在开发中的一种 API,JSR-031。
显式类型。具有类型属性的 complexType 元素。schema 中的显式类型成为生成的 Java 代码中的接口名称。
隐式类型。不具有类型属性的 complexType 元素。这种情况下,接口名称由 SchemaMapper 生成。
JSR-031。Sun 仍在开发中的一种新的 Java 规范请求。它用于将 XML 文档编译成一个或多个 Java 类,而在 Java 应用程序中可以方便地使用这些 Java 类。
打包。 将 Java 对象转换为 XML 表示,拥有当前值。
解包。 根据 XML 对象创建 Java 对象,通常是根据打包生成一个 Java 对象。
生成代码
开始生成 Java 代码之前,首先必须确定核心类的名称。将使用 org.enhydra.xml.binding 包中的 SchemaMapper,它是 Enhydra 应用服务器实用程序类集合的一部分。还可以将任何必需的支持类放到这个包中。
除了类名称以外,还必须确定用来读取和创建 XML 的 Java API。如上一篇文章中所讨论过的,三种主要选择是 SAX、DOM 和 JDOM。由于 SAX 仅仅适用于读取 XML 文档,因此它不适合创建 XML。由于在打包阶段中要将 Java 对象转换为 XML 表示,因此在此阶段中需要创建 XML。这就将选择的范围缩小到 DOM 和 JDOM。在这两种选择都可用的情况下,本例中我选择使用 JDOM API,仅为了显示其功能性(并不仅仅因为我是它的合著者之一!)。
最后,必须指出如何将 XML schema 提供给 SchemaMapper 类。通常,可以假设类的生成是脱机完成的(通过静态 main 方法)。仅通过使 main 方法调用非静态方法,还可以从运行时环境中使用类。做了这些决定后,就可以开始勾画类的框架了。
组装 SchemaMapper 类框架
要做的第一件事就是为要生成的代码设置一些基本存储器。必须能够从每个执行映射的 XML schema 生成多个接口和实现。Java HashMap 正好满足要求。键是接口或实现名称以及映射表中的值,该值是将要输出到新 Java 程序文件的实际代码。还需要存储每对接口/实现的属性(属性是在这两种类之间共享的)。这里,我再次使用 HashMap。其中,键是接口名称。但是,由于每个接口可能有多个属性,因此该值是另一个具有属性及其类型的 HashMap。最后,必须存储 XML schema 的名称空间,因为 JDOM 将使用这个名称空间来访问 XML schema 中的结构。所有这些具体信息都足以初步勾画出新类的框架,新类在清单 2 中。
还请注意在清单 2 中已添加了两个需要使用的基本方法:其中一个方法需要使用 XML schema 的 URL 来执行生成(允许它在网络可访问 schema 以及本地 schema 下运行),另一个方法将类输出到指定的目录中。最后,简单的 main 方法将 XML schema 看作一个变量,然后执行生成。
清单 2. SchemaMapper 类的框架 package org.enhydra.xml.binding;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
// JDOM classes used for document representation
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.NoSuchAttributeException;
import org.jdom.NoSuchChildException;
import org.jdom.input.SAXBuilder;
/**
* <p>
* <code>SchemaMapper</code> handles generation of Java interfaces and classes
* from an XML schema, essentially allowing data contracts to be set up
* for the binding of XML instance documents to Java objects.
* </p>
*
* @author Brett McLaughlin
*/
public class SchemaMapper {
/** Storage for code for interfaces */
private Map interfaces;
/** Storage for code for implementations */
private Map implementations;
/** Properties that accessor/mutators should be created for */
protected Map properties;
/** XML Schema Namespace */
private Namespace schemaNamespace;
/** XML Schema Namespace URI */
private static final String SCHEMA_NAMESPACE_URI =
"http://www.w3.org/1999/XMLSchema";
/**
* <p>
* Allocate storage and set up defaults.
* </p>
*/
public SchemaMapper() {
interfaces = new HashMap();
implementations = new HashMap();
properties = new HashMap();
schemaNamespace = Namespace.getNamespace(SCHEMA_NAMESPACE_URI);
}
/**
* <p>
* This is the "entry point" for generation of Java classes from an XML
* Schema. It allows a schema to be supplied, via <code>URL</code>,
* and that schema is used for input to generation.
* </p>
*
* @param schemaURL <code>URL</code> at which XML Schema is located.
* @throws <code>IOException</code> - when problems in generation occur.
*/
public void generateClasses(URL schemaURL) throws IOException {
// Perform generation
}
/**
* <p>
* This will write out the generated classes to the supplied stream.
* </p>
*
* @param directory <code>File</code> to write to (should be a directory).
* @throws <code>IOException</code> - when output errors occur.
*/
public void writeClasses(File dir) throws IOException {
// Perform output to files
}
/**
* <p>
* This provides a static entry point for class generation from
* XML Schemas.
* </p>
*
* @param args <code>String[]</code> list of files to parse.
*/
public static void main(String[] args) {
SchemaMapper mapper = new SchemaMapper();
try {
for (int i=0; i<args.length; i++) {
File file = new File(args[i]);
mapper.generateClasses(file.toURL());
mapper.writeClasses(new File("."));
}
} catch (FileNotFoundException e) {
System.out.println("Could not locate XML Schema: ");
e.printStackTrace();
} catch (IOException e) {
System.out.println("Java class generation failed: ");
e.printStackTrace();
}
}
}
在清单 2 中,可以看到对于每个作为自变量传递的 XML schema,main 方法都调用生成过程。首先,方法会生成类。将文件名转换为 URL,并传递到 generateClasses(URL schemaURL)。然后,通过 writeClasses(File dir) 方法将类写到当前目录中(转换成 Java File: new File("."))。
任何其它 Java 类都可以在运行时进行相同的调用,并生成类。例如,一个定制类装入器也许能发现需要打包,确定仍要生成的接口和实现,并使用 SchemaMapper 类来执行该任务。所有这一切都在运行时完成。因为 generateClasses() 方法需要一个 URL,所以在网络上使用这个类非常简单。例如,可以使用它来请求从 HTTP 上公开可用的 XML schema 生成类。
由于对如何使用类做了尽量少的假设,因此它是一个普通类;程序可以同时在本地和远程使用它。并且这个类可以当作一组 Java 语言和 XML 实用程序类的一部分,而不是必须以某种特殊形式使用的专用类。这种可重用性原则对 XML 特别关键,因为在不同系统上进行网络访问和通信是 XML 的基本前提。
生成类
构建好类的框架后,就可以添加类的主体了。
我已经提到过生成过程具有递归性质。请记住这一点,需要填充 generateClasses() 方法才能开始。可以使用 JDOM 读取 XML schema,然后从 schema 中抽取每个 complexType 元素。对于这些元素中的每一个,如清单 3 所示,递归进程从 handleComplexType() 调用处开始(以后将进一步讨论)。
清单 3. generateClasses() 方法 public void generateClasses(URL schemaURL) throws IOException {
/**
* Create builder to generate JDOM representation of XML Schema,
* without validation and using Apache Xerces.
*/
SAXBuilder builder = new SAXBuilder();
try {
Document schemaDoc = builder.build(schemaURL);
// Handle complex types
List complexTypes = schemaDoc.getRootElement()
.getChildren("complexType",
schemaNamespace);
for (Iterator i = complexTypes.iterator(); i.hasNext(); ) {
// Iterate and handle
Element complexType = (Element)i.next();
handleComplexType(complexType);
}
} catch (JDOMException e) {
throw new IOException(e.getMessage());
}
}
为简便起见,将强调一些重点,而不是详细阐述将 schema 转换为 Java 类的整个过程。可以联机查看完整的 SchemaMapper 类,或者可以下载它。
生成器必须确定在 XML schema 中找到的每个 complexType 元素是显式的(具有“类型”属性),还是隐式的(没有“类型”属性)。如果类型是显式的,则类型将成为接口名称,并且首字母大写。如果类型是隐式的,那么将根据特性名称构造接口名称。清单 4 中显示了处理这个逻辑的代码段。(如要了解更多数据绑定的定义,请参阅侧栏,术语解释。)
清单 4. 确定接口名称 // Determine if this is an explict or implicit type
String type = null;
// Handle extension, if needed
String baseType = null;
try {
// Assume that we are dealing with an explicit type
type = complexType.getAttribute("name")
.getValue();
} catch (NoSuchAttributeException e) {
/*
* It is safe with an implicit type to assume that the parent
* is of type "element", has no "type" attribute, and that we
* can derive the type as the value of the element's "name"
* attribute with the word "Type" appended to it.
*/
try {
type = new StringBuffer()
.append(BindingUtils.initialCaps(
complexType.getParent()
.getAttribute("name")
.getValue()))
.append("Type")
.toString();
} catch (NoSuchAttributeException nsae) {
// Shouldn't happen in schema-valid documents
throw new IOException("All elements must at have a name.");
}
}
因此,根据代码中的命名约定,具有 ServiceConfiguration 类型的元素将生成名为 ServiceConfiguration 的 Java 接口。名为 port 但没有显式类型的元素将生成叫做 PortType 的 Java 接口。它采用元素名称 (port),将首字母转成大写 (Port),再加上单词 Type,就得到了 PortType。
同样,所有实现类都使用接口名称,然后添加缩写 Impl。所以,最终实现类是 ServiceConfigurationImpl 和 PortTypeImpl。
使用这些命名约定,您可以很容易地确定将数据约束映射到 Java 接口会得到哪些 Java 类。如果设置了应用程序在运行时装入类,那么类装入器或其它实用程序可以迅速确定是否已装入了所需的类。类装入器或实用程序只要从 XML schema 中找出生成的类名称,然后尝试装入它们就可以了。命名逻辑是事先确定的,因此检查起来非常方便。
一旦确定了名称,就可以生成接口和实现类的框架(请参阅清单 5)。
清单 5. 生成代码 StringBuffer interfaceCode = new StringBuffer();
StringBuffer implementationCode = new StringBuffer();
/*
* Start writing out the interface and implementation class
* definitions.
*/
interfaceCode.append("public interface ")
.append(interfaceName);
// Add in extension if appropriate
if (baseType != null) {
interfaceCode.append(" extends ")
.append(baseType);
}
interfaceCode.append(" {\n");
implementationCode.append("public class ")
.append(implementationName);
// Add in extension if appropriate
if (baseType != null) {
implementationCode.append(" extends ")
.append(baseType)
.append("Impl");
}
implementationCode.append(" implements ")
.append(interfaceName)
.append(" {\n");
// Add in properties and methods
// Close up interface and implementation classes
interfaceCode.append("}");
implementationCode.append("}");
实际上,生成属性和方法是相当简单的。将接口和相应实现的名称添加到类的存储器中,然后是右花括号,它们的作用是结束类。像这样成对生成类,而不是单独生成类,将使同时在接口和实现反映出该过程变得简单。检查源代码(请参阅参考资料),就可以得到足够的解释。
清单 5 中的粗体注释表示源列表中的多行代码。在这里精简代码是为了保持简洁。对于正在创建的 XML schema 的每个特性(由 schema attribute 表示),都会将读方法和写方法添加到接口和实现(实现还有执行方法逻辑的代码)。同时,将为实现类的代码添加变量。
最终结果就是本系列第一部分中生成的类。可以在这里查看它们,或者与本文中的其余代码一起下载(请参阅参考资料):
ServiceConfiguration.java
ServiceConfigurationImpl.java
PortType.java
PortTypeImpl.java
DocumentType.java
DocumentTypeImpl.java
WebServiceConfiguration.java
WebServiceConfigurationImpl.java
有两个辅助程序类也将参与类生成:
BindingUtils,将首字母变成大写。虽然,可以将这个方法添加到生成器类,但我打算以后在打包和解包类时再使用该方法,所以我将它归到这个辅助程序类中。可以联机查看 BindingUtils,或者可以下载它。
DataMapping,SchemaMapper 类用来转换数据类型。可以联机查看源码或者下载源码。
完成包
如许多其它开放源码软件,在这里显示的数据绑定包是一项正在进行中的工作。虽然它已经初具规模,但仍有很大空间可用于添加更多功能和做改进。因此,以这段代码为基础,可以有许多方式应用程序中加以衍生。
可以重新使用该样本代码,以将 XML schema 的数据约束转换为类型安全的 Java 接口和实现。例如,迄今为止,示例代码还没有处理 XML schema 中可能指定的范围。而对于许多 XML 开发人员,那些数据范围才是使用 schema 的真正原因。然后,请考虑清单 6 中 Web 服务的扩充 XML schema。
清单 6. 带扩充约束的 Web 服务配置 <?xml version="1.0"?>
<schema targetNamespace="http://www.enhydra.org"
xmlns="http://www.w3.org/1999/XMLSchema"
xmlns:enhydra="http://www.enhydra.org"
>
<complexType name="ServiceConfiguration">
<attribute name="name" type="string" />
<attribute name="version" type="float" />
</complexType>
<element name="serviceConfiguration" type="ServiceConfiguration" />
<complexType name="WebServiceConfiguration"
baseType="ServiceConfiguration"
derivedBy="extension">
<element name="port">
<complexType>
<attribute name="protocol" type="string" />
<attribute name="number">
<simpleType base="integer">
<minExclusive value="0" />
<maxInclusive value="32767" />
</simpleType>
</attribute>
<attribute name="protected" type="string" />
</complexType>
</element>
<element name="document">
<complexType>
<attribute name="root" type="string" />
<attribute name="index" type="string" />
<attribute name="error" type="string" />
</complexType>
</element>
</complexType>
<element name="webServiceConfiguration" type="WebServiceConfiguration" />
</schema>
清单 6 说明了 number 属性的类型,并且在用红色强调的几行中指定了值的合法范围(1 到 32,767)。当前版本的 SchemaMapper 将忽略这些附加声明。从 schema 创建 Java 接口和实现类时,没有必要处理 XML schema 中的 minXXX 和 maxXXX 关键字,但它们可以增加相当多的附加验证。
请查看清单 7 中的代码示例,这些代码是可在实现类中生成的代码,以确保只有 schema 指定范围中的值可以作为变量。
清单 7. 带范围检查的生成代码 public class PortTypeImpl implements PortType {
private String protocol;
private int number;
private String protected;
public void setNumber(int number) {
if ((number > 0) && (number <= 32767)) {
this.number = number;
} else {
throw IllegalArgumentException("Argument must be greater than 0
and less than or equal to 32767");
}
}
public int getNumber() {
return number;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return protocol;
}
public void setProtected(String protected) {
this.protected = protected;
}
public String getProtected() {
return protected;
}
}
如果对类提供了非法值,那么清单 7 中的生成代码块将抛出一个运行时异常,这样既确保了类型安全性又确保了范围安全性。
可以很方便地将类似于清单 6 和清单 7 中的增强部分添加到我提供的基本代码中,因为本文中的所有代码完全都是开放源码。您也许还想加入 Enhydra 体系结构工作组邮件发送清单,在该清单中维护和讨论了该代码的未来版本和修订本。可以从 Enhydra Web 站点上加入该清单,列在本文的参考资料中。
总结
目前为止,应该已经了解什么是数据绑定。已知道使用数据绑定的原因,特别是配置信息。已经掌握如何创建 XML schema 和配置 Web 容器服务的 XML 实例文档,而且我们已经详细讨论了 org.enhydra.xml.binding.SchemaMapper 类。使用这个类,您可以创建 Java 接口和(该接口的)实现,它将管理从 XML 文档创建的 Java 实例。还知道如何将约束从 XML schema 映射到 Java。
现在,已经可以进入下一部分。在下一部分中,将开始把 XML 文档实际转换为 Java 对象的过程,其中 Java 对象是生成类的实例。下一篇文章将说明如何完成这个过程,及其逆向过程,以及 org.enhydra.xml.binding.Unmarshaller 和 org.enhydra.xml.binding.Marshaller 类。这两个类将磁盘上文本的 XML 格式数据移到内存中的 Java 表示,然后再移回来。
希望您能喜欢 XML schema 生成类,下次再见!
_____________________________________________________________
<来源 ibm developer/>
<摘录日期 2001/01/15 />
|