注意:您应该熟悉 WebSphere Studio Application Developer Integration Edition Version 5.1.1 Web 服务开发环境、ASP .NET Web 服务,并了解构建 BPEL 流程的知识。本文还带有 BPEL 业务流程的样本代码。
由于 XML 和 Web 服务需要使用 BPEL,它迅速成为面向服务体系结构(SOA)的基础,BPEL 还提供了 WebSphere Application Server Enterprise Process Choreographer(Process Choreographer)的公开标准。这些年来,许多企业应用程序都分别在 J2EE 和.NET 平台上被独立和并行的开发和部署。这些业务应用程序都设计有细粒度的业务功能。例如,在 J2EE 中,使用实体 bean 实现信息持久性,并使用会话 bean 实现业务逻辑。这些业务应用程序同样还在有限的业务域里提供集成框架,用于后端或是遗留企业应用程序的集成。举例来说,Java Connector Architecture(JCA)和 Java Messaging Service(JMS)都是典型的 J2EE 应用程序集成框架。
随着 Web 服务的出现,后端企业应用程序通过使用 WSDL 被公开为可发现并可调用的业务服务。WSDL 为 Web 服务接口定义了服务语义,例如操作、协议绑定和消息类型等。BPEL 层在 WSDL 之上,它指定参与流程流的复合 Web 服务的行为。因此,它使业务分析人员和架构师可以定义业务流程流的逻辑,并可以使用 BPEL 来支持与 J2EE Web 服务和 .NET Web 服务的长期运行的会话。
实际上,BPEL 流程流成功与否,基本取决于每个 Web 服务的 WSDL 文档中定义的 XML 服务语义。XML schema 使 XML 与其他文件格式区别开来,XSD 是 XML schema 定义,它是综合且复杂的数据类型定义系统。简单地说,XSD 定义了 XML 文档的外型。使用 XSD 设计简单且强类型(strongly-typed)的对象是 Web 服务互操作性的基础。“改进 J2EE 技术和 .NET 间的互操作性”(本系列的第 1 部分)指出,许多 Web 服务编程人员忽视了 XSD schema 设计的重要性。换句话说,即他们使用自己喜爱的编程语言来为 Web 服务实现编写代码,并随后用供应商的工具从实现中获得 Web 服务语义。这种自底向上的方法产生了有关互操作性的问题。
.NET 和 J2EE 之间的互操作性问题通常源于 XML 命名空间和复杂数据类型,例如嵌套的复杂类型数组以及日期和时间(本系列的第 2 部分和第 3 部分)。本文中讲述的技巧将展示在 BPEL 流程集成中如何在两个平台之间安全并正确的传递嵌套数组、复杂类型和日期。但这需要您为这些复杂类型仔细设计 XSD schema。
要构建流程,您需要在 Windows 中安装 IBM WebSphere Studio Application Developer V5.1.1 和 Microsoft .NET Framwork 1.1。对于本文来说,这两种产品都在同一台机器上安装并运行。.NET Visual Studio 是用来构建 .NET Web 服务的集成工具,在本技巧中不予使用。
IIS 的文档根目录在缺省情况下为 C:\Inetpub\wwwroot。我将使用该目录发布 .NET Web 服务。同时,.NET Framework 1.1 安装在 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 目录下,且 SDK 在 C:\Program Files\Microsoft.NET\SDK\v1.1 目录下。
将以下目录添至系统 PATH 变量:
- c:\Windows\Microsoft.NET\Framework\v1.1.4322
- c:\Program Files\Microsoft.NET\SDK\v1.1\Bin
BPEL 业务流程的样本代码在下载部分。
设想一个购买场景,购买者通过货物代理商来执行定购请求。货物代理商拥有许多提供货源的供应商;每个参与者都是独立的订户且拥有自己的产品库存管理系统。也就是说,某个供应商可能运行 J2EE Web 服务来管理其库存而其它供应商可能会使用 .NET Web 服务来进行相同的操作。
购买流程从向不同的供应商索取产品报价开始。在购买者提交订单之前,代理商与每个供应商联系以获取相应产品的报价,且每个供应商将返回该产品的详细信息。之后购买者将浏览信息并继续进行下个定购流程。图 1 展示了两个供应商(Supplier A 和 Supplier B)报价请求的流程图。购买者请求代理商提供报价,代理商将该报价请求传给各供应商,并随后将供应商提供的信息返回给购买者。
图 1.报价请求的流程图
在该图中:
- Buyer 是提出购买请求的客户端。
- Agent 是业务流程,请求供应商提供产品信息并处理购买者的订单。
- Supplier A 是 Java Web 服务,管理供应商 A 的库存。
- Supplier B 是 .NET Web 服务,管理供应商 B 的库存。
对于购买者提出的购买请求,代理流程将首先对各供应商构造产品报价请求。各供应商作出反应,提供相应的产品信息,包括价格、数量和其它产品信息。代理随后将这些产品信息返回给购买者以供浏览和定购。
在接下来的章节中,我们将构建代理流程,为 Supplier A 构建 Java Web 服务,并为 Supplier B 构建 .NET Web 服务。
在本系列的第 1 部分中,您将发现,常被忽视的最重要的最佳实践之一是用于 Web 服务的 XML schemas 和 WSDL 设计。编程人员通常用自己喜爱的编程语言开始构建 Web 服务,然后使用供应商工具获取 Web 服务语义并公开 WSDL。这就是所谓的自底向上的方法——它并不考虑 Web 服务是围绕消息进行的,且数据和消息类型都必须设计得很谨慎而不是简单地用工具生成。本部分将展示如何使用 WebSphere Studio Application Developer Integration Edition Version 5.1.1 为 Web 服务设计 XML schema 和 WSDL。
对于 Supplier A,库存服务必须执行至少两个操作:getQuote 用来返回库存中的产品信息,fulfillOrder 用来执行来自代理的定购请求。下文中的图 2 展示了该服务接口的流程。
图 2. Supplier A Web 服务接口图
Product 是一个 complexType,有四个属性;每个属性都是 基本数据类型。本部分将展示如何在 WebSphere Studio Application Developer Integration Edition Version 5.1.1 中利用 XML schema 编辑器来用 XML schema 语言定义产品类型。下个部分将展示如何设计 .NET Web 服务的复合 Product complexType 来演示互操作性。
首先,创建服务项目和包,用来为所有伙伴 Web 服务和流程定义保存 XML schema 文件和 WSDL。
- 启动新的 WebSphere Studio Application Developer Integration Edition Version 5.1.1 工作区。
- 选择 File>New>Service Project。
- 指定服务项目名称为
QuoteProcessService,并点击 Finish。 - 右键单击
QuoteProcessService项目并选择 New>Package。 - 输入新 Java 包的名称
quote.process并单击 Finish。
接下来,创建新的 XML 并定义 Product complexType。
- 右键单击
quote.process包并选择 New>XML schema。 - 输入新的 schema 名称
SupplierASchema.xsd并单击 Finish。打开 XML schema 编辑器。 - 在 Outline 视图中,选择
SupplierASchema.xsd。 - 在 Schema 面板中,输入
http://schema.a.supplier作为命名空间 URI。这将作为 schema 的目标命名空间。点击 Apply。 - 在 Outline 视图中,右键单击
SupplierASchema.xsd并单击Add Complex Type。 - 在 Outline 视图中,选择
NewComplexType。 - 在 Complex Type 面板中,输入复杂类型的名称:
Product。 - 在 Outline 视图中,右键单击
Product类型并单击Add Content Model。 - 在 Outline 视图中,扩展
Product。 - 右键单击 content model icon 并单击
Add Element。添加NewElement。 - 在 Outline 视图中,选择
NewElement. - 在 Element 面板中,将
NewElement重新命名为_name并将类型设置为xsd:string。
请注意步骤 4,schema 的目标命名空间设置为 http://schema.a.supplier 而不是 http://a.supplier/schema。这是 JAX-RPC 行为的结果:JAX-RPC 将使用 URI 的域名部分来生成可序列化产品类的包名。为避免潜在的命名冲突,命名空间 URI 的域名部分应该尽可能的细粒度,如"改进 J2EE 技术和 .NET 间的互操作性,第 3 部分"所示。另外一个原因是如果序列化器类的包名是 supplier.a ,那么当接收到命名空间限定的产品对象({http://a.supplier/schema}Product)时,一些客户端查询反序列化器会失败。
重复最后三个步骤,创建 Product 类型的其它元素并保存 XSD 文件。
-
_price: xsd:float -
_qty: xsd:int -
_refurbished: xsd:boolean
您可以为 Supplier A 设计 WSDL 并定义服务方法(操作)和消息绑定。在报价流程中,仅需 getQuote 操作。
首先,创建空 WSDL,并导入在之前步骤中定义的产品 schema。
- 右键单击
quote.process包。选择 New>Other>Web Services>WSDL 并单击 Next。 - 输入新的 WDSL 名
SupplierAService.wsdl并单击 Next。 - 在向导中,设置目标命名空间为
http://a.supplier/service/并单击 Finish。打开 WSDL 编辑器。 - 在 Outline 视图中,右键单击 Imports 并选择 Add Child>Import。
- 在 Import 面板中,单击在位置 box 旁边的 push button。
- 浏览到
SupplierASchema.xsd并单击 OK。
Product complexType 被导入到命名空间 http://schema.a.supplier 下的 WSDL 中。
接下来,创建消息和消息部件。对于每次 getQuote 调用,将会产生请求消息传送产品名称,以及响应消息返回来自库存的 Product 对象。
- 在 Outline 视图中,右键单击 Messages 并选择 Add Child>Message。
- 输入名称
getQuoteRequest。 - 右键单击新创建的
getQuoteRequest并选择 Add Child>Part。 - 将新消息部件命名为
productName并单击 OK。
请注意,消息部件名称的缺省类型为 xsd:string。接受该缺省类型。现在重复以上步骤。用消息部件 product 创建新的消息 getQuoteResponse,该消息是导入的 SupplierASchema.xsd 中已定义的 Product 类型。
- 右键单击
Product消息部件并单击 Set Type。 - 选中 Select Existing Type 单选框,并选择
xsd1:Product,如以下图 3 所示。单击 Finish。
图 3. 指定 product 消息部件类型
接下来,定义 Port Type,所有的服务操作都在此处被展示。在该场景中,只定义了 getQuote 操作。
- 在 Outline 视图中,右键单击
Port Types并选择 Add Child>Port Type。 - 将新端口类型命名为
SupplierAQuotePortType并单击 OK。 - 右键单击
SupplierAQuotePortType并选择 Add Child>Operation。 - 输入新操作名
getQuote并单击 OK。 - 右键单击
getQuote操作并选择 Add Child>input。 - 右键单击 input>Set Message。
- 选择现有的
tns:getQuoteRequest消息,该消息在步骤 4. 创建消息和消息部件中已定义,然后点击 Finish。
重复以上的三步,创建 getQuote 操作的输出并将其链接至 tns:getQuoteResponse。
最后,您需要定义服务端口的绑定协议。
- 在 Outline 视图中,右键单击
Bindings并选择 Add Child>Binding。 - 指定绑定细节,如以下图 4 所示。请确保选择 document/literal 作为 SOAP 绑定选项。单击 Finish。
- 右键单击
Services并选择 Add Child>Service。 - 输入
SupplierAQuoteService作为新服务名称并单击 OK。 - 右键单击
SupplierAQuoteService并选择 Add Child>Port。 - 如图 5 所示,输入端口细节并单击 Finish。
图 4.指定绑定细节
图 5.指定服务端口细节
最后,您可以实现基于 SupplierAService.wsdl 和 SupplierASchema.xsd 的 Web 服务。WebSphere Studio 可以从 WSDL 生成 Skeleton Java bean Web 服务。
- 选择 File>New>Other>Web Services>Web service 并单击 Next。
- 在接下来的 Web Service 向导中,选择 Web 服务类型为 Skeleton Java bean Web Service。对其它域的保留缺省设置并单击 Next。
- 在接下来的窗口中,指定
SupplierAServiceEAR为 Service project EAR,并指定SupplierAServiceWeb为 Service Web 项目。这是拥有 Java Web 服务的企业项目。单击 Next。 - 浏览到创建好的
SupplierAService.wsdl。单击 OK,单击 Next,然后单击 Finish。
检查 SupplierAServiceWeb 项目。可序列化的 Product 类由 SupplierASchema.xsd 中定义的复杂类型 Product 生成,且 Java Skeleton Web 服务也已构建。但是,这还只是有接口功能的空服务;接口操作 getQuote 的具体实现需要在 SupplierAQuoteServiceBindingImpl 中手工提供。
首先,添加构造函数至 Product 类。
清单 1.添加构造函数至
Product 类
public Product(String name, int qty, float price, boolean isRefurbished) {
this.set_name(name);
this.set_qty(qty);
this.set_price(price);
this.set_refurbished(isRefurbished);
}
|
接下来,添加构造函数至 SupplierAQuoteServiceBindingImpl 类,用以对库存硬编码。实际上,您可能需要公开接口方法,例如 addInventory(Product item) 用来重新储存产品。
清单 2.添加构造函数至
SupplierAQuoteServiceBindingImpl 类
private static Hashtable fCurrentInventory = new Hashtable();
public SupplierAQuoteServiceBindingImpl() {
fCurrentInventory.put(
"IBM ThinkPad T40",
new Product("IBM ThinkPad T40", 200, 1499.99f, false));
fCurrentInventory.put(
"Dell Inspiron 4000",
new Product("Dell Inspiron 4000", 100, 999.99f, true));
fCurrentInventory.put(
"Toshiba Satellite 2210X",
new Product("Toshiba Satellite 2210X", 300, 599.99f, false));
}
|
用清单 3 中的代码替代 SupplierAQuoteServiceBindingImpl 类中的 getQuote 方法。
清单 3. 替代
getQuote 方法
public Product getQuote(java.lang.String productName)
throws java.rmi.RemoteException {
if (fCurrentInventory.containsKey(productName))
return (Product) fCurrentInventory.get(productName);
else
return new Product(productName, 0, 0f, false);
}
|
Supplier A 的报价 Web 服务已经准备好,可以进行部署和运行了。在 BPEL 流程创建之前还不能对其进行测试。
对于 Supplier B 的 .NET Web 服务,schema 要更复杂一些:在其它复杂类型中再嵌套复杂类型数组。在编程语言中添加的这种复杂性并不十分奇怪,事实上是非常正常的事情。但是,它经常是导致 XML 消息序列化失败的起因。特别是,消息接受方通常不能匹配合适的 XML 序列化器类。:dateTime 也是 J2EE 和 .NET 间出现互操作性问题的常见根源。本技巧的主要目的之一,就是说明如何为消息和数据类型谨慎地设计 XML schema,以避免出现互操作性问题。
要构建 .NET Web 服务,您需要使用稍微有些不同的方法。但通常都应该首先设计 XSD schema。
假设 Supplier B 对保存它的库存产品信息有不同的需求。与 Supplier A 中列出库存产品名称不同,Supplier B 保存了产品信息清单,例如生产日期、库存日期或是重新进货日期等。
图 6.Supplier B Web 服务的接口图
在 UML 图中,Product 有 _dates 属性,该属性是 DateInfo
的集合,DateInfo 都是复杂类型。在编程语言中,UML 集合转化成为数组。但在另一个端,如何用 XML schema 或是 XSD 类型来表示 Product 和 DateInfo 之间的关系呢?此时,需要另一个复杂类型的属性 ArrayOfDateInfo 用来描述两者之间的关系。ArrayOfDateInfo 类型有 DateInfo 类型元素的未绑定序列。因此,要正确序列化 .NET Web 服务中的 Product 对象,需要在 XML Schema 中定义三种复杂类型:DateInfo、ArrayOfDateInfo 和 Product。
和先前演示的步骤类似,首先用目标命名空间 http://schema.b.supplier 创建 SupplierBSchema.xsd 并定义复杂类型 DateInfo,如清单 4 所示。
清单 4. 定义复杂类型
DateInfo
<complexType name="DateInfo">
<sequence>
<element name="_date" type="dateTime"/>
<element name="_desc" type="string"/>
</sequence>
</complexType>
|
接下来,通过 SupplierBSchema.xsd 中 DateInfo 类型元素的未绑定序列来添加复杂类型 ArrayOfDateInfo。
- 在 Outline 视图中,右键单击
SupplierBSchema.xsd并单击 Add Complex Type。 - 将新的复杂类型重命名为
ArrayOfDateInfo。 - 右键单击
ArrayOfDateInfo并单击 Add Content Model。 - 右键单击 content model icon 并单击 Add element。
- 将新元素重新命名为:
DateInfo。 - 设置用户定义的复杂类型:
SupplierBSchema:DateInfo。 - 将
minOccurs属性设置为零,并将maxOccurs属性设置为 unbounded。
ArrayOfDateInfo schema 的结果如 清单 5 所示。
清单 5.
ArrayOfDateInfo schema
<complexType name="ArrayOfDateInfo">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="_dateInfo" type="SupplierBSchema:DateInfo"/>
</sequence>
</complexType>
|
按照如定义 complexType Product 类似的步骤进行,如以下清单 6 所示。
清单 6.
complexType Product
<complexType name="Product">
<sequence>
<element name="_name" type="string"/>
<element name="_qty" type="int"/>
<element name="_price" type="float"/>
<element name="_dates" type="SupplierBSchema:ArrayOfDateInfo"/>
</sequence>
</complexType>
|
在 .NET Framework 1.1 中,XML Schema Definition 工具(Xsd.exe)可通过 XSD 文件生成运行时类。Xsd.exe 实用程序通过 XSD schema 中生成一组 C# 类模板。通过模板,您可以提供具体的实现并生成整个项目。然而,Xsd.exe 实用程序需要在 schema 中定义至少一个顶级元素,因此您需要定义一个全局元素:productItem。
- 在 Outline 视图中,右键单击
SupplierBSchema.xsd并单击 Add Global Element。 - 将该元素命名为
productItem,并将其类型设置为SupplierBSchema:Product。 - 保存文件。
现在,导出 SupplierBSchema.xsd 文件到一个目录,并运行该目录中的 Xsd.exe 命令,生成 C# 类型类:xsd.exe SupplierBSchema.xsd /classes。
一组 C# 类型的类在 SupplierBSchema.cs 文件中生成。DateInfo 和 Product 类通过 http://schema.b.supplier 命名空间定义并限定。请参见 Download 部分关于类的完整资料。在设计完互操作性场景中最重要的部分后,接下来开始构建 .NET Web 服务实现。在 构建代理流程 部分中,SupplierASchema.xsd 和 SupplierBSchema.xsd 同样是构建 BPEL 流程的起始点。
要构建 Supplier B 的 .NET Web 服务,您可以使用 .NET Visual Studio 构建 .NET 部件,或者在 .asmx 文件中,简单地编写 C# 代码并将类型类封装在 SupplierBSchema.cs 文件中,此处将使用后一种方法。
- 复制
SupplierBSchema.cs文件至C:\Inetpub\wwwroot\SupplierB\目录并重命名为SupplierBQuoteService.asmx,在编辑器中打开。 - 注释掉以下行:
[System.Xml.Serialization.XmlRootAttribute("productItem", Namespace="http://schema.b.supplier", IsNullable=false)]. - 将构造函数添加至
Product和DateInfo类中,并进行初始化。在 .NET 中,序列化类也需要缺省构造函数。public DateInfo() {} public DateInfo(DateTime date, string desc) { _date = date; _desc = desc; }
andpublic Product() {} public Product(string name, int qty, float price, DateInfo[] dates) { _name = name; _qty = qty; _price = price; _dates = dates; }
-
添加 Web 服务类
SupplierBQuoteService并在命名空间 http://b.supplier/service 下将getQuote方法公开为 document/literal Web 服务方法。在 .NET 中,document/literal 是缺省绑定样式:[WebService(Namespace="http://b.supplier/service")] public class SupplierBQuoteService { private static Hashtable fCurrentInventory = null; private static Hashtable getCurrentInventory() { return fCurrentInventory; } public SupplierBQuoteService() { fCurrentInventory = new Hashtable(); Product item1 = new Product("IBM ThinkPad T40", 200, 1399.99f, new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"), new DateInfo(DateTime.Now.AddYears(3), "Expiry Date")}); Product item2 = new Product("Dell Inspiron 4000", 200, 899.99f, new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"), new DateInfo(DateTime.Now.AddYears(5), "Expiry Date")}); Product item3 = new Product("Toshiba Satellite 2210X", 200, 599.99f, new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"), new DateInfo(DateTime.Now.AddYears(10), "Expiry Date")}); getCurrentInventory().Add("IBM ThinkPad T40", item1); getCurrentInventory().Add("Dell Inspiron 4000", item2); getCurrentInventory().Add("Toshiba Satellite 2210X", item3); } [WebMethod] public Product getQuote(string quoteItemName) { string item = quoteItemName; if (!getCurrentInventory().ContainsKey(item)) return new Product(item, 0, 0, new DateInfo[] {new DateInfo(DateTime.Now, "Manufacture Date"), new DateInfo(DateTime.Now, "Expiry Date")}); else return (Product)(getCurrentInventory()[item]); } }
完成的 SupplierBQuoteService.asmx 文件包含在 Download 部分中。
现在您可以在浏览器上测试 Supplier B Web 服务的 getQuote 方法。
- 在浏览器上,输入以下 URL:http://localhost/SupplierB/getQuoteServiceImpl.asmx。
- 只返回 Web 服务方法
getQuote。单击getQuote方法。 - 在文本框中输入 IBM ThinkPad T40 并单击 Invoke。
将返回 IBM ThinkPad T40 产品信息,如以下 图 7 所示。
图 7. .NET Web 服务的
getQuote 方法的测试结果
请注意 Manufacture Date 和 Expiry Date 是如何显示的。
在浏览器中输入 URL: http://localhost/SupplierB/SupplierBQuoteService.asmx?wsdl。浏览器将显示 .NET Web 服务的 WSDL 文档。虽然该文档由 .NET WSDL 引擎生成,但日期类型 schema 和命名空间直接来自于前面步骤中设计的 SupplierBSchema.xsd。在服务项目中,将 WSDL 导入至 quote.process 包。
在本部分中,您将定义代理 Quote Process 接口。该流程的输出数据类型由 Supplier A 和 Supplier B 的产品报价结果组合而成,如图 8 中的 UML 所示。
图 8.
Quote Process 的接口图
您需要定义复杂类型,通过输入两个分别来自 SupplierASchema.xsd 和 SupplierBSchema.xsd 的 Product 复杂类型来组成最后的报价信息。
首先,创建 QuoteProcess.xsd schema 文件并输入 SupplierASchema.xsd 和 SupplierBSchema.xsd:
- 右键单击
quote.process包并选择 New>XML Schema。 - 输入新 schema 名称:
QuoteProcessSchema.xsd并单击 Finish。打开模式编辑器。 - 在 Outline 视图中,选择
QuoteProcessSchema.xsd。 - 在 Outline 视图中,右键单击
QuoteProcessSchema.xsd并单击 Add Import。 - 在 Outline 视图中,扩展 + sign 并单击 import icon。
- 在 Import 面板中,浏览到
SupplierASchema.xsd文件并单击 Finish。
重复步骤 4 至 6,并导入 SupplierBSchema.xsd 文件。
接下来,创建 ProductQuotes 复杂类型:
- 在 Outline 视图中,右键单击
QuoteProcess.xsd并单击 Add Complex Type。 - 在 Complex Type 窗口中,将新的复杂类型命名为
ProductQuotes。 - 在 Outline 视图中,右键单击
ProductQuotes并单击 Add Content Model. - 在 Outline 视图中,右键单击 icon: 并单击 Add Element。
- 在 Element 窗口,将新元素命名为
SupplierAQuote。 - 将
SupplierAQuote元素设置为用户定义的复杂类型SupplierASchema:Product。
重复最后三个步骤,添加另一元素 SupplierBQuote,并将其设置为用户定义的复杂类型 SupplierBSchema:Product。
在 Source 视图中,作为结果产生的 ProductQuotes schema 如清单 7 所示。
清单 7.
ProductQuotes schema
<?xml version="1.0"?>
<schema targetNamespace="http://www.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:QuoteProcessSchema="http://www.ibm.com"
xmlns:SupplierASchema="http://schema.a.supplier" xmlns:SupplierBSchema=
"http://schema.b.supplier">
<import namespace="http://schema.b.supplier" schemaLocation=
"SupplierBSchema.xsd"/><import schemaLocation=
"SupplierASchema.xsd" namespace="http://schema.a.supplier"/>
<complexType name="ProductQuotes">
<sequence>
<element name="SupplierBQuote" type="SupplierBSchema:Product"/>
<element name="SupplierAQuote" type="SupplierASchema:Product"/>
</sequence>
</complexType>
</schema>
|
BPEL 流程将作为 Web 服务进行部署和运行。接下来的步骤将创建流程接口 WSDL 并导入 QuoteProcessSchema.xsd。
创建流程接口 WSDL。
- 在
quote.process包中,创建空 WSDL 且命名为quoteProcessClient.wsdl。在向导中,将目标命名空间设置为http://quote.process/quoteProcessClient/。 - 在 Outline 视图中打开 WSDL 编辑器后,右键单击 Imports 并输入
QuoteProcessSchema.xsd文件。
接下来,定义流程入站和出站消息。
- 在 Outline 视图中,右键单击 Messages 并选择 Add Child>Message。
- 将新消息名设置为
getQuotesRequest并单击 OK。 - 在 Outline 视图中,右键单击
getQuotesRequest并选择 Add Child>Part。 - 输入部件名
productName。其缺省类型为xsd:string。
重复上述步骤,创建新消息 getQuotesResponse,添加消息部件 quotes,并为 quotes 设置类型。
- 在 Outline 视图中,右键单击 quotes 并单击
Set Type。 - 将类型指定为
xsd1:ProductQuotes并单击 Finish,如以下图 9 所示。
图 9.指定 quotes 类型
接下来,为流程添加 Port Types 以及操作并定义输入和输出。
- 在 Outline 视图中,右键单击
Port Types并选择 Add Child>Port Type。 - 添加新
Port Type,名为QuotesProcessPortType。 - 右键单击
QuotesProcessPortType并选择 Add Child>Operation。 - 将操作命名为
getQuotes。 - 右键单击
getQuotes操作,选择 Add Child>input 并右键单击 input。 - 单击
Set Message并选择 Select an existing message 单选框。 - 将输入消息设置为
tns:getQuotesRequest并单击 Finish。
重复最后三个步骤,添加输出,设置消息为 tns:getQuotesResponse,并保存 quoteProcessClient.wsdl。
现在,构建业务流程流。首先,创建空流程定义文件。
- 右键单击
QuoteProcessService中的quote.process包,并选择 New>Business Process。 - 将业务流程命名为
QuotesProcess并单击 Next。 - 选择 Sequence-based BPEL Process 并单击 Finish。
BPEL 流程通过名为 <partnerLink> 的简单概念调用 Web 服务。<partnerLink> 是 Web 服务 WSDL 文件定义的操作的 BPEL 抽象。伙伴链接读取外部 Web 服务的 portType,该外部 Web 服务由 WSDL 定义。并允许 portType 中的实际操作和调用活动相关联。每个伙伴链接都与逻辑工作角色相关联。
在该流程中,有三个伙伴:Agent、Supplier A 和 Supplier B。每个伙伴服务的接口都在其 WSDL 文件中进行描述:分别为 quoteProcessClient.wsdl、SupplierAService.wsdl 和 SupplierBService.wsdl。
- 双击
quotesProcess.bpel文件并在编辑器中打开,删除缺省伙伴链接。 - 将
quoteProcessClient.wsdl拖放至编辑器并接受缺省值。 - 在编辑器中,选择
QuotesProcessPortType伙伴链接。在 details 区域,单击 Implementation 选项卡,并单击 role switch icon,将QuotesProcessPortTypeRole设置为流程角色名。 - 类似地,将
SupplierAService.wsdl和SupplierBService.wsdl文件拖放至编辑器;接受缺省值。
接下来,定义流程变量。该流程状态信息流受流程变量管理。在 WebSphere Studio Application Developer Integrated Edition V5.1.1 中,创建的所有流程变量都是全局变量。因此,你可以从任何代码块访问流程变量。各伙伴链接都有输入和输出变量,因此三对变量需要在 quotesProcess 中定义。
- 在 BPEL 编辑器中,删除缺省的
InputVariable。 - 单击 plus icon 并添加新的
Input变量。 - 在 Details 区域,单击 Message 选项卡,浏览到
quoteProcessClient.wsdl文件并将其链接至getQuotesRequest消息。
重复相同的步骤创建以下流程变量。将其链接至各自的消息,如以下表格所示:
| Variable | Message | WSDL |
| Output | getQuotesResponse | quoteProcessClient.wsdl |
| SupplierAQuoteReq | getQuoteRequest | SupplierAService.wsdl |
| SupplierAQuoteRes | getQuoteResponse | SupplierAService.wsdl |
| SupplierBQuoteReq | getQuoteSoapIn | SupplierBService.wsdl |
| SupplierBQuoteRes | getQuoteSoapOut | SupplierBService.wsdl |
接下来,执行 Receive 和 Reply 活动。Receive 活动接收 Web 服务请求,该请求启动报价流程。通过调用 Web 服务,Reply 活动将发送报价响应给调用者。
- 单击
Receive活动。 - 在 Details 区域,单击 Implementation 选项卡并将
PartnerLink设置为QuotesProcessPortType。设置 Operation 为getQuotes并将 Request 设置为Input。 - 类似地,对于
Reply活动,将PartnerLink设置为QuotesProcessPortType。将Operation设置为getQuotes并将Response设置为Output。
Flow 活动是一组以并行方式运行的活动,而 Sequence 是一组以串行方式运行的活动。在代理报价流程中,您希望同时调用 Supplier A 和 Supplier B 的 Web 服务。因此,创建 Flow 活动,两个序列活动将并行运行,且每个 Sequence 活动都准备好调用,调用供应方的 Web 服务,并处理结果。在 BPEL 中,Invoke 用来执行在外部实现的业务逻辑。
- 从 Palette 选择
Flow活动并放到编辑器中,位于Receive和Reply活动之间。 - 从 Palette 选择
Sequence活动并放至Flow活动中。将Sequence活动命名为QuoteSupplierA。 - 准备调用 Supplier A Web 服务所需的变量。在 Palette 上,选择
Assign活动并放到QuoteSupplierA序列活动中。命名为Init。 - 在 Implementation 细节区域,复制
Input变量getQuotesRequest消息的输入部分,并粘贴到SupplierAQuoteReq变量getQuoteRequest的productName。 - 将
Invoke拖放至QuoteSupplierA序列活动中,并至于Init活动下。将其重命名为getQuote。 - 在 Implementation 细节区域,将
getQuote活动设置为伙伴链接SupplierAQuotePortType,将 Operation 设置为getQuote并将 Request 消息设置为SupplierAQuoteReq,并将 Response 消息设置为SupplierAQuoteRes。
接下来,为 Supplier B 创建 Invoke 活动。要调用 Supplier B Web 服务,您需要使用流程变量 getSupplierBQuoteReq 的初始化 Java 片断。您也可以使用 Java 片断来执行简单的业务逻辑,而无需调用外部 Web 服务。
- 将
Sequence活动放到Flow活动中。并命名为QuoteSupplierB。QuoteSupplierB是QuoteSupplierA的并行活动。 - 将 Java 片断拖至
QuoteSupplierB活动中,并重新命名为Init。 - 在 Init Java 片断的 Implementation 细节区域中,复制并粘贴以下代码:
supplier.b.service.GetQuoteElement newValue = new supplier.b.service.GetQuoteElement(); newValue.setQuoteItemName(getInput(true).getInput()); getSupplierBQuoteReq(true).setParameters(newValue);
- 将
Invoke活动放至QuoteSupplierB中的Init下。重新命名为getQuote。 - 将
getQuote活动设置为伙伴连接SupplierBQuoteServiceSoap,将 Operation 设置为getQuote,将 Request 消息设置为SupplierBQuoteReq,并将 Response 消息设置为SupplierBQuoteRes。 - 现在,在
Flow活动之后,但在Reply活动准备输出前,立即放置 Java 片断。将其命名为preReply。 - 复制并粘贴以下 Implementation 细节区域中的代码片断作为
preReplyJava 片断。ProductQuotes newValue = new ProductQuotes(); newValue.setSupplierBQuote(getSupplierBQuoteRes(true). getParameters().getGetQuoteResult()); newValue.setSupplierAQuote(getSupplierAQuoteRes(true).getProduct()); getOutput(true).setOutput(newValue);
现在,您已经完成了图 10 中定义的报价流程。
图 10. 代理 Quotes 流程
保存 BPEL 文件并生成部署代码。
- 右键单击
quotesProcess.bpel文件并选择 Enterprise Services>Generate 部署代码。 - 在 Generate BPEL Deploy Code 向导中,单击
quotesProcessPortType接口。 - 选择 SOAP/HTTP 作为绑定并选择 IBM Web Service。单击 OK。
代理流程作为 SOAP/HTTP Web 服务来部署。QuotesProcessServiceWeb 项目中的 WSDL 和 XSD 文件公开自身接口供任何客户端调用流程。
图 11. 代理流程的 WSDL 和 XSD 文件
在本部分,您可以使用图 11 中的流程 WSDL 和 XSD 文件,生成在业务场景中由购买者调用的 Java 客户端代理,如图 1 所示。
首先,创建 Java 项目 QuoteProcessTestClient 并从 QuoteProcessServiceWeb 复制 WSDL 以及三份 XSD 文件至测试客户端项目。
接下来,从 WSDL 生成 JAX-RPC 客户端代理。
- 右键单击
QuotesProcess_QuotesProcessPortType_HTTP.wsdl文件并选择 Enterprise Services>Generate Service Proxy。 - 在向导中,选择 Java API for XML-based RPC (JAX-RPC) 作为代理类型,单击 Next 并单击 Next 继续。
- 请确保选择 Java 作为
Client类型以及选择QuoteProcessTestClient作为客户端项目。单击 Next。 - 在接下来的页面中,接受所有的缺省选项并单击 Finish。
JAX-RPC 代理的类设置已经生成,如图 12 所示。testclient 包中的 Buyer.java 将在下个章节进行介绍。
图 12.代理流程的 JAX-RPC 代理
要测试代理流程,首先要创建测试服务器并在其上部署 Supplier A Web 服务和代理流程。
- 切换到 Server 视图。创建新的集成测试服务器,配置并将其命名为
TestServer。 - 在 Servers 窗口,右键单击
TestServer。单击 Add 和 Remove Projects。 - 在
TestServer上添加两个项目:SupplierAServiceEAR和QuoteProcessServiceEAR。单击 Finish。 - 右键单击
TestServer并单击 Start。
接下来,实现描述 Buyer 的主要类,通过 JAX-RPC 代理来调用代理流程。
- 创建新的 Java 包。右键单击
QuoteProcessTestClient,选择 New>Package,为新 Java 包命名为testclient。单击 Finish。 - 创建 Buyer 类,将其命名为
Buyer。单击 Finish。将打开Buyer类。 - 将清单 8 中的代码复制并粘贴至
Buyer类编辑器并保存 Java 文件。
清单 8.
Buyer 类
package testclient;
import java.util.Calendar;
import java.util.Date;
import process.quote.QuotesProcessPortTypeProxy;
import supplier.b.schema.DateInfo;
import com.ibm.www.ProductQuotes;
public class Buyer {
public static void main(String[] args) {
String product = "IBM ThinkPad T40";
QuotesProcessPortTypeProxy aProxy = new QuotesProcessPortTypeProxy();
try {
ProductQuotes result = aProxy.getQuotes(product);
supplier.a.schema.Product quoteA = result.getSupplierAQuote();
supplier.b.schema.Product quoteB = result.getSupplierBQuote();
System.out.println("Quotes for product: " + product);
System.out.println("\tSupplier A: ");
System.out.println("\t\tQuantity: " + quoteA.get_qty());
System.out.println("\t\tPrice: " + quoteA.get_price());
System.out.println(
"\t\tIs refurbished: " + quoteA.is_refurbished());
System.out.println("\tSupplier B: ");
System.out.println("\t\tQuantity: " + quoteB.get_qty());
System.out.println("\t\tPrice: " + quoteB.get_price());
DateInfo[] dates = quoteB.get_dates().get_dateInfo();
for (int i = 0; i < dates.length; i++) {
Calendar cal = dates[i].get_date();
Date date = cal.getTime();
System.out.println("\t\t" + dates[i].get_desc() + ": " + date);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
最后,运行 Buyer 类获取 Supplier A 和 Supplier B 提供的关于 IBM ThinkPad T40 的产品信息。
- 选择 Package Explorer 中的
Buyer类。 - 在顶端的菜单中,选择 Run>Run As>Java Application。如果顺利完成,结果将出现在控制台中,如以下的 图 13 所示。
图 13. 报价结果
对比图 13 和图 7 中的单元测试结果,并观察两种情况下如何描述日期信息数组。
在代理流程及其客户端代理类中,xsd:dateTime 被映射至 java.util.Calendar,但是最好能呈现给购买者的是简洁的 java.util.Dates 结果而不是包含大量多余信息的 java.util.Calendar。如果客户端需要 java.util.Dates ,那么需要对其进行简单转换,如清单 8 所示。
以下是针对 J2EE 和 .NET 开发 BPEL 流程的一些其他技巧:
- WebSphere Studio Application Developer Integration Edition Version 5.1.1 提供了强大的可视流程调试器,可以在 BPEL 流程级别上逐步调试代码。
- 对于在 Web 服务中来去的 SOAP 消息,你需要对其进行截取并研究,特别是 .NET Web 服务中的 SOAP 消息。可用的跟踪工具有很多。WebSphere 提供实体类
com.ibm.ws.webservice.engine.utils.tcpmon用以嗅探两点之间的 HTTP 通信。您可以随意选择您熟悉的跟踪工具。 - 在大多数情况下,开启服务器跟踪查找异常的根源是十分必要的。
通过 BPEL 进行业务流程集成的成败关键在于参与该流程的 Web 服务的内在互操作性。文中的技巧着重强调了在设计消息模式时必须十分谨慎,且该技巧还演示了无论是哪种平台(J2EE 或是 .NET),简单 Web 服务和复杂 Web 服务都可以成功地参与业务流程。WebSphere Studio Application Developer Integrated Edition V5.1.1 提供了强大的 BPEL 流程开发环境、方便的 XML Schema 和 WSDL 设计工具。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| Code for sample BPEL business process (Purchasing) | ws-solution.zip | 153 KB | HTTP |
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- 学习相关的 WSDL 和 SOAP 规范以及其它标准:
-
查阅该系列的其他文章:
- Peter Brittenham 编写的 了解 WS-I 测试工具 提供了对 WS-I Test Tools 体系结构和功能的概述。
- 参阅 Peter Brittenham 编写的教程 结合 Java 技术使用 WS-I 测试工具,介绍了如何按部就班地使用 Java 版本的 WS-I 测试工具
- 想了解更多吗?developerWorks Web 服务和 SOA 专区有大量信息丰富的文章和有关开发 Web 服务应用程序的入门级、中级和高级教程
- 访问 快速启用 Web 服务上关于 Web 服务的知识、工具和技术,同时还提供最新的基于 Java 的软件开发工具和 IBM 的中间件(试用版),另外还有在线教程、文章以及在线技术论坛。
Wangming Ye 是 IBM 认证的企业开发人员,并是 Sun 认证的 J2EE Technology 企业架构师。最初他是 Transarc 公司(之后合并到 IBM)DCE/DFS 部门的一名开发人员,随后成为 WebSphere Edge Server 组 WebSphere Content Distribution Framework 的主要开发人员之一。目前,他在 IBM Business Partner Technical Enablement 组织的 WebSphere Competency Center 为 WebSphere 业务伙伴提供相关的技术支持。您可以通过 yme@us.ibm.com 与 Wangming 联系。