UML软件工程组织

XMLBeans帮助业务流程开发
作者: 陈逸鹤

XMLBean 是一种 XML-Java 绑定工具,是早在Beehive之前,BEA 向 Apache 提交的另一个项目。到 2004 年 6 月为止, XMLBean 就已经作为一个羽翼已丰的 Apache Software Foundation Project 得到人们的接受。

XMLBean通过编译 XML Schema 而创建,把XML实例和底层架构映射为 JavaBean 风格的对象,开发人员可以通过 getter 和 setter 访问器方法轻松定位、检索和操作 XML 实例数据。最近,笔者正在参与国外某大型保险系统的开发,与大家分享使用XMLBeans的心得。这篇文章通过一个简化了的保险实例业务流程,介绍Java数据对象XMLBeans的强大之处。

实例场景:

这是一个关于保险业务的实例,我们将从外部得到一个描述保单信息的XML文件,然后我们调用一个Web Service方法来计算保单的保额、保费,并重新返回这个XML文件。接着,我们会取得一个描述保险产品的费率列表的XML文件。通过数据转换将两这个XML文件转换为系统最终保单XML文件输出。虽然实际的保险业务要复杂很多,但这个实例可以很好地说明XMLBeans在业务流程开发中的作用。

上图是在Weblogic Integration中定义的业务流程,Client Request表示一个客户端的请求。请求包含两个XML文件,一个描述了保单的基本信息,另一个则描述了保险产品的费率列表。calculatePremium是一个Web Service方法,流程通过调用这个方法累加保单标的(标的是指被保险的对象,一个保单由1个或多个标的组成)上的保额保费,从而计算出整个保单的保额保费。constructPolicy是一个数据转换方法,利用WebLogic Workshop 的 mapper 功能实现将保单信息与保险产品费率表两个XML文档进行数据转换,最终得到一个符合系统需求的保单XML文件。

现状:

在传统的方法中,我们一般会在系统内部构建Java对象,从外部获得XML文件后,通常我们要用XML分析器来提取相关的部分,把它们转化成Java的表示形式,然后通过系统使用这些Java对象,但实际上Java中许多服务的实现并没有使用XML分析器,而是分析了传入的HTTPInputStream并使用了StringTokenizer或此类方法来提取数据。这样做的直接后果便是在开发中产生了开销。

同时,用这种方法处理XML还非常容易出错。即使确实存在表示XML数据的XML架构,开发者也不一定会得到类型安全(type-safe)数据所带来的好处,因为使用导入导出方法时,只有当XML数据进入或离开系统时才会应用有效性检查。因此,当开发者可能会选择使用XML架构时,XML有效性检查只对该文档有效;对Java对象来说,开发者必须在setter方法中手动创建前置条件(pre-condition)和后置条件(post-condition)来检查数据。

处理XML所带来的开发开销和数据有效性检验的困难一起产生了第三个困难:相信许多开发者都与我有着相同的苦恼,数据的表示随着业务需求变化而不断的变化,修改架构的同时,导致对语法分析代码,XML生成代码和有效性检验代码进行重构,而这些代码可能分散在系统得许多Java对象中。因此,使用导入导出方法处理XML文档,无法跟上业务的变化。

XMLBeans解决方案:

XMLBeans通过把Java和XML作为整体来克服以上这些困难。通过利用XML Schema的功能来提供结构化和约束性数据类型,开发者可以像Java对象那样直接访问XML文档。通过使用XMLBeans,Java开发者不需要花时间来编写导入/导出和有效性检验代码。相反,XML文档被视作极类似于Java-Bean方式访问的最好的数据对象。

新建项目

新建一个流程项目来实现之前的业务流程。打开workshop,点击File->New->Project选择Process->Process Project,在Project name中输入项目名称XMLBeansApp,如图所示:

创建Schema

