级别: 中级 Javier Garcia (javierg@us.ibm.com), 顾问软件工程师, IBM Qiang Wang (wangq@cn.ibm.com), 软件工程师, IBM, Intel, Microsoft,HP
2007 年 6 月 19 日 本文是一个考虑开发组合应用程序的系列的一部分。组合应用程序集成现有的 SOA 服务并创建能够以不同的方式组合的新服务。我们最初使用 WebSphere® Application Developer IE v5.1 开发了一个演示组合应用程序,其中使用了 WebSphere Business Integration SF 作为其运行时(请参见本系列的第 1 部分)。随着 WebSphere Process Server v6 及其对应的开发工具 WebSphere Integration Developer v6 的发布,一种基于服务组件体系结构(Service Component Architecture,SCA)的新编程模型出现了,从而要求将构件从遗留编程模型迁移到新的编程模型。下面我们将与您分享在此迁移过程期间学习到的一些重要教训。
引言
本文描述将原型组合应用程序从 WebSphere Application Developer-IE v5.1 迁移到 WebSphere Integration Developer v6 的一些问题和解决方案。我们将介绍的主要问题包括 WSDL 绑定、WSDL 接口、XSD 定义和业务流程执行语言(Business Process Execution Language,BPEL)编码。尽管 WebSphere Integration Developer v6 具有功能强大的迁移向导,但我们发现可以对简单项目进行自动迁移。然而,更复杂的 BPEL 应用程序将需要对迁移过程的更深入了解。有关详尽的迁移注意事项,请参考 WebSphere Integration Developer 帮助或 WebSphere Integration Developer /WPS 信息中心。
绑定的迁移问题
本系列中的第一篇文章确定了一些由一家银行客户发起的用例。“贷款申请”用例调用了一个 BPEL 业务流程。该业务流程完成一系列调用服务来处理贷款的步骤。所调用的有些服务使用了 Java™ 或 EJB 绑定。
Java 绑定问题
WebSphere Integration Developer v6 不支持 WebSphere Application Developer-IE 所生成的 WSDL 中使用的原始 Java 绑定类型。服务/端口定义使用了某种 Java 类型。因此在 WSDL 中生成了一个 Java ClassName 而不是一个端点地址。(请参见清单 1)。
清单 1. 带 Java 绑定的 WSDL
<service name="LoanTrackingServiceProxyPortTypeService">
<port binding="tns:LoanTrackingServiceProxyPortTypeJavaBinding"
name="LoanTrackingServiceProxyPortTypeJavaPort">
<java:address className="loantrackingservice.LoanTrackingServiceProxy"/>
</port>
</service>
|
如果将此类 WSDL 直接导入 SCA 模块,那么即使没有异常或由 WebSphere Integration Developer 标记的错误,所生成的导入也无法成功进行绑定(请参见图 1)。事实上,WebSphere Integration Developer V6 仅支持带 SOAP 绑定的 Web 服务导入。因此,导入的端点将保留为空,从而在将模块部署到 WPS 并在我们尝试调用它时导致运行时异常。
图 1. 带 Java 绑定的 WSDL 导入
Enterprise JavaBean (EJB) 绑定问题
WebSphere Integration Developer v6 不支持 WebSphere Application Developer-IE 所生成的 WSDL 中使用的原始 EJB 绑定类型。其中的服务/端口定义(类似于前面提到的 Java 类型)使用了 EJB 类型。因此,提供了 EJB Home ClassName 和 JNDI Name 而不是端点地址。(请参见清单 2)。
清单 2. 带 EJB 绑定的 WSDL
<service name="LoanTrackingService">
<port binding="binding1:LoanTrackingServiceEJBBinding"
name="LoanTrackingServiceEJBPort">
<ejb:address className="loantrackingservice.LoanTrackingServiceHome"
jndiName="ejb/loantrackingservice/LoanTrackingServiceHome"/>
</port>
</service>
|
这也会由于前面提到的相同原因而在执行期间导致异常(请参见图 2)。
图 2. 带 EJB 绑定的 WSDL 导入
解决方案
- 丢弃带 EJB 或 Java 绑定的以前 WSDL。
- 重新生成带 SOAP 绑定的对应 Web 服务,并将其直接导入 SCA 模块。
- 修改 BPEL 流程以引用新导入的 Web 服务接口。
WSDL 接口的迁移问题
本系列第一部分中讨论过的用例之一是贷款发放场景。在客户申请贷款之后,一名扮演“平台销售 (Platform Sales)”角色的银行员工审核贷款请求并批准或拒绝该请求。此用例的部分地方调用了一个信用记录服务。在此服务调用期间,使用 document/literal 样式并避免 RPC 样式及其 soapenc:Array 是非常重要的。但是在需要连接到某个使用该样式的服务的场景中,我们如何做呢?接下来的部分将讨论此问题以及避免使用重复命名空间的重要性。
避免使用 soapenc:Array
避免创建或导入引用 soapenc:Array 类型的 WSDL 接口,因为 SCA 编程模型本身并不支持该接口。WPS 6.0 将 RPC 样式的 SOAP 编码数组类型视为具体类型的无限序列。因此,建议不要使用它们。标准方法是使用 document/literal 样式。WebSphere Application Developer-IE 生成的原始 WSDL 文件中的 soapenc:Array 类型定义如清单 3 所示。
清单 3. soapenc:Array 的 XML 类型定义
<complexType name="ArrayOfLoanRequestBean">
<complexContent>
<restriction base="soapenc:Array">
<all/>
<attribute ref="soapenc:arrayType" wsdl:arrayType="xsd1:LoanRequestBean[]"/>
</restriction>
</complexContent>
</complexType>
|
解决方案 1
- 更改 WSDL 中的数组类型定义。
- 重新生成 Web 服务。
- 再次运行 WebSphere Application Developer-IE Service Project 迁移向导,以在 WebSphere Integration Developer 中生成新构件(服务项目和 BEPL 流程中修改后的 Java 代码片段)。
解决方案 2
然而,在某些情况下,SCA 应用程序必须调用某个使用 soapenc:Arraytype 的外部服务。(例如,调用某个外部信用记录服务,该服务在其接口定义中使用了 soapenc:Array 类型)。在这种情况下,修改服务实现和接口定义是不可能的,因此必须使用其他解决方案来解决此问题。
- 通过添加占位符元素来人工更改导入的外部 WSDL,这些元素可以在将该 WSDL 导入 SCA 模块后促进后续的迁移工作。
- 使用服务数据对象(Service Data Object,SDO)API 来处理SCA 模块中的 Java 代码片段中的
soapenc:Array 数据。
清单 4 是一个示例,其中的数据对象根类型为 soapenc:Array。请注意 sampleElements DataObject 是如何通过所列出的第二个模式来创建的。首先获得该 DataObject 的类型,然后再获得 sampleStructElement 的属性(请参见清单 5)。这实际上是一个占位符属性,并且仅用于获得一个有效的属性来将 DataObjects 添加到该序列。
清单 4. 示例 WSDL 代码
<s:schema elementFormDefault="qualified" targetNamespace="http://soapinterop.org/xsd">
<s:import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
<s:import namespace="http://schemas.xmlsoap.org/wsdl/" />
<s:complexType name="SOAPStruct">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" form="unqualified" name="varInt"
type="s:int" /> <s:element minOccurs="1" maxOccurs="1" form="unqualified"
name="varString" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" form="unqualified"
name="varFloat" type="s:float" />
</s:sequence>
</s:complexType>
<s:complexType name="ArrayOfSOAPStruct">
<s:complexContent mixed="false">
<s:restriction base="soapenc:Array">
<s:attribute wsdl:arrayType="s0:SOAPStruct[]" ref="soapenc:arrayType" />
</s:restriction>
</s:complexContent>
</s:complexType>
</s:schema>
<wsdl:message name="echoStructArraySoapIn">
<wsdl:part name="inputStructArray" type="s0:ArrayOfSOAPStruct" />
</wsdl:message>
<wsdl:message name="echoStructArraySoapOut">
<wsdl:part name="return" type="s0:ArrayOfSOAPStruct" />
</wsdl:message>
<wsdl:operation name="echoStructArray">
<wsdl:input message="tns:echoStructArraySoapIn" />
<wsdl:output message="tns:echoStructArraySoapOut" />
</wsdl:operation>
SampleElements.xsd
<schema targetNamespace="http://sample/elements" xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://sample/elements">
<element name="sampleStringElement" type="string"/>
<element name="sampleStructElement" type="any"/>
</schema>
|
清单 5. 该 Web 服务的示例客户端代码
// Create the input DataObject and get the SDO sequence for the any element
DataFactory dataFactory=DataFactory.INSTANCE;
DataObject arrayOfStruct = dataFactory.create(
"http://soapinterop.org/xsd","ArrayOfSOAPStruct");
Sequence sequence=arrayOfStruct.getSequence("any");
// Get the SDO property for sample element we want to use here to populate the sequence
// We have defined this element in an XSD file, see SampleElements.xsd
DataObject sampleElements=dataFactory.create("http://sample/elements", "DocumentRoot");
Property property = sampleElements.getType().getProperty("sampleStructElement");
//Add the elements to the sequence
DataObject item=dataFactory.create("http://soapinterop.org/xsd", "SOAPStruct");
item.setInt("varInt", 1);
item.setString("varString", "Hello");
item.setFloat("varFloat", 1.0f);
sequence.add(property, item);
item=dataFactory.create("http://soapinterop.org/xsd", "SOAPStruct");
item.setInt("varInt", 2);
item.setString("varString", "World");
item.setFloat("varFloat", 2.0f);
sequence.add(property, item);
//Invoke the echoStructArray operation
System.out.println("[client] invoking echoStructArray operation");
DataObject echoArrayOfStruct =
(DataObject)interopTest.invoke("echoStructArray", arrayOfStruct);
// Display the results
if (echoArrayOfStruct!=null)
{
sequence=echoArrayOfStruct.getSequence("any");
for (int i=0, n=sequence.size(); i<n; i++)
{
item=(DataObject)sequence.getValue(i);
// Create the input DataObject and get the SDO sequence for the any element
DataFactory dataFactory=DataFactory.INSTANCE;
DataObject arrayOfStruct =
dataFactory.create("http://soapinterop.org/xsd","ArrayOfSOAPStruct");
Sequence sequence=arrayOfStruct.getSequence("any");
// Get the SDO property for sample element we want to use here to populate the sequence
// We have defined this element in an XSD file, see SampleElements.xsd
DataObject sampleElements=dataFactory.create("http://sample/elements",
"DocumentRoot");
Property property = sampleElements.getType().getProperty("sampleStructElement");
//Add the elements to the sequence
DataObject item=dataFactory.create("http://soapinterop.org/xsd", "SOAPStruct");
item.setInt("varInt", 1);
item.setString("varString", "Hello");
item.setFloat("varFloat", 1.0f);
System.out.println("[client] item varInt = "+ item.getInt("varInt")+"
varString="+item.getString("varString")+" varFloat="+item.getFloat("varFloat"));
}
|
避免使用重复命名空间
在 WebSphere Integration Developer 中,您不能导入两个带有相同命名空间的不同 WSDL 文件。当在完全相同的命名空间中声明了复杂类型(在 WSDL 或 XSD 中)时,则不能通过迁移向导过程来正确迁移它们。在 IBM WebSphere® Process Server v6 中,具有相同名称和目标命名空间的两个不同 WSDL/XSD 定义是不允许的。
清单 6 和 7 显示了两个具有相同命名空间的 WSDL 定义。
清单 6. LoanRequest1.WSDL
<wsdl:definitions xmlns:tns="http://loanrequest/LoanRequest" ......
targetNamespace="http://loanrequest/LoanRequest">
<message name="processLoanRequest">
<part name="loanAmount" type="xsd:decimal"></part>
<part name="customerId" type="xsd:string"></part>
<part name="bankId" type="xsd:string"></part>
<part name="ssn" type="xsd:string"></part>
<part name="processConfig" type="tns:ProcessConfig"></part>
<part name="productId" type="tns:ProductId"></part>
</message>
|
清单 7. LoanRequest2.WSDL
<wsdl:definitions xmlns:tns="http://loanrequest/LoanRequest" ......
targetNamespace="http://loanrequest/LoanRequest">
<message name="processLoanRequest">
<part name="loanAmount" type="xsd:decimal"/>
<part name="customerId" type="xsd:string"/>
<part name="bankId" type="xsd:string"/>
<part name="ssn" type="xsd:string"/>
<part name="configParams" type="tns:ConfigParams"/>
</message>
|
解决方案
- 更该命名空间以使它们成为唯一的。
- 验证命名空间更改没有导致副作用。清除工作区并重新构建所有项目。如果没有在 WebSphere Integration Developer 中检测到任何错误,则将更新后的 SCA 应用程序部署到 WebSphere Process Server 中并进行测试。
对于从 EJB 生成的任何 WSDL 和 XSD,我们应该确保目标命名空间是唯一的(Java 类名称和包名称用于创建目标命名空间),以避免在迁移到 WebSphere Process Server V6 时发生冲突。
XSD 迁移问题
在前面提到的与贷款发放有关的同一个用例中,我们使用了返回类型 Object,此类型生成一个 anyType 类型,然而后者却具有下一部分所描述的后果。
避免使用 xsd:anyType
准确定义 WSDL 接口。避免使用引用 xsd:anyType 的 XSD complexType,因为WebSphere Integration Developer v6 不支持该类型。在尝试设置该类型的值时将会导致错误。
在下面的示例中,导入 SCA 模块的 WSDL 有一个指定了 anyType 的 complexType,从而在操作的输出中导致了类型 anyType(请参见图 3)。虽然在开发期间没有在 WebSphere Integration Developer 中检测到异常或错误,但是在运行时期间,当尝试获取或设置 Java 代码片段中的业务对象数据项的值时,将会引发一个“类强制转换异常”。
图 3. 带 xsd:anyType 的 XML 类型定义
例如,在尝试使用一个 Boolean 值来设置 Java 代码片段中的 getVariableReturn 时,将会导致从 WebSphere Process Server 运行时中引发类强制转换异常。
图 4. anyType 导致的类强制转换异常
//将存在一个由类型为 anyType 的 value 项导致的类强制转换错误; setDecisionVar.set("getVariableReturn",(Object)(new Boolean(true));
解决方案
- 修改对应的会话 Bean 接口,以避免使用
Object 作为输入/输出参数类型。(通过将参数类型指定为 Object,所生成的 WSDL 将指定 xsd:anyType 作为对应的 XML 元素类型。)
- 重新生成 Web 服务,随后可将其导入 SCA 模块。
- 按照新的 WSDL 修改迁移后的 BPEL 流程中的所有相关调用点。
BPEL 迁移问题
本系列第一部分中使用的贷款发放用例使用一个信用记录服务来批准或拒绝贷款。其中存在动态设置该服务端点的要求。该端点最初是使用业务流程引擎(Business Process Engine,BPE)API 来设置的。然而,WebSphere Integration Developer /WebSphere Process Server 具有一种更好的机制可供使用。
动态设置端点(第一个问题)
WebSphere Application Developer-IE 可以使用 BPEL 流程中嵌入的 Java 代码片段来动态设置端点。然而,该代码不再有效,因为 WebSphere Process Server 中的 BPE API 已经更改。因此,需要更新代码以使用新的 API。用于动态设置端点的原始代码如清单 8 所示。在清单 9 中,该代码已迁移为使用新的 WebSphere Process Server BPE API。
清单 8. WebSphere Application Developer-IE 中的动态端点设置代码
String ep = getApproveCheckRequest().getApprovalExtensionEP();
com.ibm.websphere.srm.bpel.wsaddressing.AttributedURI address = new AttributedURI();
com.ibm.ws.webservices.engine.types.URI addressValue = new URI(ep);
com.ibm.websphere.srm.bpel.wsaddressing.EndpointReferenceType
e = new EndpointReferenceType();
address.setValue(addressValue);
e.setAddress(address);
this.setPartnerLinkLoanApprovalExtension(e);
|
清单 9. WebSphere Integration Developer 中的动态端点设置代码
//Get the new address of approvalExtension parnter
String ep = approveCheckRequest.getString("approvalExtensionEP");
System.out.println("Setting approval extension endpoint to: " + ep);
if (ep != null && ep.length() > 0) {
ServiceManager sm = ServiceManager.INSTANCE;
Service s = (Service)sm.locateService("LoanApprovalExtension");
EndpointReference endPoint = s.getEndpointReference();
//Update the endpoint address for the partner "LoanApprovalExtension"
endPoint.setAddress(ep);
}
|
尽管用于设置引用伙伴端点的代码已经更新,但是如果该伙伴已连接到某个组件(如果在运行时期间在动态重置之前向它分配了绑定),则端点更改将不会生效。在运行时期间,调用仍将定向到缺省端点。
动态设置端点(第二个问题)
当将 SCA 模块中的某个引用保留为空时(例如,它未连接到任何 SCA 组件),则可以使用上述动态端点设置代码来将绑定信息附加到该空引用。然而,如果我们尝试使用不同的端点绑定来再次更新其端点,则该引用将不再有效,并且会引发一个“文档根目录错误”异常。这是 WebSphere Process Server v6 中的一个限制。
解决方案
SCA 编程模型可以使用 WebSphere Integration Developer /WebSphere Process Server“选择器”机制在运行时动态更改端点。选择器是 WebSphere Integration Developer /WebSphere Process Server 支持的一种 SCA 组件,可用于将来自客户端应用程序的操作路由到多个可能的 SCA 服务实现之一。因此,从功能的角度看,它可以完成与通过 BPE API 来动态设置端点相同的事情。图 5 显示了带有选择器的组装关系图
图 5. 使用选择器
结束语
本文描述了在将 WebSphere Application Developer-IE 业务集成项目迁移到 WebSphere Integration Developer 业务集成模块期间遇到的一些问题,并提供了一些解决方案。我们介绍了将 Java 和 EJB 绑定替换为 SOAP 绑定的重要性;我们不提倡使用 soapenc:Array,并提供了一种与使用 soapenc:Array 的服务进行交互的方法,用于那些无法更改的服务的场景(例如,第三方服务)。我们考虑了避免在 WSDL 定义中使用重复命名空间的重要性和避免使用 XSD:anyType 定义的原因。最后,我们考虑了使用选择器来动态更改端点而不是使用 BPE API。
致谢
本文中使用的实际应用程序由来自 IBM Strategic Technology Architecture and Incubation (STAI) 团队的同事通过 WebSphere Application Developer-IE 设计和开发完成。迁移工作由 Min Li、Zhi Gan、Yinchun Guo 和 Duowei Sun执行,他们全都来自北京的 IBM China Software Development Lab。
参考资料 学习
讨论
作者简介  | 
|  | Javier Garcia 是一位拥有 20 多年开发经验的顾问软件工程师。他当前在 SWG Strategy and Technology 部门工作,工作重点是 SOA 和组合应用程序。 |
 | 
|  | Qiang Wang 是 China Technology Institute, China Software Development Laboratory 的一位软件工程师。他的工作重点是 IBM Incubator Project 和 SOA 相关主题。他的兴趣包括 J2EE、SOA、MDA/MDD、AOP 和 RUP。他当前从事与 WBM、WebSphere Integration Developer 和 WPS 相关的开发工作。 |
对本文的评价
|