主要的软件供应商很快就意识到他们谁也无法支配企业平台市场。因此,互操作性成为当务之急。
软件行业已经使用各种技术尝试实现互操作性(参见“互操作性简史”) 但是,这些尝试的一个不足在于它们都假设您能将远程对象作为本地对象来对待,而实际情况并非如此。另外,参数类型的兼容性意味着语义的兼容性,并且它们不允许系统由服务水平协议(service level agreements,SLA)构建块所组成。因此我们需要做一些其他的事情。
随后,可扩展标记语言(Extensible Markup Language,XML)作为一种数据表达语言出现了,从此用于软件开发的一个更加面向服务的架构诞生了。简单对象访问协议(Simple Object Access Protocol,SOAP),Web Service和一系列WS-*标准随之产生。除此之外,还建立了Web 服务互操作性(Web Services Interoperability,WSI)组织来协调各种标准。软件行业的主要公司(包括BEA和Microsoft)都采用标准化的XML和基于SOAP的Web Service。那么,这是否意味着真正的互操作性在今天是可能的?
下面我们将介绍如何使用Microsoft .Net和BEA WebLogic以一种与各种标准兼容的方式构建Web Service。让我们从一些面向服务的架构(SOA)基础开始吧。
SOA与Web Service
作为Web Service实现的SOA与面向对象的架构(如CORBA和Java RMI)根本不同。与COM和DCOM的面向组件技术也不同。COM+和J2EE会话bean展现出了一些SOA的成分,例如松散耦合、SLA、和SLA的组成。
面向服务的架构必须验证所有的契约,不管是从结构上和语义上用schema(XML Schema Definition [XSD]),或是Web服务定义语言(Web Service Definition Language,WSDL),和用于Web服务的业务流程执行语言(Business Process Execution Language for Web Services,BPEL4WS),还是通过WS-Policy和相关标准的SLA。
Web Service可能由其他Web Service组成,而Web Service SLA可能由其他多种策略组成。Web Service还是基于标准的并且必须是松散耦合的,这意味着它们是基于消息的并且是平台无关的。还要考虑故障的产生。
由于潜在的往返代价,对Web Service的访问是粗粒度的。“对象“中的单个域是不允许直接访问的,Web Service几乎没有对大量数据的接口方法。
图1是当今Web Service标准的一个概况。这些标准仍在迅速变化中。

图1
这是当今的Web Service标准。绿色的标准没有任何互操作性问题,黄色的标准仅有部分供应商支持,白色的则没有供应商支持,这种情况可以得到迅速改变
项目概述
BEA Systems 雇请IDesign(一个.Net架构的咨询和培训公司)来研究如何使.Net和WebLogic以一个标准兼容的方式进行互操作。此项目重用了BEA WebLogic Platform 8.1 Evaluation Guide(评估WebLogic8.1的参考应用)第一章中的实现。这一章指导开发人员开发一个Web应用程序和假想公司Avitek(销售照相机和附件)的一套Web Service(参见图2)。Avitek应用程序在WebLogic中的架构如图3所示。作为对比,.Net版本的架构如图4所示。此研究的目的在于:
- 展示Workshop和.Net之间真正的Web Service互操作性现在已经成为现实。
- 证明这种互操作性可以以安全的方式完成。
- 为Microsoft 和 BEA程序员提供Web Service互操作的实践指导。
- 展示Visual Studio .Net和WebLogic Workshop如何为实现这种互操作性提供高效的生产环境。
- 提出最佳实践方式和突出提示,窍门和容易犯的错误。
- 展望互操作性的未来。

图2 这是运行在Workshop测试浏览器里的Avitek订单输入向导页面

图3 该图显示了摘自WebLogic Platform 8.1 Evaluation Guide(WebLogic8.1评估指导)的Avitek应用程序架构

图4
Avitek的应用程序架构(.Net版本)
开始之前需要注意: BEA的Workshop、WebLogic Server,和Visual Stuidio.Net 2003都是强大的用户应用程序。为了实现可接受的性能,您的开发工作站要有1 GB的RAM。
混和环境应用程序
Avitek应用程序包括一个.NetWinForms编写的智能客户端。这个客户端在WebLogic 8.1中调用一个同步的订单输入Web Service,并使用SSL传输层安全和WS- Security的用户名和密码。已经使用调用基于ASP.Net的Web Service来替代Workshop中的PricingService。由此得到如图5所示的结构。