XMLBeans 的起点是 XML Schema。Schema(包含在 XSD 文件中)是 XML 文档,它定义了其他 XML 文档必须遵守的一组规则。XML Schema 规范提供了丰富的数据模型,使您可以表达复杂的结构,并对数据加以约束。例如,XML Schema 可以对文档中数据的顺序进行控制,或对特定的值加以约束(例如,保单录入日期必须大于 1900)。而在 Java 中,则通常要编写自定义代码才能强制实施此类规则。XMLBean 遵守 Schema 约束。

Weblogic Platform8.1 提供了对于XML的强化支持,主要是通过XMLBean来实现的,当用户通过Workshop,编写了XML的Schema后保存时,Workshop会自动按照Schema,生成XMLBean的子类,通过生成的内置类和方法,可以实现XML的解析和XML元素的生成、修改和访问。

在Schemas文件夹下,新建一个表示保单信息的Schema文件:PolicyInfo.xsd,我们可以在XMLSpy中编辑这个schema,如图所示:

得到对应的xml文件。

<?xml version="1.0"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:po="http://temp.openuri.org/XMLBeansApp/PolicyInfo"
targetNamespace="http://temp.openuri.org/XMLBeansApp/PolicyInfo"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="policy-info">
<xs:complexType>
<xs:sequence>
<!--保单ID-->
<xs:element name="policyID" type="xs:long"/>
<!--保险产品名称-->
<xs:element name="productName" type="xs:string"/>
<!--保险产品ID-->
<xs:element name="productID" type="xs:long"/>
<!--保单起期-->
<xs:element name="effDate" type="xs:dateTime"/>
<!--保单止期-->
<xs:element name="expDate" type="xs:dateTime"/>
<!--保单保额-->
<xs:element name="sumInsured" type="xs:double"/>
<!--保单保费-->
<xs:element name="premium" type="xs:double"/>
<!--客户-->
<xs:element name="customer" type="po:customer"/>
<!--标的列表,标的至少1个-->
<xs:element name="insured-item" type="po:insured-item" minOccurs="1" maxOccurs="unbounded"/>

</xs:sequence>
</xs:complexType>
</xs:element>
<!--客户-->
<xs:complexType name="customer">
<xs:sequence>
<!--客户名称-->
<xs:element name="name" type="xs:string"/>
<!--联系电话-->
<xs:element name="phone" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<!--标的-->
<xs:complexType name="insured-item">
<xs:sequence>
<!--标的名称-->
<xs:element name="name" type="xs:string"/>
<!--标的数量-->
<xs:element name="quantity" type="xs:int"/>
<!--单位保费-->
<xs:element name="sumInsured" type="xs:double"/>
<!--单位保额-->
<xs:element name="premium" type="xs:double"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

保存这个文件,在WorkShop中,系统将会自动生成这个Schema所对应的多个XMLBeans Class文件,类似结构如图所示:

