在业务流程中融合J2EE和.NET技术

注意:您应该熟悉 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 服务。

创建 Supplier A 的 Java 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 来演示互操作性。

步骤 1.创建服务项目

首先,创建服务项目和包,用来为所有伙伴 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。

步骤 2.创建新的 XML

接下来,创建新的 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

步骤 3. 创建 Supplier A 的 WSDL

您可以为 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 中。

步骤 4. 创建消息和消息部件

接下来,创建消息和消息部件。对于每次 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 消息部件类型

步骤 5. 定义 Port Type

接下来,定义 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。

步骤 6. 定义绑定协议

最后,您需要定义服务端口的绑定协议。

在 Outline 视图中,右键单击 Bindings 并选择 Add Child>Binding。

指定绑定细节,如以下图 4 所示。请确保选择 document/literal 作为 SOAP 绑定选项。单击 Finish。

右键单击 Services 并选择 Add Child>Service。

输入 SupplierAQuoteService 作为新服务名称并单击 OK。

右键单击 SupplierAQuoteService 并选择 Add Child>Port。

如图 5 所示,输入端口细节并单击 Finish。

图 4.指定绑定细节

图 5.指定服务端口细节

步骤 7. 实现 Web 服务

最后,您可以实现基于 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);elsereturn new Product(productName, 0, 0f, false);}

Supplier A 的报价 Web 服务已经准备好,可以进行部署和运行了。在 BPEL 流程创建之前还不能对其进行测试。

创建 Supplier B 的 .NET Web 服务

对于 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

接下来,通过 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

      name="_dateInfo" type="SupplierBSchema:DateInfo"/>

按照如定义 complexType Product 类似的步骤进行,如以下清单 6 所示。

清单 6. complexType Product

在 .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")});elsereturn (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 复杂类型来组成最后的报价信息。

步骤 1.定义复杂类型

首先,创建 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 文件。

步骤 2.创建复杂类型

接下来,创建 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

  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">   "SupplierBSchema.xsd"/>    "SupplierASchema.xsd" namespace="http://schema.a.supplier"/>

BPEL 流程将作为 Web 服务进行部署和运行。接下来的步骤将创建流程接口 WSDL 并导入 QuoteProcessSchema.xsd。

步骤 3.创建流程接口 WSDL

创建流程接口 WSDL。

在 quote.process 包中,创建空 WSDL 且命名为 quoteProcessClient.wsdl。在向导中,将目标命名空间设置为 http://quote.process/quoteProcessClient/。

在 Outline 视图中打开 WSDL 编辑器后,右键单击 Imports 并输入 QuoteProcessSchema.xsd 文件。

步骤 4.定义流程入站和出站消息

接下来,定义流程入站和出站消息。

在 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 类型

步骤 5. 添加端口类型

接下来,为流程添加 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。

步骤 6. 构建业务流程流

现在,构建业务流程流。首先,创建空流程定义文件。

右键单击 QuoteProcessService 中的 quote.process 包,并选择 New>Business Process。

将业务流程命名为 QuotesProcess 并单击 Next。

选择 Sequence-based BPEL Process 并单击 Finish。

步骤 7. 创建伙伴链接

BPEL 流程通过名为 的简单概念调用 Web 服务。 是 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 文件拖放至编辑器;接受缺省值。

步骤 8. 定义流程变量

接下来,定义流程变量。该流程状态信息流受流程变量管理。在 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

步骤 9. 执行 Receive 和 Reply 活动

接下来,执行 Receive 和 Reply 活动。Receive 活动接收 Web 服务请求,该请求启动报价流程。通过调用 Web 服务,Reply 活动将发送报价响应给调用者。

单击 Receive 活动。

在 Details 区域,单击 Implementation 选项卡并将 PartnerLink 设置为 QuotesProcessPortType。设置 Operation 为 getQuotes 并将 Request 设置为 Input。

类似地,对于 Reply 活动,将 PartnerLink 设置为 QuotesProcessPortType。将 Operation 设置为 getQuotes 并将 Response 设置为 Output。

步骤 10. 创建 Flow 和 Sequence 活动

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。

步骤 11. 创建 Invoke 活动

接下来,为 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 细节区域中的代码片断作为 preReply Java 片断。ProductQuotes newValue = new ProductQuotes();newValue.setSupplierBQuote(getSupplierBQuoteRes(true).getParameters().getGetQuoteResult());newValue.setSupplierAQuote(getSupplierAQuoteRes(true).getProduct());getOutput(true).setOutput(newValue);

现在,您已经完成了图 10 中定义的报价流程。

图 10. 代理 Quotes 流程

步骤 12. 生成部署代码

保存 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 文件

创建 Java 客户端代理与代理流程连接

在本部分,您可以使用图 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 设计工具。

本文配套源码

征服畏惧、建立自信的最快最确实的方法,

在业务流程中融合J2EE和.NET技术

相关文章:

你感兴趣的文章:

标签云: