创建web服务时,开发人员通常会从现有的WSDL、现有的实现或者以上二者的组合开始。在本文中,我们分析了WebLogic Workshop 8.1的一些功能,这些功能可以使这些web服务变得健壮,从而无惧WSDL或实现数据类型中的变化。在这个过程中,我们还注意到WebLogic Workshop和它生成的工件之间的交互,比如带注释的web服务文件。
本文在给定一些WSDL、一个实现类或者以上二者的一些组合的情况下,考虑了您可以在一个简单的web服务中融入灵活性的各种方式。例如,您可以从特定的WSDL和已有的一些种类的实现开始。您如何按照WSDL中指定的那样,把传入的数据映射为您的实现中的参数呢?有一个好机会,即WSDL中定义的数据类型和您的实现中的数据类型不匹配。从不同的角度来看,或许您有一个既定的web服务实现,但是现在有客户端想要您支持一个略有不同的WSDL接口(也许是一个更新的版本)。第一感觉可能是重写实现,或者可能的话,修改WSDL——但是上述两种方法都无法令人满意。
重写实现可能会影响系统的其他部分,而且它会给编码人员增加很多负担。如果WSDL再次以一种保持数据但是修改所使用的XML架构的方式变化,那又该如何呢?您将不得不再次修改您的实现。另一个选择是修改WSDL,但这种方法也不总是可用——尤其是当已经建立并使用web服务时。
WebLogic Workshop提供几种方法实现WSDL和实现之间的阻抗匹配。它有效地使用了一种功能强大的转换机制,用于在从客户端接收到的XML和您的实现所使用的XML之间进行转换。它为web服务返回的XML提供同样的功能。这使得您的接口和实现更加不容易变化,而且灵活的多。本文对这些机制进行了剖析。它假定您已经具有XMLBeans、 XML架构和WSDL方面的知识。
本文中列举的例子显示了Workshop生成的所有文件,并证明了这种灵活性仍然可以使开发人员完全控制web服务代码。本文末尾提供了用于下载所有代码的连接。
考虑创建Web服务
创建我们感兴趣的web服务的两个方面是到web服务本身的接口(通常在WSDL中指定),和您想要关联到web服务接口的业务逻辑。根据您已经建立的是WSDL还是实现,您可以采取4种方法:
- 已建立WSDL,当前无实现——在这里,您已经有了描述服务的WSDL,而且您需要创建web服务。WebLogic Workshop让您可以直接从WSDL简单地创建web服务实现。WebLogic Workshop还可以自动生成必要的类型,当然,接口和实现之间将会有几近完美的映射。
- 无现有实现或WSDL——假定您需要创建web服务来公开一些数据,而且你既没有实现,也没有WSDL。在这种情况下,您只要首先创建它们,然后遵照其他方法之一即可。Workshop推荐的方法是创建用于描述您想要在公共接口中公开的数据的XML架构类型,并且在您的实现中使用这些类型。然后,Workshop可以为您自动生成WSDL,以及使用并公开这些类型的web服务。
- 已建立实现,无WSDL——这里,您有您想要公开的web服务的现有实现,但是对WSDL没有约束。正如我们将看到的那样,Workshop可以自动公开参数,并执行在方法中使用的参数和(新的)WSDL中定义的类型之间的必要类型映射。Workshop创建XML映射,该图可将您的实现的数据类型映射为可以用在WSDL中的XML架构。当确定您想要在WSDL接口中公开的确切类型时,这些XML映射可以提供很大程度上的灵活性。
- 已建立WSDL,且有现有实现——假定您有一个预定义的WSDL和一些已经写好的业务逻辑,而且WSDL中公开的数据类型和实现的数据类型不匹配。这种情况通常出现在WSDL接口以某种方式变化时。Workshop提供XQuery来填补这条鸿沟。这种机制可以把传入消息中的数据映射为实现中使用的类型,而且反之亦然。
本文的余下部分对这每一个用例都进行了分析。注意,我们没有把重点放在Workshop提供的许多其他web服务功能上。例如,我们没有说明如何添加缓冲、回调或web服务会话,我们也没有指出在创建与其他控件交互的web服务控件过程中的灵活性。
已建立WSDL,无实现
让我们考虑第一种情况:我们有一份描述某个web服务的WSDL文档,而且我们想实现该web服务。正如您将看到的那样,这很容易做到。清单1显示了我们将要使用的WSDL。
清单1.Established.wsdl
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.openuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
targetNamespace="http://www.openuri.org/">
<types>
<xsd:schema elementFormDefault="qualified"
targetNamespace="http://www.openuri.org/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:art="http://www.oreilly.com/2004/articles">
<xsd:element name="myMethod">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="art:myarticle"/>
<xsd:element name="value" type="s:int"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="myMethodResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="myMethodResult" type="s:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<xs:schema elementFormDefault="qualified"
targetNamespace="http://www.oreilly.com/2004/articles"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:articles="http://www.oreilly.com/2004/articles">
<xs:element name="myarticle">
<xs:complexType>
<xs:sequence>
<xs:element ref="articles:myid"/>
<xs:element ref="articles:title"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="myid" type="xs:ID"/>
<xs:element name="title" type="xs:string"/>
</xs:schema>
</types>
<message name="myMethodSoapIn">
<part name="parameters" element="tns:myMethod"/>
</message>
<message name="myMethodSoapOut">
<part name="parameters" element="tns:myMethodResponse"/>
</message>
<portType name="MyServiceSoap">
<operation name="myMethod">
<input message="tns:myMethodSoapIn"/>
<output message="tns:myMethodSoapOut"/>
</operation>
</portType>
<binding name="MyServiceSoap" type="tns:MyServiceSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="myMethod">
<soap:operation soapAction="http://www.openuri.org/myMethod" style="document"/>
<input> <soap:body use="literal"/> </input>
<output> <soap:body use="literal"/> </output>
</operation>
</binding>
<service name="MyService">
<port name="MyServiceSoap" binding="tns:MyServiceSoap">
<soap:address location="http://localhost:7001/MyWSProject/MyWS/MyService.jws"/>
</port>
</service>
</definitions>
这里,我们想要重点讨论的关键元素是,WSDL定义了一个描述web服务的输入和输出的类型架构。实现一个满足这种WSDL条件的web服务的基本方法如下:
- 把WSDL文件导入到一个web服务项目中。
- 在Schemas项目中放上一份它的拷贝。这将生成映射到架构的XMLBeans,我们稍后可以使用它。
- 右键单击web服务项目中的WSDL文件,然后选择“Generate Web Service”。然后,您将看到一个对话框,内容是存在匹配的XMLBean并询问是否要使用它们。点击“Yes”。
这将生成一个Java Web Services (JWS)文件,它在WebLogic Workshop中的扩展名为.jws。至此,您可以运行应用程序,这将编译生成的XMLBeans,部署web应用程序,并在您的浏览器中打开Workshop测试控制台。清单2显示了一些这种生成的文件的内容。
清单 2. 生成的Java Web Services文件
/**
* :wsdl file="#MyServiceWsdl"
* -info:link autogen-style="xmlbean" source="Established.wsdl" autogen="true"
*/
public class Established implements com.bea.jws.WebService
{
/**
* :operation
* :protocol form-post="false" form-get="false"
*/
public java.lang.String myMethod (com.oreilly.x2004.articles.MyarticleDocument myarticle, int value)
{
// place your code here
return myarticle.getMyarticle().getTitle();
}
static final long serialVersionUID = 1L;
}
这是一个简单的以元数据表示的POJO实现文件。方法的第一个参数具有和WSDL中定义的类型相应的生成XMLBean类型。测试环境让您输入您想要传递给方法的XML,这样在这个例子中,我们可以使用下面的文档:
<myMethod xmlns="http://www.openuri.org/" xmlns:art="http://www.oreilly.com/2004/articles">
<art:myarticle>
<art:myid>string</art:myid>
<art:title>string</art:title>
</art:myarticle>
<value>3</value>
</myMethod>
使输入和输出映射为XMLBeans有很多好处。开发转向变得很快(我们只要把WSDL文件放入Schema项目中即可),而且您可以立即访问传入文档的所有部分。这里的关键所在是,WSDL中使用的架构直接对应于实现中使用的XMLBean类型。这里出现了一个问题,即在不用编写复杂的映射代码的情况下,如何处理WSDL或我们在实现中使用的XMLBean出现变化的情形。稍后我们将给出这个问题的答案。
无已建立的WSDL或实现
如果没有准备好的稳定WSDL或实现接口,我们自己可以自由地定义这一切。开始的最快方法是定义我们想要在web服务接口中公开的任何复杂类型。这项工作应该使用标准的XML架构来完成。我们支持使用XMLBean方法,因为这种方法可以在后面以XQuery图的形式提供灵活性,本文稍后将给出相应描述。
清单 3. 架构NWNIArticle.xsd示例
<?xml version="1.0"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.oreilly.com/2004/articles2"
targetNamespace="http://www.oreilly.com/2004/articles2"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="myarticle2">
<xs:complexType>
<xs:sequence>
<xs:element ref="tns:myid"/>
<xs:element ref="tns:title"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="myid" type="xs:ID"/>
<xs:element name="title" type="xs:string"/>
</xs:schema>
清单3显示了这样一个示例架构。注意,它和清单1 WSDL中定义的架构稍微有些不同。它还有一个不同的命名空间。您有了架构之后,将其放入Schema文件夹中,然后它会生成相应的XMLBean。
要创建web服务,遵照使用WebLogic Workshop的项目中的通常步骤:
- 右键单击项目,然后选择"New",然后选择"Web Service"。这将生成一个空白的JWS文件,我把它命名为NoWSDLNoImpl.jws。
- 使用图形视图,创建一个名为"myMethod"的方法。单击它可以查看源代码视图,您可以在源代码视图中修改方法的签名。
对于这个例子来说,我已经把方法的签名修改为使用我们刚刚定义的XMLBean类型:
import com.oreilly.x2004.articles2.Myarticle2Document;
/**
* :operation
*/
public String myMethod2(Myarticle2Document doc, int value) {
return doc.getMyarticle2().getTitle();
}
我们再次以与实现中的相应XMLBean匹配的WSDL中描述的类型作为结束。下面的两节对这一点进行了扩展,具体是通过分析如果正好相反,我们想要在与WSDL中的架构无简单对应关系的实现中公开类型(使用XML映射),或者如果出于某些原因,我们想要修改定义在WSDL中的接口类型(使用XQuery),我们要做些什么。
无已建立的WSDL和已建立的实现
现在,让我们分析这样一种情况,即您有(假定为)Java控件形式(或封装在Java控件中)的业务逻辑,而且您想把它们公开为web服务。例如,我们可能有像下面这样的方法(当然,在实际的实现中,业务方法的功能更加复杂,比如调用其他控件):
public int businessMethod(int value, String artName) {
return value * 2;
}
既然我们想要给出一种没有使用XMLBean的方法(前面的各节说明了如何使用XMLBean来做到这一点),我们选择了在参数中使用简单的Java类型。把它公开为web服务最简单的方法是,右键单击Java控件,然后选择"Generate Test JWS (Stateless)"。结果将生成一个仅仅使用Java控件中的业务逻辑的JWS文件:
package MyControl;
/**
* -info:link autogen-style="stateless" source="MyBusinessImpl.jcs" autogen="true"
*/
public class MyBusinessTest implements com.bea.jws.WebService
{
static final long serialVersionUID = 1L;
/** :control */
public MyControl.MyBusiness myBusiness;
/** :operation
*/
public int businessMethod(int value, java.lang.String artName)
{ return myBusiness.businessMethod(value, artName); }
}
现在,我们将要考虑的是如何操纵将被公开给客户端的WSDL。如果您想要了解Workshop为JWS文件指派了哪个WSDL,您可以右键单击该文件,然后选择"Generate WSDL File"。Workshop自动创建WSDL和描述输入参数(和返回值)的架构,而且它会把该架构映射为参数(和返回值)。例如,下面给出了用于调用我们的业务方法的合法输入:
<businessMethod xmlns="http://www.openuri.org/">
<value>3</value>
<!--Optional:-->
<artName>string</artName>
</businessMethod>
为了创建这个,WebLogic Workshop事实上使用了一组默认的XML映射把SOAP体中的传入XML映射为业务方法的参数。(在本节的余下部分中,我们只讨论参数,但是XML映射也可以应用于返回值。)您还可以创建定制的XML映射;这可以使您有效地控制Workshop如何把传入SOAP消息中的XML映射为Java参数。正如您将看到的那样,这还可以为您提供一个机会修改您的web服务,以处理不同的SOAP消息,而无需修改实现。如果我们不喜欢这些映射,或者如果我们考虑到未来的变化,我们可以自己改进它们。
如果您选择JWS文件中的业务方法,您将看到Property Editor在"parameter-xml"部分的下面有一个"xml map"项。选中此项将出现"Edit Maps and Interfaces"对话框,其上显示默认的XML映射,并为您提供修改它们的机会。图1显示了当前例子的结果。