图5
Avitek的互操作应用程序架构
为了在该项目中实现互操作性。我们遵循一些常规的指导方针。我们使用标准的Web Service(.Net到WebLogic或者WebLogic到.Net),在WebLogic和.Net之间使用“文档”样式的SOAP(在两个环境下都是标准的)。为保证安全性,我们使用SSL来维护保密性(加密),使用没有消息签名的WS- Security用户名/密码符号。最后,我们禁用了MustUnderstand(用于在.Net客户端路由报头,在写的同时,WebLogic并不知道如果如何在Microsoft的Web Services Enhancement [WSE]中处理路由报头)。
现在更加详细地介绍一下各个方面。
.Net到WebLogic。从.Net应用程序使用基于WebLogic的Web Service很简单。您只要在.Net应用程序里添加Web引用。一个向导会指导您完成整个过程(从Workshop的测试浏览器中复制WSDL的URL)。接下来实例化Web Service的代理类(当添加一个Web引用时VS.Net会创建代理类),并调用方法。
WebLogic到 .Net。从WebLogic应用程序使用基于ASP.Net的Web Service同样也很简单。在您的WebLogic客户端应用程序里添加一个Web Service控制(.jcx)。一个向导会指导您完成整个过程(从VS.Net的测试页面复制WSDL的URL)。接下来用控制(Workshop添加这个实例化过程)来调用服务的方法。
文档样式和RPC样式的 Web Service。.Net和WebLogic默认使用文档样式的SOAP。BEA能够使用和产生RPC样式的SOAP;但是, 除非当您调用的平台不支持文档字母编码, 不然没有必要使用RPC样式的SOAP。
SSL安全性。SSL在数据的保密性上做得非常好,但是您需要真正的认证。BEA平台的测试认证对于测试BEA与BEA之间的应用程序非常有用,不过这个认证对.Net不适用。在 www.interopwarriors.com 上已经有了一个规避方法。
WS-Security与策略文件。基于X509的签名和加密不是可互操作的。SSL仍然是保密的最佳选择。如果您使用WSE1.0,用户名标识(包含用户名和密码)的WS- Security是互操作的。但是,您必须设置路由报头的MustUnderstand标志为“false”,并避免添加基于用户名的签名。策略文件也不是互操作的,您必须手动对它们进行转换,本项目包含了两种环境的策略文件,所以简单的复制粘贴就足够了。
在撰写本文的时候,我们还不能用X509进行签名和加密。在本文出版的时候BEA应该有补丁了。虽然标准还在变化,策略文件在不久的将来应该是互操作的。到那时,无需介入,Web Service就可以在所需协议之间进行协商。
异步的Web Service就是绑定了一个协调ID的SOAP消息,很多人认为将来它是松散耦合可互操作Web Service。BEA通过WS- Conversation支持这种Web Service,虽然WS-Conversation不是标准化的,但对.Net来说,当使用请求/响应模式的时候,Microsoft推荐使用.Net的内建异步调用机制。WS-Addressing和WS-BusinessActivity将会替代这种专有的实现。注意:WS标准的名字仍然在变化。)
实现
让我们逐步创建这个项目,我们使用BEA WebLogic Platform 8.1 Evaluation Guide中第一章里的Acitek的示例(您也可以从该项目的第二章开始,其中包含了第一章的所有源代码,要了解相关的技术,参见Technology Stacks。
第一步,在Visual Studio.Net中创建一个包含两个所需项目和所有测试项目的空解决方案AcitekInterop。
从.Net 1.1调用WebLogic8.1的Web Service。首先添加一项新的Web服务OrderEntryWebService2(右键点击Avitec/webServices/orderEntryInterface)。或者,您可以修改现有的OrderEntryWebService;但是,如果出现错误,您的工作拷贝就会丢失。接下来,从Avitec 控件添加控件“Avitek Order-Entry Service”(右键点击设计器的较暗部分并选择Add Control)。
现在,添加一个getAllProducts Web方法(WebLogic),它允许Windows智能客户端检索产品目录。这可以用鼠标在Data Palette拖曳OrderEntryService的getAllProducts方法实现。然后,将函数的名字修改成getProductCatalog。再添加一个名为ProductCatalog.xsd的XML模式文件到目录/Acitec/Schemas下(参见清单1)。
清单1. OrederEntryWebservice2外部参数的XML模式定义。
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace=
"http://tempuri.org/ProductCatalog.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mstns="http://tempuri.org/ProductCatalog.xsd
"xmlns="http://tempuri.org/ProductCatalog.xsd"
elementFormDefault="qualified"
id="ProductCatalog">
<xs:element name="ProductCatalog">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="Product">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:int"/>
<xs:element name="Description" type="xs:string"/>
<xs:element name="UnitPrice" type="xs:decimal"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
这个数据集把Avitek的私有数据隐藏起来。现在,我们在Web services designer中右键点击getProductCatalog方法以把该数据映射到内部数据上,然后选择Edit XML Schema,点击Return XML选项卡,点击击Choose,然后在列表中找到并选择ProductCatalog.xsd。点击OK,然后点击Edit XQuery。现在,我们要画出如图6所示的数据映射。在出现的两个对话框中都单击OK,然后测试您的Web Service(您应该能看到从getAllProducts返回的新类型)。

图6
在Workshop中将内部模式映射到外部模式
BEA调用这种松散耦合,并且对其互操作性很有意义。如果您有事先定义好的外部模式,这种方法就允许您以一种简单的方式来配合应用程序的API。
现在,我们需要添加一个createOrder Web方法(WebLogic),它可以允许Windows智能客户端创建一个订单。我们可以通过从Data Palette拖曳OrderEntryService的createOrder方法来实现。
接下来,您要修改以下函数以使用硬编码的客户ID(而不是参数):
public java.lang.String createOrder(
OrderEntryServiceFolder.OrderInfo order)
{
int customerID = 1;
return orderEntryService.createOrder(
customerID, order);
}
我们现在来创建一个能使用OrderEntryWebService2的.Net智能客户端。利用VS.Net 2003创建一个名为AvitekOrderEntryUI的Windows应用程序客户端。您可以使用VB.Net或C#。我们用VB.Net编写Windows客户端,稍后用C#编写PricingService。
下一步,是修改默认的生成名称(好的编程习惯)。把默认的Form1.vb改名成WinUI.vb,再把代码中的Form1用WinUI替换掉(出现两次)。把项目属性对话框里的启动对象改为WinUI,再把窗体的标签改为Avitek Order Entry Smart Client。
再为定单输入数据网格(参见图7)的数据绑定添加一个数据集(OrderEntryDataset.xsd)。我们从Toolbox选项卡的Data中拖曳一个数据集控件到WinUI的设计视图中,就能添加这个数据集了。然后点击Add Dataset对话框里面的OK按钮接受选择(AvitekOrderEntryUI.OrderEntryDataset)。

图7
VS.Net的数据集设计器
要为窗体添加一个DataGrid,只需从Toolbox选项卡的Windows Forms里面拖动DataGrid控件即可。确定它的大小,并改名为m_OrderEntryDataGrid(在属性窗口里)。将CaptionVisible属性设置为false,DataSource属性设置为m_OrderEntryDataset,再把DataMember属性设置为OrderLine。您还可以随意设置列数,并禁止编辑前三列。
现在要在DataGrid下方加一个Submit Order!按钮。在Solution Explorer中右键点击AvitekOrderEntryUI/References,就能添加一个对基于WebLogic的Web服务OrderEntryWebService2的Web引用。选择Add Web Reference…,启动在Workshop里OrderEntryWebService2 Web服务。浏览Overview(参见图8),然后查看Complete WSDL(参见图9)。复制图9里地址栏的URL,将其粘贴到VS.Net的Add Web Reference对话框中URL字段中,点击Go按钮(应该出现一个描述页,参见图10)。把Web reference name改成AvitekWS(从localhost处修改)。这样就能为Web Service创建一个命名空间。点击Add Reference按钮,再添加代码以导入这个命名空间:
Imports AvitekOrderEntryUI.AvitekWS

图8 Workshop测试浏览器显示了Web Service的overview页面

图9 Workshop测试浏览器显示WSDL文件

图10 VS.Net的Add Web Reference向导
现在添加一个窗体加载句柄,设计视图中双击面板(创建Sub WinUI_Load),并添加初始化代码:
Dim catalog As ProductCatalogProduct()
Dim orderWS As OrderEntryWebService2
orderWS = New
AvitekWS.OrderEntryWebService
'set real URL here
'orderWS.Url = ""
catalog = orderWS.getProductCatalog()
m_OrderEntryDataset.Clear()
For Each product As
AvitekWS.ProductCatalogProduct In catalog
OrderEntryDataset1.OrderLine.AddOrderLine
Row(product.ID, product.Description,
product.UnitPrice, 0)
Next
M_OrderEntryDataset.AcceptChanges()
双击设计视图里的Submit Order!按钮(创建Sub SubmitButtion_Click)就能创建这个按钮的句柄。然后添加清单2所示的代码以处理提交请求。现在可以测试应用程序了。
清单2. 这是将订单提交给WebLogic Web Service的.Net代码
'create order data structure and set priority to
medium
Dim order As New OrderInfo order.Priority =
"Medium"
'count number of products with a positive order
quantity
Dim count As Integer
Dim result As String
Dim row As OrderEntryDataset.OrderLineRow
count = 0
For Each row In m_OrderEntryDataset.OrderLine.Rows
If row.Quantity > 0 Then
count += 1
End If
Next
If count > 0 Then
'create order line items based on count and
fill each line
'for C# programmers: in VB.NET
specify the upper
boundary, not the number of elements
order.OrderItems = New OrderLineItems(
count - 1) {}
count = 0
For Each row In
m_OrderEntryDataset.OrderLine.Rows
If row.Quantity > 0 Then
Dim item As New OrderLineItems
item.ProductID = row.ProductID
item.Quantity = row.Quantity
order.OrderItems(count) = item
count += 1
End If
Next
'create web service proxy
Dim orderWS As OrderEntryWebService2
orderWS = New OrderEntryWebService2
'set real URL here
'orderWS.Url = ""
'submit the order and reset the dataset
result = orderWS.createOrder(order)
'reset the UI
m_OrderEntryDataset.RejectChanges()
Else
result = "You must order at least one item!"
End If
MessageBox.Show(result, "Submit Order Result")
换一种思维
我们来看看从WebLogic调用一个.Net的Web Service又应该怎么办。第一步是在.Net里面建立PricingService。PricingService是BEA的评估指导中发布的一个企业JavaBean(EJB)。在这个实现中,您将不再继续使用它,而是使用一个.Net Web Service。
在VS.NET中添加一个新的名为AvitekPricingService的Web Service项目:
- 选择File -> New -> Project
- 选择 Visual C# Projects
- 选择ASP.Net Web Service
- 给项目命名为AvitekPricingService
- 将默认的Service1重命名为PricingService (PricingService.asmx以及代码中的所有引用)。
添加清单3中所示的代码来实现GetDiscountRate。
清单3. 实现GetDiscountRate Web方法的.Net代码
[WebMethod]
public double GetDiscountRate(int custID)
{
// this is an arbitrary implementation; real world
// implementations would look up the customer
// discount rates in a database
switch (custID % 4)
{
case 0:
return 0.00;
case 1:
return 0.05;
case 2:
return 0.10;
case 3:
return 0.15;
default:
throw new InvalidCastException(
"Modulo operation yielded bad value");
}
}
现在您需要修改BEA Avitek项目以使用.Net Web Service。第一步,在设计视图中打开OrderEntryServiceImpl.jcs并删除已有的pricingControl。下一步给基于.Net的Web Service添加一个控件,在面板上点右键点击OrderEntryService Java Control并选择Add Control->Web Service。在对话框中(见图11)填充如下内容:

图11 Workshop的Insert Control向导
图12 这是Workshop中完整的OrderEntryservice控件
.Net标准编码实践中最初使用大写字母为方法命名。因此,您需要把 getDiscountRate的调用代码修改成使用大写字母G:
discountRate =
dotNetPricingService.GetDiscountRate(
custID);
Web Service安全
您需要在WebLogic项目中指定WS- Security的需求。评估指导是跟一份预先为X509消息级安全配置好的策略文件一起提供的。您可以使用用户名和密码认证。更改策略文件OrderEntryWebService2. wsse来设定需要的用户名和密码:
<?xml version="1.0" ?>
<wsSecurityPolicy xsi:schemaLocation=
"WSSecurity-policy.xsd"
xmlns="http://www.bea.com/2003/
03/wsse/config"
xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance">
<!--
Incoming SOAP messages must be
accompanied by a username and
password.
-->
<wsSecurityIn>
<token tokenType="username"/>
</wsSecurityIn>
</wsSecurityPolicy>
将ws-security-service -> file property of OrderEntryWebService2更改为AvitekWebServicePolicy2.wsse。
要使用Microsoft的WSE1.0,退出VS.Net并安装WSE1.0(参见Resources)。在Microsoft Web Service1.0中添加一个引用并添加如下app.config文件,把项目修改为使用WSE:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- Defines WSE version (1.0) for this
client application -->
<configSections>
<section name="microsoft.web.services"
type="Microsoft.Web.Services.
Configuration.WebServicesConfiguration,
Microsoft.Web.Services, Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</configSections>
</system.net>
</configuration>
右键点击Web reference AvitekWS并选择Update Web Reference,重新生成代理类。如此创建了两个代理类:一个是用于通常的Web Service,一个是关于使用WSE。
在WinUI.vb的开头添加如下import语句:
Imports Microsoft.Web.Services
Imports Microsoft.Web.Services.Security
然后把在WinUI_Load和SubmitButton_Click中的所有对Web Service代理的引用都从OrderEntryWebService2更改为OrderEntryWebService2Wse:
Dim orderWS As OrderEntryWebService2Wse
orderWS = New OrderEntryWebService2Wse
在WinUI_Load和SubmitButton_Click添加代码,以将用户名符号连接到Web Service调用。同时还需要为路径报头禁用MustUnderstand SOAP报头属性。
'hard code user credentials for demo
Dim userToken As UsernameToken
Dim requestContext As SoapContext
userToken = New UsernameToken(
"jane", "janejane",
PasswordOption.SendPlainText)
requestContext = orderWS.RequestSoapContext
requestContext.Security.Tokens.Add(
userToken)
requestContext.Timestamp.Ttl = 60000
requestContext.Path.EncodedMustUnderstand =
"false"
现在我们来学习如何在WebLogic项目中使用WS-Security报头。经过认证的用户名包含在JwsContext中。用户名可以转换成一条Avitek客户记录。在OrderEntryWebService2.jws的开头处添加如下import语句:
import com.avitek.commerce.CommerceCustomer;
import com.bea.control.JwsContext;
然后,在Web Service类的开始处添加一个指向JwsContext的引用。
/**
* @common:context
*/
JwsContext context;
现在将方法createOrder(OrderEntryWebService2.jws)中的:
int customerID = 1;
替换为以下粗体的行:
public java.lang.String createOrder(
OrderEntryServiceFolder.OrderInfo order)
{
String customerName =
context.getCallerPrincipal().getName();
CommerceCustomer customer =
orderEntryService.findCustomerByLogin(
customerName);
int customerID = customer.getCustID();
return orderEntryService.createOrder(
customerID, order);
}
您现在可以在项目中启用SSL传输,但这只是可选项。要启用该传输,在web.xml(在Avitek/AvitekWeb/WEB-INF文件夹中)添加如下行:
<web-resource-collection>
<web-resource-name>
OrderEntryWebService2.jws
</web-resource-name>
<url-pattern>
/webServices/orderEntryInterface/
OrderEntryWebService2.jws/*
</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
在wlw-config.xml(在Avitek/AvitekWeb/WEB-INF文件夹中)中添加如下行:
<service>
<class-name>
webServices.orderEntryInterface.
OrderEntryWebService2
</class-name>
<protocol>https</protocol>
</service>
OrderEntrywebService2现在为其所有的通信都使用SSL。WebLogic附带有一个SSL测试证书,但是这不能用于.Net。您必须购买一个正式的SSL证书或者从证书颁发机构获得一个时间受限的证书。更多细节参见1.7节,步骤B:Securing Web Services With Transport-Level Security in the WebLogic evaluation guide。
最后一步是更改.Net Web Service代理类(Reference.vb)中的URL以读取https而非http。
我们学到了什么?
从一个.Net客户端调用一个基于WebLogic的Web Service是很容易的。BEA Workshop的模式映射简化了任何所需的模式调整。在这个项目中,产品目录的私有细节通过这种映射得以隐藏。从一个WebLogic客户端调用一个ASP.Net的Web Service也同样简单。所有这些任务都只需几分钟即可完成。要实现一个安全的调用环境则有些困难,但正如我们所证明的,WebLogic Workshop可以与.Net实现互操作。
|