XMLBeans实例:地址簿 加入时间 2004-3-1 1:01:52 本站域名 www.code365.com 浏览统计 Total:215 | Year:215 | Month:13 | Day:1 XMLBeans介绍 XML已经迅速成为网络上事务处理的通用语言(lingua franca)。它是Web服务概念的基础,并且被广泛地应用在电子文档的交换中。用于处理Java应用中XML的第一代工具,基于文档对象模型(DOM)、XML的简单API(SAX)及XML解析器,分别完成各自的任务。然而,第一代技术没有充分利用Java语言的灵活性和强大性。 BEA Systems最近推出了一项叫做XMLBeans的新技术,它为在Java中处理XML提供更自然的、更直观的、更有力的处理机制。XMLBeans的名字是由XML(这很明显)加上JavaBeans得出来的,因为XMLBeans借用了JavaBeans组件体系结构中流行的属性样式。 当XMLBeans的8.0版本在2003年发布后,它的使用将贯穿整个WebLogic平台。但是这项技术本身能够单独运行,并且BEA已经发布了一项Web服务,允许在你选择的XML模式中试验XMLBeans。你提交模式(.xsd)文件(或是包含多个相关模式的ZIP文件),然后Web服务返回一个JAR文件,这个JAR文件包含针对模式的XMLBeans类以及支持XMLBeans API。稍后,我将在本文中演示怎样使用这项服务。你可以在BEA dev2dev开发者站点的XMLBeans Technical Track中阅读XMLBeans Service。 我在文中给出的例子论证了XMLBeans的一个独特的特征:XMLBeans模式编译器专为你的模式生成的强类型模式类型系统API。XMLBeans也被用于无模式的方案操作中。为得到更多的关于XMLBeans性能的内容,请参见XMLBeans Overview Page。 什么是XML模式? 虽然XML模式可以用于创建内容任意的XML文档,但它更感兴趣的(而且必需)是定义特定类型的文档。例如,如果每个公司都能为购买订单定义它自己的电子文档格式,电子商业将不会走得很远。XML支持正式文档类型的定义,在这些类型的基础上可以验证其他任何特定的文档。 在XML还不成熟的时候,有人在文档类型定义(DTD)文件中定义了一个文档类型。在DTD文档中,你可以指定能够存在于实例文档中的元素类型,元素必须被安排好,对于数值元素来说,还必须有值的范围样的限制。但是DTD文档也有许多缺点,最大的缺点是DTD文档用一种离奇的语言而不是用XML语言本身来表达。 现在定义XML文档的方法是使用XML模式。XML模式的用途和DTD文档一样,只不过是它用XML语言表达的。这意味着你可以用标准的XML工具创建、编辑以及操纵XML模式。 XML模式规范(可以在http://www.w3.org/XML/Schema中找到)还定义了一列可以用模式表达的46种特定数据类型,其中包括字符串、整型、浮点型、日期等更多的类型。因此,XML模式可以利用丰富的类型系统来反映了现代编程语言中可用的新技术。 本文描述的例子使用了两种类型的XML文档:联系人(contact)和地址簿(address book)。其中地址簿可以包含联系人。 contactUsa.xsd模式 XML模式通常存放在扩展名为.xsd的文件中,这是由于XML模式定义(XML Schema Definition)的首字母缩写为xsd。下面是一个来自contactUsa.xsd(本文提到的所有文件在AddressBookApp.zip中都可以找到)文件的XML模式例子。这个模式为地址簿中的条目(联系人)定义了格式。为简单起见,这个模式中用到的邮政地址都是按照美国的习惯表示的。 <xs:schema targetNamespace="http://dearjohn/address-book" xmlns:xs=http://www.w3.org/2001/XMLSchema xmlns:address-book="http://dearjohn/address-book" elementFormDefault="qualified"> <xs:element name="contact"> <xs:complexType> <xs:sequence> <xs:element name="family-name" type="xs:string"/> <xs:element name="given-name" type="xs:string"/> <xs:element ref="address-book:mailing-address" minOccurs="0" maxOccurs="2"/> <xs:element ref="address-book:phone-number" minOccurs="0" maxOccurs="3"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="mailing-address"> <xs:complexType> <xs:sequence> <xs:element name="address-line-1" type="xs:string"/> <xs:element name="address-line-2" type="xs:string" minOccurs="0"/> <xs:element name="city" type="xs:string"/> <xs:element name="state" type="address-book:state"/> <xs:element name="zipcode" type="address-book:zipcode"/> </xs:sequence> <xs:attribute name="location" type="xs:string"/> </xs:complexType> </xs:element> <xs:element name="phone-number"> <xs:complexType> <xs:sequence> <xs:element name="area-code" type="address-book:area-code"/> <xs:element name="local-phone-number" type="address-book:local-phone-number"/> </xs:sequence> <xs:attribute name="location" type="xs:string"/> </xs:complexType> </xs:element> <xs:simpleType name="area-code"> <xs:restriction base="xs:string"> <xs:pattern value="[0-9]{3}"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="local-phone-number"> <xs:restriction base="xs:string"> <xs:pattern value="[0-9]{3}-[0-9]{4}"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="state"> <xs:restriction base="xs:NMTOKEN"> <xs:enumeration value="AL"/> <xs:enumeration value="AK"/> ... <xs:enumeration value="WI"/> <xs:enumeration value="WY"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="zipcode"> <xs:restriction base="xs:string"> <xs:pattern value="[0-9]{5}(-[0-9]{4})?"/> </xs:restriction> </xs:simpleType> </xs:schema> 在XML方案中有两种基本类型的元素:简单类型和复杂类型。简单类型定义的元素有值但没有子元素,复杂类型定义的元素可以有子元素。在上面的模式中,<contact>被定义为一个复杂元素,该元素可以(在本模式中实际上必须)有子元素<family-name>和<given-name>,还可以有类型<mailing-address>和<phone-number>的子类型。 我不想在XML模式上用太多的篇幅,它是一个复杂的主题而且关于这个主题有许多好书可以利用。但是这里有一些关于定义在contactUsa.xsd中模式的声明,即使你不熟悉XML模式,它也有助于你理解。下面是这些声明: contactUsa.xsd中定义的所有元素都在XML名字空间http://dearjohn/address-book中。targetNamespace声明定义了用于该模式(在schema-speak中叫做"全局"元素)中顶级元素的命名空间。 elementFormDefault="qualified"规范意味着本方案中的所有非全局性元素也在目标命名空间中(只要它们没有明确说明位于其他的命名空间)。在模式文件中指定elementFormDefault="qualified"几乎总是一个好主意:你一般不想让同一个模式中的全局性和非全局性元素处于不同的命名空间。 <contact>总是包括名称字段,并且可以包含最多两个<mailing-address>元素和最多三个<phone-number>元素,这是可选的。 <mailing-address>必须总是按照顺序包含<address-line-1>、<city>、<state>和<zipcode>子元素。<mailing-address>可以在子元素<address-line-1>后紧跟一个子元素<address-line-2>。 <mailing-address>和<phone-number>元素都有位置属性,然而在这个模式中没有表示出来,目的是为了让位置容纳类似于"home"和"work"这样的值。 所有<area-code>、<local-phone-number>和<zipcode>元素的值被限制为特定的文本样式。 所有<state>元素的值被限制在50个州的缩写加上哥伦比亚区的列举范围内。 下面的XML文档遵照的是contactUsa.xsd模式: <?xml version="1.0"?> <contact xmlns="http://dearjohn/address-book"> <family-name>Smithers</family-name> <given-name>Bill</given-name> <mailing-address location="home"> <address-line-1>1500 Dexter Ave.</address-line-1> <city>Seattle</city> <state>WA</state> <zipcode>98109</zipcode> </mailing-address> <mailing-address location="work"> <address-line-1>501 Pike St.</address-line-1> <address-line-2>Suite 2900</address-line-2> <city>Seattle</city> <state>WA</state> <zipcode>98101</zipcode> </mailing-address> <phone-number location="home"> <area-code>206</area-code> <local-phone-number>441-1695</local-phone-number> </phone-number> <phone-number location="mobile"> <area-code>206</area-code> <local-phone-number>778-9218</local-phone-number> </phone-number> </contact> addressBook.xsd模式 除了上面描述的contactUsa.xsd模式外,文中后面给出的地址簿的例子使用在addressBook.xsd中定义的如下模式: <xs:schema targetNamespace="http://dearjohn/address-book" xmlns:xs=http://www.w3.org/2001/XMLSchema xmlns:address-book="http://dearjohn/address-book" elementFormDefault="qualified"> <xs:include schemaLocation="contactUsa.xsd"/> <xs:element name="address-book"> <xs:complexType> <xs:sequence> <xs:element ref="address-book:contact" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> 这个模式非常简单:它定义了一个<address-book>元素,此元素可以包括0或来自contactUsa.xsd模式的更多<contact>元素。 用XMLBeans服务生成XMLBeans 既然已经定义了这些模式,就可以使用在线XMLBeans服务来针对模式生成XMLBeans类型系统(Java API)。 由于有两个相关的模式文件,那么我可以把它们压缩到一个ZIP文件中去,以便上传。然后在XMLBeans服务Schema Upload Page中的"要上传的模式或ZIP文件"域指定ZIP文件。服务编译完模式之后,XMLBeans服务下载页就出现了,从这里我可以下载包括我的XMLBeans类型的JAR文件。默认文件名是xsdTypes.jar,但是在下载过程中,我将它重命名为bookTypes.jar。另外,我需要包含通用的XMLBeans支持代码的xmlbeans.jar文件。 那就它的全部东西了。你提供一个或多个模式,XMLBeans服务就返回一个包含你的类型的JAR文件。现在我准备创建一个地址簿应用,该应用处理来自于Java的XML地址簿和联系人。 AddressBookApp应用 AddressBookApp Java应用是一个简单的命令行应用。它提供了一个具有如下选项的菜单: 1. 打开地址簿 2. 打印所有联系人姓名 3. 打印所有联系人 4. 打印联系人 5. 根据姓名查找联系人 6. 根据地区码查找联系人 7. 根据州查找联系人 8. 从文件中添加联系人 9. 删除联系人 10.保存地址簿 11.退出 我将讲述上述这些功能的亮点,并利用代码片段来解释XMLBeans是如何被用来实现这些操作的。有些操作我没有提到,因为多个"打印"和"查找"操作彼此之间非常相似。 打开地址簿 此项操作的核心位于AddressBookApp的loadAddressBookFile方法中,其中一些代码如下所示: File bookFile = null; AddressBookDocument doc = null; AddressBook book = null; bookFile = new File(bookFilename); doc = (AddressBookDocument) XmlLoader.load(bookFile); book = doc.getAddressBook(); XmlLoader是基础XMLBeans类之一,它的加载方法是可以将XML文档读入XMLBeans类型系统的方式中的一种,利用这种方法可以通过XMLBeans API操作XML。上面的代码展示了一种使用XMLBeans加载XML文档的典型模式:调用XmlLoader的一个方法并且把结果强制转换到你正在加载的文档类型。在本例中是AddressBookDocument,这种类型代表一个与addressBook.xsd模式一致的XML文档。 当我第一次使用XMLBeans时,经常在这个地方遇到ClassCastException运行时异常的问题。这个异常总是意味着一种情况:指定的文档不是一个有效的模式实例,强制转换的目标类型(在我的例子中是AddressBookDocument)就是在从这里生成的。如果你遇到过这种错误,不要逐个模式地关注你尝试加载的实例文档的结构。要特别注意在模式和实例文档两者中的命名空间声明。利用像XML Spy这样的第三方模式敏感(schema-aware)XML工具非常有用。 一旦文档被成功地加载和强制转换,我就可以只调用AddressBookDocument的getAddressBook,来获得表示文件中<address-book>元素的XMLBean。我获得的AddressBook Java对象是应用程序中所有其他操作执行的对象。 注意,当我使用基于文件的方法加载XML文档时,XMLBeans也提供了从流中加载XML文档的方法。 打印所有联系人姓名 该操作调用AddressBookApp的 printAddressBookNames的方法,核心代码如下所示: Contact contact = null; int numRecs; int i; numRecs = book.sizeOfContactArray(); for (i=0; i<numRecs; i++) { contact = book.getContactArray(i); System.out.println((i+1) + ": " + getFormattedContactName(contact)); } Contact是表示<contact>元素的XMLBeans Java类型。我调用AddressBook类的 sizeOfContactArray方法来找出有多少<contact>元素以<address-book>元素的子元素存在,其中<address-book>元素由book变量表示。然后对所有<contact>元素都照此办理,为每个联系人打印AddressBookApp的getFormattedContactName方法的结果。getFormattedContactName包括下列代码: String familyName = contact.getFamilyName(); String givenName = contact.getGivenName(); return new String( ((familyName == null) ? "[no family-name]" : familyName) + ", " + ((givenName == null) ? "[no given-name]" : givenName)); 最重要的部分是头两行,在这两行里我调用Contact类的getFamilyName和getGivenName方法来获得<contact>元素的子元素<family-name> 和 <given-name>字符串的值。 联系人的列表在打印之前没有保存,把它留下给用户作为练习用。我可以给出一点提示,即XMLBeans的XQuery能力对于实现这一目的很有用。 希望你已经开始意识到XMLBeans是很直观的。生成的Java类型和在模式中定义的XML元素有同样的名称,这一点仅限于"java-fied"中。如果你熟悉XML文档的结构,那么就不难理解Java中XML文档的结构了。 根据名字查找联系人 这步操作会提示用户输入搜索字符串,然后打印出所有<address-book>中<contact>中的名字,在<contact>中包含了搜索字符串。代码如下: inputStr = getStringInput("Enter search string:").toLowerCase(); if (inputStr != null) { numContacts = _currentBook.sizeOfContactArray(); for (i=0; i<numContacts; i++) { contact = _currentBook.getContactArray(i); if (getFormattedContactName(contact). toLowerCase(). indexOf(inputStr) != -1) { System.out.println((i+1) + ": " + getFormattedContactName(contact)); } } } 这段代码非常简单。通过循环扫描<address-book>中的所有<contact>,检查对应各个联系人的getFormattedContactName返回的字符串是否包含了要搜索的那个字符串。这里之所以要使用getFormattedContactName函数,是因为这个函数可以方便地返回既包含了<family-name>元素又包含了<given-name>元素的字符串。注意,XMLBeans包含了XQuery功能,这样就可以使所有"查找"操作快速执行,不过,这些我要留到其他文章里讲。 从文件中添加联系人 这步操作会提示用户XML的文件名应该符合contactUsa.xsd模式,然后调用AddressBookApp的loadContactFile方法,这个方法和上面在"打开地址簿"部分所描述的loadAddressBookFile方法非常相似。 当新的<contact>元素被成功地载入到一个Contact XMLBeans对象中时,AddressBookApp的addNewContact方法会将其添加到<address-book>中,代码如下: int numRecs; int i; MailingAddress addr, newAddr; PhoneNumber phoneNum, newPhoneNum; Contact newContact = _currentBook.addNewContact(); newContact.setFamilyName(contact.getFamilyName()); newContact.setGivenName(contact.getGivenName()); numRecs = contact.sizeOfMailingAddressArray(); for (i=0; i<numRecs; i++) { newAddr = contact.getMailingAddressArray(i); addr = newContact.addNewMailingAddress(); if (newAddr.isSetLocation()) { addr.setLocation(newAddr.getLocation()); } if (newAddr.getAddressLine1() != null) { addr.setAddressLine1(newAddr.getAddressLine1()); } ... more of the same for other child elements } numRecs = contact.sizeOfPhoneNumberArray(); for (i=0; i<numRecs; i++) { newPhoneNum = contact.getPhoneNumberArray(i); phoneNum = newContact.addNewPhoneNumber(); if (newPhoneNum.isSetLocation()) { phoneNum.setLocation(newPhoneNum.getLocation()); } if (newPhoneNum.getAreaCode() != null) { phoneNum.setAreaCode(newPhoneNum.getAreaCode()); } if (newPhoneNum.getLocalPhoneNumber() != null) { phoneNum.setLocalPhoneNumber( newPhoneNum.getLocalPhoneNumber()); } } 这段代码用于替代XMLBeans的一个特性,在写这篇文章的时候,该特性还没有实现(XMLBeans当前还只有测试版的代码)。不过代码倒是很是简单:只是遍历树并传送来自元素及其属性的值,通过下面一行代码就可以最终实现这步操作: book.setContact(newContact); 删除联系人 这是所有操作中最简单的操作。首先提示用户需要删除<contact>的索引,接着调用_currentBook.removeContact(index-1)函数,该函数将从<address-book>元素中删除指定的<contact>子元素。这里之所以要将用户提供索引减1,是因为为了方便起见,应用程序的用户接口的索引是基于1的。 保存地址簿 最后一步操作将<address-book>元素的当前状态写入到一个由用户提供名称的文件中。下面是相应的代码: File bookFile = new File(inputStr); PrintStream destStream = new PrintStream(new FileOutputStream(bookFile), false, "UTF-8"); destStream.println(""); HashMap propMap = new HashMap(); propMap.put(XmlOptions.SAVE_PRETTY_PRINT, null); propMap.put(XmlOptions.SAVE_PRETTY_PRINT_INDENT, new Integer(2)); String xmlText = _currentBook.xmlText(propMap); destStream.println(xmlText); destStream.close(); 这里我使用了一般的Java IO类创建了一个输出流,以后,XML文档将要写入到这个输入流中。注意,这里我指定了必须用编码("UTF-8")编写这个文档。XML文档的符号编码是个比较复杂的话题,所有这些都牵涉到国际化的问题。UTF是用于一种通用的安全的编码方式。 接下来打印<?xml?>声明,这些声明都写在每个XML文档的开头,没有顺序要求。 下一步是生成实际的XML文本,该文本将被写入文件中。为完成这步操作,这里调用了XmlObject(所有XMLBeans类的基类)的 xmlText方法。xmlText方法通过选项的选项映像来控制它的行为。我使用SAVE_PRETTY_PRINT选项来请求人类可识别的格式化,并指定了SAVE_PRETTY_PRINT_INDENT选项,表示我希望有多种级别的XML结构以供选择,而不仅仅是只有两种选择。 最后,将XML文本写入到输出文件,接着关闭该文件。 编译AddressBookApp应用程序 运行应用程序是非常简单的: javac -classpath "bookTypes.jar;xmlbeans.jar" address\AddressBookApp.java (显然,在Unix/Linux中,符号"\"应改为"/") 在类路径中,惟一要添加的就是bookTypes.jar(即包含了按照我自己的模式生成的类型的JAR文件)和xmlbeans.jar(即包含了一般XMLBean支持的代码的JAR文件)。 注意,XMLBeans需要Java 2, Standard Edition (J2SE) 1.4环境的支持。 运行AddressBookApp应用程序 运行app也很简单: java -cp ".;./bookTypes.jar;./xmlbeans.jar" address.AddressBookApp ZIP文件里包含了实例代码,同时还包含了四个contact XML示例文件和两个示例地址名册:testbook.xml和fullbook.xml。testbook.xml仅包含在contactSmithers.xml中定义的联系人,fullbook.xml包含所有四个示例联系人。这些文件给了你足够的资料,通过这些资料你可以对应用程序中的所有可用操作进行试验。 结论 相信这个实例已经向我们展示了用XMLBeans处理XML是多么简单。我鼓励你下载实例代码并进行试验。然后用你自己的模式试一试。 我特意使这个实例相对简单一些,而更多地关注XMLBean根据你自己的模式所生成的特定模式的API。关于XMLBeans还有更多的内容,比如它可以操纵无相关模式的XML文档,可以使用XQuery在XML上执行复杂搜索和变换,还有其他一些功能。