图 1.默认的 XML映射
XML映射为选择XML文档中的数据提供了一种非常直观的方式。在图1中,大括号中的名称与我们实现中的参数相对应。被传递给参数时,它们在XML上下文中的位置决定了数据的来源。例如,值参数将从"value"元素中的文本获得它的值,该元素本身应该位于"businessMethod"元素中,等等。
那么,让我们看看如何使用这些映射。假定,我们想给元素取不同的名称,而把值定义为一个属性而不是一个元素。(换一种说法,假定WSDL被修改,这样传入的XML就不一样了。)接着,我们可以简单地把XML映射修改为下面这样:
<businessMethod xmlns="http://www.openuri.org/"
<articleDetails value="{value}">
<name>{artName}</name>
</articleDetails>
</businessMethod>
实际上,您正以一种十分有趣的方式修改WSDL。通过简单地指定XML的形状,WebLogic Workshop断定WSDL是必需的,并在您的方法之前添加了一个转换层。所以对于这个例子,如果您为JWS生成WSDL,您将看到如下内容:
<s:element name="businessMethod">
<s:complexType>
<s:sequence>
<s:element name="articleDetails">
<s:complexType>
<s:sequence>
<s:element name="name" type="s:string"/>
</s:sequence>
<s:attribute name="value" type="s:int"/>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
这是修改您的web服务的一种强大手段,根本不用花费什么力气。XML映射本身作为JWS文件的另一个属性而结束,查看源代码时您可以查看这个属性。
XML映射提供的转换系统比我们刚刚看到的还要丰富得多。例如,它们可以处理重复性元素,把它们映射为数组值。还有用于指定映射的纯XML语法。这对于从一些其他数据自动生成映射相当有用。您还可以把XML映射放在文件中;这允许您构建一个可重用转换的库。最后,您可以使用ECMAScript (JavaScript)的扩展版本,通过编程来修改定义Java参数的XML消息组件。为了很好地概览这些功能,请参考文章如何使用XML映射,或者查看WebLogic Workshop的优秀文档。
已建立的WSDL和已建立的实现
在最后一个场景中,我们有一个已经建立的web服务接口(我们有描述服务的WSDL)和一个已经建立的实现(使用XMLBean),而该实现并不十分适合该web服务接口。注意,如果我们只是映射为标准的Java类型,而不是XMLBean参数,那么我们可以使用前面描述过的XML映射。
作为一个示例实现,这里给出了一个带两个参数的简单Java控件。第一个参数是由XMLBean(映射为清单3中描述的架构)定义的,而第二个参数是double类型的。
package Control;
import com.bea.control.*;
import com.oreilly.x2004.articles2.Myarticle2Document;
/**
* -info:code-gen control-interface="true"
*/
public class SimpleImpl implements Simple, ControlSource
{
static final long serialVersionUID = 1L;
/**
* :operation
*/
public String businessMethod(Myarticle2Document ad, double value)
{
return ad.getMyarticle2().getTitle();
}
}
我们试着设定这样一种场景,即我们有一个不兼容的WSDL。这里的关键在于,我们准备使用WebLogic Workshop在传入SOAP消息和实现之间插入一个转换器。所以,一天过去之后,我们在Java Web Service文件中公开的业务方法将和上面的Java控件拥有相同的签名。然而,我们将把传入类型声明为不同的类型,并构建一个转换器来匹配二者。举一个例子,让我们假定WSDL声明输入的参数以遵从这些架构:
<xs:schema elementFormDefault="qualified"
targetNamespace="http://www.oreilly.com/2004/articles3"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:articles="http://www.oreilly.com/2004/articles3">
<xs:element name="article">
<xs:complexType>
<xs:sequence>
<xs:element ref="articles:title"/>
<xs:element minOccurs="0" ref="articles:prices"/>
</xs:sequence>
<xs:attributeGroup ref="articles:article.attlist"/>
</xs:complexType>
</xs:element>
<xs:element name="description" type="xs:string"/>
<xs:attributeGroup name="article.attlist">
<xs:attribute name="id" use="required" type="xs:ID"/>
</xs:attributeGroup>
<xs:element name="title" type="xs:string"/>
<xs:element name="prices">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="articles:price"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="price" type="xs:string"/>
</xs:schema>
和示例文件中的EstablishedAndImpl.xsd一样,这个架构是可用的。如果我们的WSDL声明了这种类型的方法,那么一旦我们生成JWS文件,就会出现问题。除了属于不同的命名空间之外,该类型还声明了一系列“price”元素。当前,正如WSDL定义的那样,有效输入看起来应该是这样:
<ns0:article
xmlns:ns0="http://www.oreilly.com/2004/articles3"
id="myId">
<ns0:title>title_1</ns0:title>
<ns0:prices>
<ns0:price>11</ns0:price>
<ns0:price>12</ns0:price>
</ns0:prices>
</ns0:article>
这里的思路是,我们想把title 元素和id 属性映射为Myarticle2Document 的title元素和id 属性(提供第一个参数给我们的业务方法),而且为了达到有趣的效果,我们准备计算"price"元素的总数,以便提供业务方法的第二个参数。
根据您创建JWS文件的方式,您可能想稳定地建立上面的架构,作为方法输入的架构。为此,选择JWS文件中的方法,然后在Property Editor中的"parameter-xml"下选择"schema-element"项。在"Parameter"选项卡下,选择"Choose",您现在能够导航到WebLogic Workshop所知的任何架构元素。从EstablishedAndImpl.xsd选择"article"。
现在,我们要做的是把这份文档映射为我们的Myarticle2Document类型,而且在此映射过程中,我们想把title元素复制给新的title元素,把id属性复制给一个id元素,然后计算price元素的总数,以便创建实现的第二个参数。
再次选择方法,然后在Property Editor中从"parameter-xml"选择"xquery"。您将会看到XQuery Mapper,它为把使用XQuery把一份XML文档映射为另一份XML文档提供了一种有效的图形化方式。
图 2. XQuery Mapper外观
左侧的Source架构描述了WSDL中声明的架构,而右侧的Target架构描述了参数。使用这个工具十分容易。我们只要把id属性拖拉到myid元素,然后再把title属性拖拉到title元素即可。我们还要把price元素(可能有很多)拖拉到值参数(记住这应该是一个double类型的值)。为了使这个总数等于price元素的值,我们打开"Advanced Option"面板,然后声明一个应该应用于转换的函数。在这个例子中,它是一个非常简单的XQuery片段 "xf:sum(/ns0:prices/ns0:price)",用于在适当节点上执行求和功能。
XQuery Mapper的结果是一个简单的XQuery片段,作为JWS文件中的方法注释。它看起来是这个样子:
:parameter-xml xquery::
declare namespace ns0 = "http://www.oreilly.com/2004/articles3"
declare namespace ns1 = "http://www.openuri.org/"
declare namespace ns2 = "http://www.oreilly.com/2004/articles2"
<ns1:businessMethod>
<ns2:myarticle2>
<ns2:myid>{ data(/) }</ns2:myid>
<ns2:title>{ data(/ns0:title) }</ns2:title>
</ns2:myarticle2>
<ns1:value>{ xf:sum(/ns0:prices/ns0:price) }</ns1:value>
</ns1:businessMethod>
::
这种简单性似乎与它提供给您的强大功能不合。只要拖拉几下鼠标按钮,您就能够使用XQuery的所有功能完全转换传入的XML。您还可以创建您可以在转换期间调用的XQuery代码片段,再次构建一个可重用转换的库,这类似于使用XML映射时所做的工作。参见“附加阅读”中列出的参考文章,以便获得关于XQuery更加详细的信息。
结束语
WebLogic Workshop 8.1在创建web服务的过程中提供大量帮助。本文已经考察了其支持的一小部分——即创建简单的web服务、WSDL和XMLBean——重点放在这些功能如何交互,以提供能够应付变化的健壮的web服务实现。用于提供这种健壮性的三个首要机制是XMLBean、 XML映射和XQuery映射。
XML映射提供一种直观的方式把传入的XML消息映射为Java参数。这允许公共接口中出现变化,而不会影响到实现。另一方面,XQuery映射为不同XML架构之间的映射提供了一种功能更加强大的转换语言。和XMLBean提供的极致抽象一起,这两种机制都做到了一点,即把修改web服务之后所需的编码工作量减到最低。
|