可以看到,对应的Schema生成了Customer,InsuredItem,PolicyInfoDocument以及PolicyInfoDocument.PolicyInfo类。所有类的结构与Schema文件的名字无关,类的package与Schema的命名空间(xmlns:po="http://temp.openuri.org/XMLBeansApp/PolicyInfo)有关,接下来,我们就可以使用这些类来操作XML了。

在Web Service方法中使用XMLBeans来处理XML对象

XML最强大的功能就是可以使数据以一种结构化的,容易阅读的文档进行传递,实际上,这种功能会引起Web服务的增值。为了提供某种服务,供应商必须接受某种可读的文档而不是定义一种自定义的二进制数据格式。使用XMLBeans,我们可以将文档直接解析为对应的Java对象,这样就可以大大降低开发上的开销并且保证数据的有效性。

新建一个service文件夹,右键点击该文件夹,选择New->Web Service

在File name中输入PolicyService作为该Web Service的名称。

右键点击这个Web Service选择Add Method添加两个Web Service方法:calculatePremium,getAllInsuredItem用于计算保单保费和获取保单的标的列表。

final static String m_namespaceDeclaration =
"declare namespace xq='http://temp.openuri.org/XMLBeansApp/PolicyInfo'";
/**
* :operation
*/
public XmlObject[] getAllInsuredItem(XmlObject policyInfo)
{
String query = "/xq:policy-info/xq:insured-item";
String queryExpression =m_namespaceDeclaration+query;

return policyInfo.selectPath(queryExpression);
}

可以像从数据库检索数据一样,使用 XQuery 和 XPath 检索 XML 的特定部分。XQuery 和 XPath 提供一种语法,用来指定您感兴趣的元素和特性。XMLBean API 提供用来执行 XQuery 和 XPath 表达式的两个方法以及使用它们的两种不同方式。这两个方法是 selectPath 和execQuery,可以从 XmlObject(或从它继承的对象)或 XmlCursor 调用它们。这两个方法的结果略有不同。

上面这段代码从policyInfo.xml中返回表示标的列表的XmlObject数组。

/**
* :operation
*/
public PolicyInfoDocument calculatePremium(XmlObject policyInfo)
{
double si=0;
double premium=0;

PolicyInfoDocument poDoc = (PolicyInfoDocument)policyInfo;
PolicyInfo po = poDoc.getPolicyInfo();

//获取标的列表
InsuredItem[] insuredItems = po.getInsuredItemArray();
//累加标的保费,保额
for(int i=0;i<insuredItems.length;i++){
si = si+insuredItems[i].getSumInsured()*insuredItems[i].getQuantity();
premium = premium+ insuredItems[i].getPremium()*insuredItems[i].getQuantity();
}

po.setSumInsured(si);
po.setPremium(premium);
return poDoc;
}

上面这段代码说明了如何利用XMLBeans来处理XML,通过poDoc.getPolicyInfo(),我们直接解析XML文档对象并获取了代表保单的XMLBean,接着我们就可以像处理普通Java对象一样来处理XMLBean,我们遍历每个保单标的,然后累加它们的保额,保费,并将结果设置到保单对象上,最后我们返回这个设置了保额保费的保单XML对象poDoc。

测试Web Service方法

让我们测试一下这个Web Service,看看它是否能够正常工作。在WorkShop中测试Web Service非常方便,确保当前在Web Service视图中,直接点击Start按钮。

在测试页面的SOAP body:中粘贴如下测试XML文件

<?xml version="1.0" ?>
<xq:policy-info xmlns:xq="http://temp.openuri.org/XMLBeansApp/PolicyInfo">
<xq:policyID>100221</xq:policyID>
<xq:productName>PKG-CMD</xq:productName>
<xq:productID>255145</xq:productID>
<xq:effDate>2006-01-07T14:16:00-05:00</xq:effDate>
<xq:expDate>2007-01-07T14:16:00-05:00</xq:expDate>
<xq:sumInsured>0</xq:sumInsured>
<xq:premium>0</xq:premium>
<xq:customer>
<xq:name>edison chen</xq:name>
<xq:phone>66668888</xq:phone>
</xq:customer>
<xq:insured-item>
<xq:name>insuredA</xq:name>
<xq:quantity>1</xq:quantity>
<xq:sumInsured>5000</xq:sumInsured>
<xq:premium>50</xq:premium>
</xq:insured-item>
<xq:insured-item>
<xq:name>insuredB</xq:name>
<xq:quantity>2</xq:quantity>
<xq:sumInsured>1000</xq:sumInsured>
<xq:premium>10</xq:premium>
</xq:insured-item>
<xq:insured-item>
<xq:name>insuredC</xq:name>
<xq:quantity>1</xq:quantity>
<xq:sumInsured>8000</xq:sumInsured>
<xq:premium>80</xq:premium>
</xq:insured-item>
</xq:policy-info>

在测试XML文件中,可以发现<xq:sumInsured>0</xq:sumInsured>,<xq:premium>0</xq:premium>,表示保单的保额,保费都还是0。而三个标的项都有数据,点击calculatePremium按钮,测试计算保额保费的WebService方法。察看测试结果。

从Service Response中可以看到,sumInsured和premium变为了15000和150,分别是标的项累加计算的结果。这个Web Service方法,为我们完成了保单保额保费的计算,并且返回了一个XML文档,用于随后的操作。

使用XQuery实现数据转换

数据转换是指数据从一种格式向另一种格式的映射和转换。我们可以利用WebLogic Workshop 的 mapper 功能实现数据转换。一种数据转换可以具有多个源输入,但是只能有一个输出目标。例如,可以将数据从三个输入数据源转换到一个目标源,如下图所示。

在我们的实例场景中,保单信息XML(policy.xsd),和保险产品费率列表XML(quoteRate.xsd)是转换源,在保险产品费率列表中找到匹配保单信息中对应保险产品的费率信息。转换后得到一个新的XML结构(policy.xsd)的XML作为流程的最终输出。

创建转换文件

鼠标右键单击 process文件夹并从下拉菜单选择“新建”—>“转换文件”。

在“文件名”字段中,输入 policytran.dtf,点击Create创建转换文件,右键点击设计视图选择“Add Transformation Method”,添加转换方法,如图所示:

右键点击该方法,选择“Config XQuery Transformation”,分别选择policyInfo.xsd中的policy-info和quoteRate.xsd中的rate-quote作为转换源,选择policy.xsd的policy作为目标输出。打开转换设计视图,如图所示:

我们将两个XML文件关联起来,只需拖动两个文档对应的字段即可,这里是productID,其他字段的对应关系也只需通过拖动操作来完成。

让我们看一下,转换对应的xquery文件:

declare namespace ns0 = "http://temp.openuri.org/XMLBeansApp/PolicyInfo"
declare namespace ns1 = "http://temp.openuri.org/XMLBeansApp/RateQuote"
declare namespace ns2 = "http://temp.openuri.org/XMLBeansApp/Policy"


let -info1 := ,
-quote1 :=
where data(-info1/ns0:productID) = data(-quote1/ns1:product-rate[1]/ns1:productID)
return
<ns2:policy>
<ns2:policyID>{ data(-info1/ns0:policyID) }</ns2:policyID>
<ns2:productName>{ data(-info1/ns0:productName) }</ns2:productName>
<ns2:effDate>{ data(-info1/ns0:effDate) }</ns2:effDate>
<ns2:expDate>{ data(-info1/ns0:expDate) }</ns2:expDate>
<ns2:rate>{ data(-quote1/ns1:product-rate[1]/ns1:rate) }</ns2:rate>
<ns2:sumInsured>{ data(-info1/ns0:sumInsured) }</ns2:sumInsured>
<ns2:premium>{ data(-info1/ns0:premium) }</ns2:premium>
<ns2:pay>{ (-quote1/ns1:product-rate[1]/ns1:rate)*(-info1/ns0:premium) }</ns2:pay>
{
let := -info1/ns0:customer
return
<ns2:customer>
<ns2:name>{ data(/ns0:name) }</ns2:name>
<ns2:phone>{ data(/ns0:phone) }</ns2:phone>
</ns2:customer>
}
{
for -item1 in -info1/ns0:insured-item
return
<ns2:insured-item>
<ns2:name>{ data(-item1/ns0:name) }</ns2:name>
<ns2:sumInsured>{ (-item1/ns0:quantity)*(-item1/ns0:sumInsured) }</ns2:sumInsured>
<ns2:premium>{ (-item1/ns0:quantity)*(-item1/ns0:premium) }</ns2:premium>
</ns2:insured-item>
}
</ns2:policy>

打开TestView视图,填入测试数据,然后点击Test,可以直接测试这个转换方法。

结束语

XMLBeans早已成为Apache的开源项目,通过高性能、开放源代码的实现,XMLBeans克服了将XML和Java一起使用的传统困难。此外,它还提供了有效性检验和可执行规范的功能。通过保持XML文档和Java对象的同步,XMLBeans提供了面向服务开发所需的Java和XML的结合。随着SOA在越来越多应用中的实施,相信XMLBeans一定会得到更广泛的应用。

本文通过一个保险实例流程的开发过程,介绍了如何使用XMLBeans在Web Service方法中处理XML,以及借助WebLogic的mapper工具方便地进行数据转换。

本文示例下载:XMLBeansApp.zip


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