Web服务开发技术-实验指导书

更新时间:2024-06-13 09:49:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Web服务技术

实验指导

实验0、JAX-WS概述 .................................................................................................................... 3 实验1、使用JAXP进行XML的转换 ........................................................................................... 6 实验2、使用SAX进行XML的解析 ........................................................................................... 11 实验3、使用SOAPUI查看SOAP消息 ..................................................................................... 15 实验4、使用JAX-WS开发WEB服务服务器端 ........................................................................ 20 实验5、使用JAX-WS开发WEB服务客户端 ............................................................................ 31 实验6、WEB服务的打包与部署 ................................................................................................. 42

实验0、JAX-WS概述

实验目的:

1、JAX-WS服务调用处理过程

实验环境: 名称 下载地址 JDK1.6 http://www.java.net/download/jdk6/6u10/promoted/b32/binaries/jdk-6u10-rc2-bin-b32-windows-i586-p-12_sep_2008.exe SOAPUI3.6.1 Eclipse3.4 http://sourceforge.net/projects/soapui/files/soapui/3.6.1/soapUI-x32-3_6_1.exe/download http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/helios/SR2/eclipse-java-helios-SR2-win32.zip GLASSFhttps://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDSISH3.1 _Developer-Site/en_US/-/USD/ViewProductDetail-Start?ProductRef=ogs-3.1-oth-JPR@CDS-CDS_Developer 实验内容: JAX-WS(Java API for XML Web Service)是JAVASE和JAVAEE中用于构建WEB服务客户端和服务器端的接口,它提供了一套可以帮助开发人员快速实现SOA程序的工具。由于JAX-WS使用了W3C定义的WEB服务的标准:HTTP、SOAP、WSDL,因此具有较好的扩展性和灵活性。

在JAX-WS中,WEB服务的调用过程如下图所示:

参数 参数 SOAP 请求 SOAP 请求 参数 JAVA SEI JAVA 参数 参数 代理参数 SOAP 响应 返回 SOAP 响应 返回 对象JAVA 方法调用 SOAP消息交换 JAVA 方法调用 1、 在客户端创建一个Web服务端点的实例,该服务端点实现一个Java接口,用JWS

的术语来说,这种接口叫做服务端点接口(SEI)。通常情况下,SEI实例是用Java动态代理机制实现的。

2、 当调用SEI实例的方法时,JAX-WS会把传递给它的参数序列化成与目标服务的

WSDL指定的XML模式一致的XML片段,并最终将这些片段封装到SOAP请求消息中。

3、 将消息传递给网络传输,向目标Web服务交付消息。

4、 服务器端在接收到SOAP消息后,会首先决定调用哪个WSDL操作。

5、 在确定了目标WSDL操作之后,会决定调用哪个JAVA类/方法。这个过程叫做分

发(dispatching)

6、 将SOAP消息反序列化为Java对象并传递给目标Java类和方法,从而得到目标

方法返回的Java对象。

7、 将返回的Java对象再将反序列化为与目标WSDL操作指定的返回消息一致的XML

片段。将这些片段包装成SOAP消息发送回客户端。

8、 客户端将返回的XML片段反序列化为Java对象并返回给调用者,从而完成对WEB

服务的调用过程。

从上面的过程可以看出,JAX-WS可以分为三个部分:

? 调用子系统:处理WEB服务客户端与服务器端的调用过程。 ? 序列化子系统:将Java对象序列化为XML元素以及将XML元素反序列化了Java

对象。

? 部署子系统:将目标Java对象部署为WEB服务从而使其可以通过SOAP消息调

用。

在整个调用过程中,很重要的一步就是决定调用哪个Java类和方法的分发操作。要了解分发过程,首先就要对WSDL的样式有所了解,这可以参照附件中的“WSDL样式.mht”的内容。现在假设WSDL采用的是document/literal/wrapped样式,有一个获取一段时间内订单的方法如下:

public class Ordermanager{ }

public Order[] getOrders(Date start,Date end); ...

将其发布为WEB服务后,WSDL部分内容如下所示:

...

50.

54. 56.

57.

60. transport=\61.

62.

63. 65.

66.

68. 71. 72.

73.

location=\76. 77. ...

假设服务器端收到如下的SOAP消息:

1. Post /getorders/getordersdates HTTP1.1 2. Host: localhost:8080 3. ...

4. 5.

6.

xmlns:getord=‖http://www.example.com/oms/getorders‖> 7. 2011-02-17 8.

9. 2011-03-17 10. 11. 12. 13.

则整个分发过程如下所示:

1、 根据SOAP消息底层协中的地址去识别相对应的soap:address(wsdl:75)元素。

个例子中的两个地址并不完全相同(主机名分别为localhost与127.0.0.1),但是对等的,因此需要分发程序进行一些处理。

2、 在找到相对应的soap:address元素之后,就可以根据其父元素的wsdl:port元素

的binding属性找到相应的wsdl:binding元素,然后根据wsdl:binding元素的type属性找到相应的wsdl:portType元素。wsdl:portType元素是一组wsdl:operation元素的集合,这与Java类中有一组方法是相对应的。因此wsdl:portType对应Java类,wsdl:operation对应Java类中的方法。这样就可以根据类中的标注找到相应的类。

3、 调用子系统从

SOAP中找到包装器元素getOrdersDates。在

document/literal/wrapped样式的WSDL中,所有输入参数是被包装在一个与要调用的wsdl:operation同名的元素中的,因此根据这个包装器元素就可以确定相应的wsdl:operation,然后就可以根据类中的标注找到相应的方法,并将输入参数按照标注指定的对应关系反序列化为类的调用参数,从而完成整个分发过程。

实验附件:

实验1、使用JAXP进行XML的转换

实验环境:

Eclipse3.4

1、 使用XSLT进行XML格式之间的转换

2、 使用DOM接口进行XML格式之间的转换。

实验内容:

实验步骤:

在SOA系统的开发过程中,经常会遇到需要在不同格式之间进行数据转换的情况。本

次实验使用JAXP(Java API for XML Processing)来进行不同XML格式之间数据的转换。

假设现在有两个WEB服务:

? 订单管理服务:该服务可以接收并处理新的订单。

? 客户历史记录管理服务:该服务可以接收对客户历史记录的更新。

而我们需要一个新的系统将上面两个WEB服务链接起来。在这个新的系统中,首先是订单管理服务接收新的订单,再把它们转换成客户历史记录,最后再将这些记录发送给客户历史记录管理服务。在这过程中,我们要把订单格式转换为客户历史记录格式。订单与客户历史记录两种格式的XML模式文件夹分别为orders.xsd与custhistentries.xsd。两种数据格式及它们之间的对应关系如下图所示。

客户历史记录 客户编号 订单信息 CUS001 订单编号:ORDER001 定购单号:PO-001 商品编号:GOODS001 商品编号:GOODS002 其它信息:紧急订单 订单 订单编号 ORDER001 标头 销售组织:.NE 采购日期:2011-01-01 客户编号:CUS001 支付方式:PO 定购单号:PO-001 保证交付日期:2011-01-07 订单条目编号:GOODS001 存储位置:NE02 目标数量:50 度量单位:KG 每度量单位价格:7.95 描述:这是商品1 订单条目订单条目编号:GOODS002 目标数量:5 度量单位:个 每度量单位价格:20 描述:这是商品2 其它信息紧急订单

1、使用XSLT进行XML格式之间的转换

XSLT(eXtensible Stylesheet language Transformations)是一种基于XML的一种强大且有效的数据转换引掣,可以很方便的解决数据转换的问题。

XSLT是声明式的语言,用于定义从原文档到目标文档的转换。XLST转换文件夹由一套模板规则组成(由xsl:template元素的实例代表),它们是根节点xsl:stylesheet元素的子元素。stylesheet元素中的各个子元素定义了如何根据原始文档来创建目标文档的转换结构。XLST使用XPath语言来标识源文件中的数据块,在模板规则的帮助下,XPath表达式可以决定将相应的数据块放在目标文档的什么位置。

程序开发步骤如下: 第一步:

打开Eclipse,新建Java项目XMLTransform。新建Java包schema,并将orders.xsd与cushistentries.xsd放在此包下,新建包xslt用于存放xslt文件,新建包code存放java代码。在项目根目录下新建文件夹xml并将用于转换的数据order.xml放在此目录下。

第二步:

在xslt包中新建文件order_to_history.xslt作为从订单记录到客户历史记录转换的样式表。 该文件夹说明如下:

首先,文件中最开始的几行

1.

3. xmlns:xsl=\4. xmlns:oms=\

5.

定义了所用到的命名空间以及输出文档的格式,其中oms 前缀代表订单的命名空间

http://www.example.com/oms。xsl:output元素用于控制样式输出的格式表,它的属性method=‖xml‖表示输出的结果应该是XML。 这个XSLT文件接下来的一部分

11.

12. 17. 18.

19.

20.

22. 24.

25.

27. select=\28.

29. select=\30. 32. 33.

定义了oms:orders的转换规则。在这段代码的开始处,可以看到一个模板元素匹配源文档中的‖oms:Orders‖元素。在该模板元素的内部又定义了一个CustomerHistoryEntries元素。模板内部的内容定义了目标文档的输出,所以这个模板提供了如何将一个oms:Orders元素转换为一个css:CustomerHistoryEntries的规则。在这个模板元素内,有一个指令元素。这个特殊指令告诉XSLT处理器为oms:Orders节点的子节点应用于样式表中的其它模板,并将转换的结果插入到目标文档中。这样一来就构造好了目标文档中的css:CustomerHistoryEntries元素。

从第19行开始定义了另一个用于匹配oms:Order元素的模板。所以,如果oms:Orders的所有子元素都是oms:Order元素的实例,这些子元素就会由这个模板来处理并将处理结果插入到CustomerHistoryEntries标签中。在该模板元素中又包含了另一个xsl:apply-templates指令,不过这个指令有一个select属性,其中的XPath表达式./oms:OrderHeader/ oms:PURCH_ORD_NO对这个模板所匹配的子元素进行了限制。该XPath表达式说明了该该模板只适用于oms:OrderHeader的子元素oms:CUST_NO。这样就可以把oms:CUST_NO元素转换为css:CustomerNumber元素,并将其插到目标文档的正确位置。

该文件夹的剩余部分使用了另外一些模板元素定义了对源文档中的其它元素的处理,原理与此处相同,所以不再赘述。 第三步:

在Java代码中处理XSLT,可以使用JAXP(Java API of XML Processing)。JAXP中的javax.xml.transform.Transformer类会根据样式表文件中指定的规则把源文档中转换成目标文档。JAXP为所有的Java XML处理都提供了支持。 在code包下新建类XSLTXform,其代码主体如下:

1.public class XSLTXform {

2. public static void main(String[] args) throws Exception { 3. String ordersFile = \

4. String outputFile = \5.

6. Source src = new StreamSource(new File(ordersFile)); 7. Result res = new StreamResult(new File(outputFile)); 8.

9. Transformer transformer =

10. TransformerFactory.newInstance().newTransformer 11.

(new

StreamSource(XSLTXform.class.getResourceAsStream(\ory.xslt\

12. transformer.setOutputProperty(OutputKeys.INDENT, \

13.

14. transformer.transform(src, res); 15. 16. } 17.}

首先新建了一个用于封装输入的xml文档的Source对象与一个用于封装输出xml文档的Result对象,然后新建一个封装了XSLT转换文件夹的Transformer对象。最后调用Transformer的transform方法来完成实际的转换过程。 第四步:

运行程序,可以在xml文件夹下看到输出的custhistentry.xml文件。

2、使用DOM接口进行XML格式之间的转换。

另一方面,我们也可以直接使用DOM接口来完成XML的转换。DOM是一种解析和修改XML文档的一种标准接口,它将XML文档转换为内存中的一棵由元素、属性和文本构成的树,通过对该节点树进行处理来完成对XML文档的处理。本例中演示如何使用DOM接口来完成与上例等价的转换,从而同时演示如何使用DOM接口对XML文档进行读和写。

第一步:

在XMLTransform项目中创建DomXform类。该类是使用DOM接口进行转换的主类。 第二步:

27. String ordersFile = \

28. String outputFile = \29.

30. Source src = new StreamSource(new File(ordersFile)); 31. Result res = new StreamResult(new File(outputFile)); 32.

33. DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); 34. DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); 35.

36. Document srcDoc = docBuilder.newDocument(); 37. Document resDoc = docBuilder.newDocument(); 38.

39. Transformer xformer = TransformerFactory.newInstance().newTransformer(); 40. xformer.transform(src, new DOMResult(srcDoc));

要想使用DOM接口来完成对XML文档的处理,首先要创建一个DOM解析器工厂对象并使用该对象创建一个DOM解析器。然后创建分别与源XML文档和目标XML文档相对应的Document对象,并使用Transformer对象将源XML文档读入与之相对应的Document中,从而使其可以使用DOM接口进行处理。

第三步:

根据目标文档的格式,在目标文档中创建新的节点,所需要的数据可以从源文档中查询。比如对于目标文档中的客户编号节点,可以通过以下方式添加:

45. Element custNumEle = resDoc.createElementNS(RES_DEFAULT_NS, 46. 48.

\

.getElementsByTagNameNS(SRC_DEFAULT_NS, \

47. String custNumber = srcDoc

49. 51.

.getFirstChild().getNodeValue();

50. custNumEle.appendChild(resDoc.createTextNode(custNumber)); 52. custHistEntryEle.appendChild(custNumEle);

在以上代码中,首先使用Document#createElementNS方法创建了一个局部名称为CustomerNumber的元素,它所在的命名空间是由String类型常量RES_DEFAULT_NS指定的。然后,使用Document#getElementsByTagNameNS方法在源文档中查找局部名称为CUST_NO,命名空间由String常量SRC_DEFAULT_NS指定的元素,它在源文档中可以保证是惟一出现的。查找到的元素节点的第一个子节点是一个文本节点,文本节点的内容就是客户的编号。因此,可以读出该编号,在目标文档中使用Document#createTextNode创建一个包含客户编号的文档结点,并使用Element#appendChild方法将该文档节点添加为的子节点:CustomerNumber的子节点。最后,再调用Element#appendChild方法将:CustomerNumber添加为:CustomeHistoryEntry的子节点。

第四步:

对于目标文档中的商品编号列表,可以使用与第三步相似的方法,找到源文档中的所有的商品编号节点,对于每一个节点,在目标文档中添加一个相应的节点。代码如下:

73. NodeList itemNumEles = srcDoc.getElementsByTagNameNS(SRC_DEFAULT_NS, 74. 76. 77. 78. 79. 80. 81. 82. }

第五步:

\

75. for (int i = 0; i < itemNumEles.getLength(); ++i) {

String currentNumber = itemNumEles.item(i).getFirstChild()

.getNodeValue(); \

Element itemNumEle = resDoc.createElementNS(RES_DEFAULT_NS, itemNumEle.appendChild(resDoc.createTextNode(currentNumber)); orderLookupInfoEle.appendChild(itemNumEle);

运行程序,可以看到得到了与使用XLST方法相同的目标文档。

实验附件:

实验2、使用SAX进行XML的解析

实验环境:

Eclipse3.4

1、 使用SAX进行XML的解析

实验内容: 实验步骤:

在WEB服务开发过程中,我们经常需要将XML文件解析成相对应的Java对象。要完成XML解析的工作,需要使用相应的XML解析器。在实验1的第2个实验“使用DOM接口进行XML格式之间的转换”中,我们已经看到了如何使用DOM解析器对XML文件进行解

析和修改,本实验将使用另外一种解析器SAX来完成XML文件的解析。与DOM在内存中建立一棵节点树并通过对树的操作来完成对XML文件的处理相比,SAX基于事件流的模型。解析器从顺序扫描整个XML文件,当遇到特定事件比如遇到元素起始标签、遇到元素闭合标签、遇到文件节点等,就会调用预先注册的事件处理程序来完成相应操作,从而完成整个文件的解析。与DOM不同的是,SAX只能进行文档的解析,而不能进行文档的修改。

SAX与DOM的比较如下: 结构 处理过程 DOM 基于树的 SAX 基于事件 首先读入整个文档,然后在内存程序提前注册事件处理程序。解析器扫描中建立节点树。程序通过访问节文档,当遇到事件时触发相应的事件处理点树来完成XML的处理过程。 程序。 较复杂,但比较灵活。 占用处理器和内存较多 非顺序访问或要进行修改 较易使用,但功能有限 只能进行解析。 占用内存少,处理速度快。 扫描大的文档。 易用性 资源占用 使用场景 可进行的操作 创建、解析、修改、删除 假设对于实验1的订单管理程序,程序在收到订单请求后,需要将XML文档解析成相应的Java对象,以便程序的进一步处理。使用SAX解析过程如下:

第一步:

在Eclipse中新建项目XmlParser。创建包schema并将orders.xsd拷贝到该包下。

第二步:

首先,创建与orders.xsd中定义的类型相对应的Java类并放置于bean包下。为了完成解析程序,共创建了四个Java类,如下所示:

? Order:与 orders.xsd中的oms:OrderType类型相对应。

? OrderHeader:orders.xsd中的oms:BUSOBJ_HEADER类型相对应。 ? OrderItem:与orders.xsd中的oms:BUSOBJ_ITEM类型相对应。 ? Card:与orders.xsd中的oms:BUSOBJ_CCARD类型相对应。

另外创建了一个表示付款方式的枚举类型PaymentMethod,因为oms:BUSOBJ_HE ADER的PYMT_METH子元素只能取PO和CC两个值。

第三步:

在parser包下创建SAX事件处理类OrderHandler。因为SAX解析器是通过在扫描文档遇到相应的事件时触发提前注册的事件处理程序来完成解析的,因此要求程序员创建自己的事件处理程序。OrderHandler继承自org.xml.sax.helpers. DefaultHandler类并重写了其中的三个方法:

? startElement:当遇到一个元素开始标签的时候,会触发该处理函数。 ? endELement:当遇到一个元素的结束标签时,会触发该处理函数。 ? characters:当SAX器解析出一个文本节点时,会触发该处理函数。 Orderhandler中定义了三个实例变量: ? order:解析元素及其子元素时,代表当前解析的XML元素对应的Order

对象。

? currentItem:解析元素及其子元素时,代表当前解析的XML元素对应

的OrderItem对象。

? currentElementName:当解析出文本节点时,代表当前文本节点所在的父元素

的名称。

在三个重写的处理函数中,基本思想是根据遇到的元素的名称,分别进行不同的处理。具体处理过程如下:

? startElement:如果是元素,就实例化order变量。如果是

元素,就实例化order变量的orderHeader属性。如果是元素,就实例化item变量。除此之外,使用currentElementName变量记录当前元素的名称供characters方法使用。

? endELement: 如果是元素,就说明当前元素已经解析完毕,

item变量就是与解析完成的元素相对的对象,因此要将该对象添加到order变量的items,即OrderItem的列表中。除此之外,还要清除currentElementName变量,说明当前元素已经解析完成。

? characters:根据currentElementName的值设定order或item对象的属

性。 第四步:

创建OrderParser类作为order.xml的解析器。在解析过程中,为了保证文档是正确的,应当在解析之前先进行验证。所以在OrderParser中创建validate方法,实现如下:

18. SchemaFactory schemaFactory = SchemaFactory 19. 20.

21. Source schemaSource = new StreamSource(this.getClass() 22. 23.

24. try { 25. 26. 27. 28. 30. 31. 32. }

Schema schema = schemaFactory.newSchema(schemaSource); Validator validator = schema.newValidator(); validator.validate(new StreamSource(f)); return true;

e.printStackTrace(); return false;

.getResourceAsStream(\

.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

29. } catch (Exception e) {

该方法中首先创建一个模式工厂类,将通过其工厂方法newSchema创建一个Schema对象。Schema可以看作验证器的工厂,因此通过该对象可以创建一个验证器对象。然后调用验证器对象的validate方法,该方法会使用与Schema对象相对应的schema文件去验证目标XML文档,如果有错的话,就会抛出异常。 第五步:

在OrderParser类中创建parse方法来完成XML文档的解析。该方法实现如下:

37. SAXParserFactory spf = SAXParserFactory.newInstance(); 38. spf.setNamespaceAware(true); 39. SAXParser sp; 40. try { 41. 42.

sp = spf.newSAXParser();

OrderHandler oh = new OrderHandler();

43. 44. 46. 47. 48. }

sp.parse(f, oh); return oh.getOrder(); e.printStackTrace(); return null;

45. } catch (Exception e) {

首先创建一个SAX解析器工厂,并设定该工厂产生的解析器会提供对命名空间的支持。否则的话,OrderHandler对象的startElement和endElement的入参中localName和namespce会为空。然后创建解析器对象并注册给其OrderHandler事件处理对象,最后调用parse方法来完成解析过程。

第五步:

创建Main.java作为主程序。主程序中首先读入order.xml,然后分别调用OrderParser的validate和parse方法来完成文档的验证和解析。运行程序,可以看到程序解析出了正确的信息:

1. 订单信息

2. 订单号:ORDER001 3. 订单头信息 4. 销售组织:NE

5. 购买日期:Sat Jan 01 00:01:00 CST 2011 6. 客户号:CUS001 7. 购买方式:PO 8. 购买订单号:PO-001 9. 到货日期:2011-01-07 10. 11. 订单条目 12. 条目号:001 13. 存储位置:NE02 14. 数目:50.0 15. 数量单位:KG 16. 单价:7.95 17. 简介:这是商品1 18. 19. 订单条目 20. 条目号:002 21. 存储位置:null 22. 数目:5.0 23. 数量单位:个 24. 单价:20.0 25. 简介:这是商品2 26.

27. 订单信息:紧急订单

实验附件:

实验3、使用SOAPUI查看SOAP消息

实验环境:

SOAPUI 3.6.1

实验步骤:

SOAPUI是一个通用的WEB服务客户端工具,使用SOAPUI可以方便的查看WEB服务的WSDL文件以及客户端与服务器端通信所产生的SOAP消息,可以用于对所开发的WEB服务及客户端进行调试。

本次实验和实验2、3均基于以下订单处理场景:

1、 假设客户想要从网上订购一些商品,他会发送一个―客户订单‖,其中包括客户编号、可

选的订购单号、可选的信用卡信息,以及一个要购买商品的列表。

2、 订单管理系统接收到―客户订单‖后,会对订单进行处理,确定要订购的商品以及需要的

付款方式,与库存管理系统进行交互,根据当前库存决定可能的商品发货日期。

3、 最后,订单管理程序为客户发送一个响应消息——订单确认信息,其中包括了订购商品的

付款信息以及预计的发货日期,作为给用户的收据。 一个可能的例子如图…所示:

为了突出SOA程序设计开发中的问题,在对场景设计编码过程中,使用一段简单的 码代替了第2步中对―客户订单‖的真实处理过程,从而将更多的注意力放在了客户端与服务端的交互上。

本实验中使用的是SOAPUI 3.6.1免费版。与正式版相比,免费版缺少了一些功能,但并不会对我们的使用造成太大影响。 第一步:

从实验2的附件中找到WSServer项目并将该项目导入到Eclipse中。运行server.main.Main,该类会把服务发布在http://localhost:8088/RequestOrderService上,其WSDL可以通过http://localhost:8088/RequestOrderService?WSDL访问。 第二步:

打开SOAPUI。如果打开程序的时候,弹出如下错误窗口的话:

那么打开/bin/ soapUI-3.6.1.vmoptions文件并添加一行,内容如下:

-Dsoapui.jxbrowser.disable=true

重新运行程序即可。

依次选择File->new soapUI Project,在弹出的窗口中输入项目名称以及WSDL的地址,点击确定,如下图所示:

这样就在SOAPUI中新建了一个项目。SOAPUI会识别出WSDL文件中所有的绑定和操作,在左侧项目树状视图上双击,可以在打开的对话框中看到服务和绑定的相关属性。该服务的属性对话框如下图所示:

第二步:

点击确定之后,可以看到SOAPUI已经识别出WSDL中的服务与操作。双击Request1,可以看出SOAPUI根据WSDL中wsdl:types定义已经准备了请求SOAP消息的模板。替换其中的―?‖占位符,将某些不需要的Optional的节点删掉,从而创建一个请求消息。

第三步:

点击左上角的发送按钮,可以在右边看到服务器端返回的SOAP响应消息:

第三步:

SOAPUI可以用来对WEB服务进行测试,从而帮助WEB服务开发人员对服务进行调试。首先,SOAPUI可以通过添加断言(Assertion)的方式对服务进行功能性测试。首先,给请求添加一个测试集:

在弹出的对话框中依次输入测试包和测试用例的名称,点击确定,可以看到SOAPUI打开了测试用例编辑窗口,并且在左侧项目树状视图中可以看到相应测试包的信息。

现在,就可以给请求添加断言。SOAPUI支持多种断言,包括Schema样式、简单字符串包含、XPATH匹配、非SOAP错误、脚本断言等多种断言类型,可以进行各种不同的断言操作。

可以点击添加断言按钮给请求添加一个断言:

由于在返回的消息中会包括信息卡的持卡人姓名,因此可以在弹出的断言类型选择对话框中选择包含断言,包含的内容填写要发送的请求消息的持卡人姓名:

重新发送请求,可以看到断言成立:

第四步:

还可以使用SOAPUI进行负载测试,从而可以对服务的响应时间等性能因素进行测试。可以使用如果下方法添加测试集:

在弹出的页面填写测试用例的名称,并在弹出的测试用例编辑对话框中填写相应参数。

以下是使用5个线程运行20秒的测试结果,结果显示了响应时间、发送数据量等信息。

还可以用如下方法添加对性能特征的断言:

这些断言的使用方法与功能测试的断言的使用方法相似,此处不再重复。

以上是对SOAPUI的简单介绍。SOAPUI是一个功能很强大的软件,如果想要进一步的了解的话,可以参照SOAPUI的在线帮助:

http://www.soapui.org/Getting-Started/installing-soapui.html

实验附件:

实验4、使用JAX-WS开发WEB服务服务器端

实验环境:

1、 JDK1.6

2、 Eclipse3.4

实验内容:

1、 使用服务器端点接口(SEI)从WSDL开始开发 2、 不使用JAXB来实现服务提供者与XML处理 3、 使用标注从现有Java程序开始开发

实验步骤:

一、使用服务器端点接口(SEI)从WSDL开始开发

开发一个WEB服务的服务器端的一种情况是从一个已经存在的WSDL开始开发,要实现符合此WSDL契约的WEB服务,最直接的方式是使用服务端点接口(Service Endpoint Interface,SEI)。SEI是根据JAX-WS WSDL/JAVA映射生成的Java接口,在服务器端实现该接口即可提供符合指定契约的WEB服务。

假设我们现在已经有了实验1中提到的订单处理系统的WSDL文件,如本实验附件中的RequestOrder.wsdl所示。在该WSDL文件的wsdl:types元素定义中有如下元素: 9.

12.

15.

这两个元素从外部文件中导入了两个XML schema文件。在实际的开发过程中,经常会出来某些数据已经存在标准的格式(如订单),使用这些标准的数据格式可以使系统更易理解并提高系统的可扩展性。

要生成SEI,可以使用Java SE 6.0中的wsimport工具。该程序位于/bin目录中。实验步骤如下: 第一步:

新建文件夹wsdl并将RequestOrder.wsdl、faults.xsd与orders.xsd放在该目录下。 第二步:

打开命令窗口(cmd)并跳转到wsdl目录下,运行wsimport命令

/bin/wsimport -d ./bin -s ./src -p server.sei RequestOrder.wsdl

其中-d选项指明创建的class文件目录,-s指明源代码文件目录,-p指明所创建的类所在的包,最后的输入参数指明的是要创建的Web服务的WSDL地址。

第三步:

打开目录wsdl/src/server/sei,可以看到JAX-WS共生成了13个文件。这些文件包括以下几个部分:

? Service Endpoint Interface (SEI)类:RequestOrderPort.java ? Service类:RequestOrderService.java ? 从wsdl:fault映射类:InputFault.java ? 从XML schema映射而来的Java类:其余的类

其中上面第四点提到的从XML schema到Java类的映射是JAX-WX中很重要JAXB2.0(Java XML Binding)标准。由于在SOA程序中,WEB服务调用一般是通过SOAP请求来实现的。由于SOAP是一种基于XML的协议,因此在JAX-WS,客户端要想调用WEB服务,就要首先将调用WEB服务所用到的参数从Java程序中的对象转换成XML片段,而服务器端为了在Java程序中处理这些参数,就要将收到的XML片段转换成Java对象。在JAXB2.0中,这两个过程分别叫做封送(marshalling)和拆收(unmarshalling)。同样,服务器端要想将处理的结果返回给客户端,也要经历Java对象—>XML片段—>Java对象的过程。JAXB2.0提供了一种方便快速的将JAVA类与XML Schema类型绑定的方法。它通过标注将Java类与对应的XML Schema绑定在一起。

WSDL文件与生成的Java类之间的具体珠对应关系如下:

1、 wsdl:portType映射为了服务端点接口(SEI)——RequestOrderPort。 2、 名为requestOrder的wsdl:operation被映射到了具体有相同名称的SEI方

法。

3、 wsdl:input消息决定了requestOrder方法的参数。可以看到,这个消息

(req:request)只有一个单独的wsdl:part,其中的包装器req:requestOrder是在wsdl:types区段中定义的。该包装器元素的子元素(即CUST_NO、PURCH_ORDER_NO、ccard、item)定义了方法的参数。这里要注意的是,由于item元素的maxOccurs > 1,所以它将被映射为列表(List)类型。

4、 与上面相似,wsdl:output消息决定响应类型为OrderType

5、 按照JAX-WS映射的定义,每一个wsdl:fault都映射为一个可抛出的异常。所

以,这里单一的wsdl:fault映射为requestOrder方法中声明的―throws InputFault‖语句

6、 wsdl:fault中引用的wsdl:message被映射为继承自

java.lang.Exception的一个子类。此处名为inputFault的wsdl:message映射为InputFault类,该类是fault bean的一个包装器异常类。 7、 wsdl:fault元素引用的只带有一个单独part(wsdl:part)的wsdl:message

元素。该part元素引用的全局元素声明被映射为一个JavaBean,JAX-WS将该JavaBean称为fault bean。在这个示例中,fault:inputMessageValidationFault元素映射到名为inputMessageValidationFault的fault bean。这样的fault bean是包装器类封装的属性。

8、 wsdl:types中的元素被映射到一些Java Bean类,具体映射过程后面将详细说

明。

SEI的映射中使用了标注来确定如何将方法调用封送为SOAP请求消息,以及如何将

SOAP响应消息拆收为方法的返回值类型的实例。本示例中SEI类如下所示:

20.@WebService(name = \

\21.@XmlSeeAlso({

22. ObjectFactory.class 23.})

24.public interface RequestOrderPort {

……(省略某些注释) 37.@WebMethod

38.@WebResult(name = \

\

39.@RequestWrapper(localName=\

\\

40.@ResponseWrapper(localName = \

\\

41.public OrderType requestOrder(

42. @WebParam(name = \

\

43. String custNO, 44.

@WebParam(name = \\

45. String purchORDNO,

46. @WebParam(name = \

\

47. BUSOBJCCARD ccard,

48. @WebParam(name = \targetNamespace = \49. List item) 50. throws InputFault 51. ; 52. 53.}

其中所用到的标注描述如下: 标注 @WebService @WebMethod @WebResult 用途 表示某个Java类要实现一个Web服务,或者表示某个Java接口定义了一个Web服务接口。 表示相关的方法是从wsdl:operation映射而来的。 表示将SEI方法的返回值关联到某个wsdl:part或wsdl:part元素的定义。文档/字面值包装方式的Web服务中,@WebResult标注将SEI方法的返回值映射为响应包装器元素的对应子元素。 用于标识请求包装器bean,这是一个JAXB生成的类,它将映射到请求消息的包装器元素。 @RequestWrapper @ResponseWrapper 用于标识响应包装器bean,这是一个JAXB生成的类,它将映射到响应消息的包装器元素。 @WebParam 将SEI方法的参数与特定的wsdl:part(rpc方式)或者特定的wsdl:part元素定义的包装器子元素相关联(document方式)。在任何一种情况下,@WebParam标注都是将参数的Java表示与WSDL/SOAP的对应表示关联起来。 wsdl:types中的元素与Java Bean类映射关系描述如下:

1、 生成的package-info.java定义了java包与目标命名空间的对应的关系,其中的

@javax.xml.bind.annotation.XmlSchema(namespace= \elementFormDefault=

javax.xml.bind.annotation.XmlNsForm.QUALIFIED) 其中的namespace属性将这个包映射命名空间http://www.example.com/oms,它通过elementFormDefault元素指定这个包的模式是限定的,这意味着这个包中所含的Java类的各属性在映射到模式单元定义时,元素应该具有命名空间的限定。 2、 输入元素中,CUST_NO和PURCH_ORD_NO直接映射到java.lang.String。 3、 oms:BUSOBJ_CCARD映射到了BUSOBJCCARD类 4、 oms:BUSOBJ_ITEM映射到了BUSOBJITEM类。

5、 oms:Order映射到了OrderType类,由于在订单中订单条目可以出现多次,因此其

中的orderItems属性是一个包装类OrderItems的实例,OrderItems类中封装了BUSOBJITEM类的一个列表。

6、 req:requestOrder元素和req:requestOrderResponse包装器元素分别映射为

RequestOrder类和RequestOrderResponse类。

现在以RequestOrder类来说明JAXB标注的含义。首先,对于类的标注如下:

47. @XmlAccessorType(XmlAccessType.FIELD) 48. @XmlType(name = \49. \50. \51. \52. \53. })

54. @XmlRootElement(name = \\55. public class RequestOrder {

56. @XmlElement(name = \namespace = \

required = true)

57. protected String custno;

@XmlAccessorType指定该类的访问类型是字段(不需要getter/setter方法)还是属性(需要getter/setter)方法。该类中指定访问类型是字段,即每个非静态的、非transient的、未使用@XmlTransient标注的字段都会自动绑定到XML。

@XmlType标注用于将一或枚举类型映射到XML schema类型。在这个例子中,由于@XmlType.name为‖‖,因此会映射到XML中的一个匿名类型。@XmlType.propOrder定义的顺序对应于XML schema中的定义,指定子元素出现的顺序。

@XmlRootElement标注用于将一个类映射到一个全局元素的定义。全局元素的名称

由name属性决定,命名空间由namespace属性决定。

@XmlElement标注用于将一个类的属性或者字段映射到元素的子元素。其中name属性指定元素的名称,namespace属性指定子元素定义所属的命名空间,require属性指定该元素是否在父元素的子元素序列中必须出现。

另外,打开InputMessageValidationFaultType类,可以看到其定义如下:

27. @XmlAccessorType(XmlAccessType.FIELD)

28. @XmlType(name = \\

29. public class InputMessageValidationFaultType { 30.

31. @XmlAttribute

32. protected String msg;

该类使用@XmlAttribute标注将msg字段映射到父元素的一个属性而不是子元素。 以上只是对JAXB的简单介绍,如果想要详细的了解JAXB的话,可以参考JAXB 2.0的规范[JSR-222](在实验附件中)。

第四步:

打开Eclipse,新建项目WSServer,将生成的包server.sei拷贝到源文件夹。

第五步:

新建包server.imp并为RequestOrderPort SEI接口创建实现类RequestOrderImp.java。该类实现了业务方法

requestOrder(String,String,BUSOBJCCARD,List)。该方法使用JAXB生成的类来实现业务逻辑,代码如下:

45. // generate a psuedo-unique 10 digit order ID

46. String orderId = Long.toString((new Date()).getTime()); 47. orderId = orderId.substring(orderId.length() - 10); 48. OrderType response = new OrderType(); 49. response.setOrderKey(orderId); 50. // create OrderHeader

51. BUSOBJHEADER hdr = new BUSOBJHEADER(); 52. response.setOrderHeader(hdr); 53. hdr.setCUSTNO(custNum);

54. GregorianCalendar cal = new GregorianCalendar(); 55. hdr.setPURCHDATE(dateAsString(cal)); 56. cal.add(Calendar.DAY_OF_MONTH, 14); 57. hdr.setWARDELDATE(dateAsString(cal)); 58. if (poNum != null && poNum.length() > 0) { 59. 60. 62. 63. 64. 65. 66.

hdr.setPYMTMETH(\hdr.setPURCHORDNO(poNum); hdr.setPYMTMETH(\// OrderType.OrderCcard

OrderType.OrderCcard ordCcard = new OrderType.OrderCcard(); ordCcard.setCcard(ccard);

response.setOrderCcard(ordCcard);

61. } else {

第六步:

在业务逻辑的处理过程之中,经常会遇到坏的数据,这时就要向客户端返回一个SOAP报错消息,向客户端表明失败的原因。服务器端返回SOAP报错消息可以分为两种情况: 1、 已经映射到wsdl:fault消息的Java异常。在RequestOrder服务中定义了输入验

证错误InputFault:

63.

67.

2、 未被映射到wsdl:fault的异常。对于某些因无法预料的运行时错误而导致的异常,

则很可能没有相应的wsdl:fault消息,只能作为通用的的SOAP采取多种报错返回。 对于第一种情况,JAX-WS从WSDL到JAVA的映射可以将wsdl:fault消息绑定到java.lang.Exception的子类。当从WSDL生成SEI时,也会生成这些可被映射的。在运行时如果想返回这种SOAP错误,一种选择是直接抛出一个被映射的异常,JAX-WS运行时引擎可以自动的将其转换到相应的wsdl:fault消息,包装在SOAP消息体中并返回。例如可以这样处理输入参数异常:

31. InputMessageValidationFaultType ft = new InputMessageValidationFaultType(); 32. if (custNum == null) {

33. ft.setMsg(\34. 35. }

36. if (poNum == null && ccard == null) { 37. 38. 39. }

40. if (itemList == null || itemList.isEmpty()) { 41. 42. 43. }

ft.setMsg(\

throw new InputFault(\ft.setMsg(\

throw new InputFault(\throw new InputFault(\

对于第二种情况,可以通过构造类javax.xml.ws.soap.SOAPFaultException类的实例来实现。当在服务实现Bean中内部的抛出一个SOAPFaultException时,JAX-WS运行时引掣就将它转换成一个SOAP报错消息,并返回给客户端。例如,当信用卡过期的时候,可以用这种方法向客户端报错:

68. SOAPFactory fac = SOAPFactory.newInstance(); 69. SOAPFault sf = fac 70. .createFault(

71. \72. new QName(

73. \74. \

75. Detail d = sf.addDetail(); 76. DetailEntry de = d

77. addDetailEntry(new QName(\

78. de.setValue(\79. throw new SOAPFaultException(sf);

第七步:

在Java SE6.0中,一个很方便的发布WEB服务的方法是使用javax.xml.ws. Endpoint类。使用该类发布一个WEB服务非常简单。只要调用Endpoint#publish方法并提供一个服务端点的URL地址即可。

在WSServer项目中新建一个server.wsdl包并将RequestOrder.wsdl放入其中。新建一个server.main包并创建Main类。其代码如下所示:

16. public class Main {

17. public static void main(String[] args) { 18. 19. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 35. 36. } 37. }

ArrayList metadata = new ArrayList(); Source wsdlSource = new StreamSource(wsdlStream); String wsdlId = wsdlURL.toExternalForm(); wsdlSource.setSystemId(wsdlId);

metadata.add(wsdlSource); endpoint.setMetadata(metadata);

System.out.println(\}

Endpoint endpoint = Endpoint.create(new RequestOrderImp()); InputStream wsdlStream = Main.class.getClassLoader() URL wsdlURL = Main.class.getClassLoader().getResource(

\

throw new RuntimeException(\

if (wsdlStream == null) {

20. .getResourceAsStream(\

34. endpoint.publish(\

这样就把这个服务发布在http://localhost:8088/RequestOrderService上。在发布服务之间,首先设置了Endpoint对象的元数据。Endpoint对象的元数据可以包括一组XML模式与WSDL。设置了元数据之后,JAX-WS根据标注创建WSDL文件时会尽可能使用提供的元数据而不是自己重新生成。但是,即使设置了元数据,也不能保证程序会精确的复制它。

第八步:

运行WSServer项目,打开浏览器,输入地址http://localhost:8088/ RequestOrderService?wsdl并打开,可以看到已发布服务的WSDL。如图所示。

二、不使用JAXB来实现服务提供者与XML处理

有的时候,可能不想使用JAXB生成类,而是希望可以直接使用XML来实现业务逻辑。XML处理可以提高性能,特别是使用XMLT样式表来实现业务逻辑的时候。另外,如果使用SEI,当WSDL发生变动,哪怕这种变动非常微小而不影响业务逻辑,也需要重新生成SEI并修改代码。而使用XML可以对WSDL的变化有一定程序的隔离。

为了部署可以直接处理XML消息的WEB服务,要使用javax.xml.ws.provider接口。这个接口中定义了一个方法:

T invoke(T request) 在这个方法中,参数类型T代表消息或消息的有效负载。Provider支持两种方式:Message和Message Payload模式。在Message模式中,服务实现Bean直接处理特定协议的消息结构,比如当用于SOAP协议绑定时,invoke要处理整个SOAP消息。而在Message Payload模式中,服务实现负载只需处理消息的负载,即SOAP的消息体

(soap:body)。

直接处理XML消息的RequestOrder服务实现如下: 第一步:

在Eclipse中新建项目WSServerNoSEI。 第二步:

在包server.imp下新建类RequestOrderEndpoint。该类是接口Provider的一个实现。其类标注如下 :

50. @WebServiceProvider(serviceName = \51. portName = \

52. targetNamespace = \53. )

54.@ServiceMode(Service.Mode.PAYLOAD)

55.public class RequestOrderEndpoint implements Provider { 这两个标注说明这是一个服务实现Bean并且使用Message Payload模式。由于Provider的实现不是通过WSDL到JAVA的映射实现的,因此必须在@WebServiceProvider中通过wsdlLocation属性说明或在使用Endpoint发布的时候设置相应的元数据(本例采取的方法)。

在该类的invoke方法中,通过Dom方式组建了用作调用结果的SOAP文档(具体代码见附件)。从代码中可以看出,使用DOM API要比使用JAXB更加繁琐、容易出错。

将该服务实现Bean发布与使用SEI的方法相同,也是使用Endpoint#publish方法。

三、使用标注从Java开始开发

有的时候,企业内部可能已经有一些系统实现了所需要的业务逻辑,如果想把这些系统发布了WEB服务,首先可以考虑的一种方法是向原来的类中添加标注,使其与要发布的目标WSDL相符。这包括两个方面的内容:

1、 给业务逻辑实现类添加标注,使其与WSDL中wsdl:port的定义相一致。 2、 给业务逻辑中使用的实体类添加JAXB标注,使其与WSDL中wsdl:types中定义

的schema一致。

但是,很多情况下,这种方式是不可实现的,原因如下:

? 系统可能是一个第三方的程序包,无法修改,或者代码由其它部门开发,修改需要

的时间太长。

? 业务逻辑类的方法与wsdl:port中定义的wsdl:operation方法可能不能完全

一致。

? 实体类可能与WSDL中定义的模式差别太大,无法使用JAXB标注实现。

对于业务逻辑实现类,如果可以通过添加标注的方法实现。则首先考虑向类中添加标注实现。如果不能通过这种方法实现的话,可以给已有的业务逻辑实现类添加适配器层,对外开放WSDL中定义的接口,在收到请求时,调用业务逻辑实现类的方法来完成业务逻辑。 对于实体类,也首先考虑直接添加标注的方法,如果不能实现的话,可以考虑以下方法:

? 添加适配层,将参数从WSDL中schema映射类转换为程序内部的实体类并调用业

务实现方法,然后将调用返回值转换为WSDL中schema对应类。

? 使用XSLT。将收到的SOAP消息中的请求参数XML片段使XLST转换到与业务实

体类相对应的XML格式,然后进行拆收和业务逻辑方法调用,最后再将返回值封送的XML片段使用XLST转换到与WSDL中schema定义相符的格式并传回客户

端。这种方法要求可以访问并修改原代码以添加标注。 现在来看一个可以直接添加标注的例子: 第一步:

将附件中的OriServiceImplement项目导入到Eclipse中,现在已有的业务逻辑的实现位于services包中(省略了异常处理),它所访问的实体类位于beans包中。它们都还没有添加标注。

第二步:

首先给beans包中的实体类添加标注,使其与要发布的WEB服务的WSDL中定义的schema相对应。这里来说明OrderType类的映射:

11. @XmlAccessorType(XmlAccessType.FIELD)

12. @XmlType(namespace = \

13. @XmlRootElement(name = \14. public class OrderType { 15. 16. 17. 18. 19. 20. 21.

protected String OrderKey; protected Header OrderHeader;

@XmlElementWrapper(name = \protected List item;

@XmlJavaTypeAdapter(WrapCcardAdapter.class) protected MyCreditCard orderCcard; protected String orderText;

OrderType类中使用了两个未介绍的标注:@XmlElementWrapper和@XmlJavaTypeAdapter。

@XmlElementWrapper用于给一个集合类的属性添加一个包装器对象,从而在生成的XML中,集合类属性对应的元素列表是作为包装器对象对应的元素的子元素出现,而是作为其所在Java类对应的元素的子元素出现。在该类对应的XML片段中,包装器元素OrderItems是Order元素的子元素,而OrderItems的子元素是MyItem对应的BUSOBJ_ITEM类型XML元素的一个列表。如果不使用该标注类而要达到相同效果的话,就要增加一个只有一个List属性的OrderItems类。

@XmlJavaTypeAdapter是JAXB中一个强有力的工具,它使得我们可以通过手工编码的方式,来完成标准映射无法实现的映射。在这个例子中,是使用该方法为类型为MyCreditCard类型的orderCcard属性添加了一个OrderCcard类型的包装对象(@XmlElementWrapper标注只能用于集合对象)。该标注的属性指定了个映射类:WrapCcardAdapter。该类的内容如下:

5. public class WrapCcardAdapter extends 6. 7.

8. public MyCreditCard unmarshal(MyCreditCard.OrderCcard wrapper) 9. 10. 11. } 12.

13. public MyCreditCard.OrderCcard marshal(MyCreditCard ccard) throws Exception

{ 14.

MyCreditCard.OrderCcard wrapper = new MyCreditCard.OrderCcard();

throws Exception { return wrapper.ccard;

XmlAdapter {

15. 16. 17. } 18. }

wrapper.ccard = ccard; return wrapper;

可以看出WrapCcardAdapter扩展自抽象类:

Javax.xml.bind.annotation.adapters.XmlAdapter

XmlAdapter类引用了两个类型参数,一个是JAXB不知道如何处理的类型(BoundType),另外一个是JAXB知道如何处理的类型(ValueType),在封送过程中,JAXB首先将BoundType封送到ValueType,然后继续对ValueType进行封送。在拆收的过程中,JAXB会首先将XML拆收到ValueType,然后再继续对BoundType进行拆收。这两个过程分别对应着XmlAdapter类中的两个抽象方法: marshal()

unmarshal() 在该例中,marshal新建一个包装对象并且设置其与原对象的包含关系,在unmarshal方法中,直接取出包装对象中的原对象并返回。

第三步:

由于要采用的是document/literal/wrapped样式的WSDL,所以要创建请求和响应的包装器类。新建包server.sei并新建类MyRequestOrder以及MyRequestOrderResponse,分别向其中添加与请求参数与响应参数相对应的属性并完成映射。

第四步:

给RequestOrderService类添加标注,使其成为一个WEB服务。添加标注后该类的内容如下:

22. @WebService(serviceName = \

= \

23. public class RequestOrderService { 24. @WebMethod

25. @WebResult(name = \targetNamespace = \26. @RequestWrapper(localName = \

\27. @ResponseWrapper(localName = \

\\28. public OrderType userRequestOrders( 29. 30. 31. 32. 33. 34.

@WebParam(name = \\String custNum,

@WebParam(name = \\String poNum,

@WebParam(name = \\MyCreditCard card,

35. 36.

@WebParam(name = \\List itemList) {

该类标注的方法与直接使用wsimport工具生成的SEI中的RequestOrderPort基本相同,此处不再重复。

第五步:

新建server.main.Main类,使用Endpoint类将服务发布到http://localhost:8088/RequestOrderService。使用SOAPUI发起请求,可以看到服务可以正确响应。

除直接标注的方法外,使用适配器的方法可以通过首先使用wsimport生成SEI,然后在SEI的实现类中直接调用已经存在的业务方法实现,其步骤与使用直接使用SEI的方法相似。使用XLST使用的方法已在实验1中演示,此处也不再重复。

实验附件:

实验5、使用JAX-WS开发WEB服务客户端

实验环境:

1、 GLASSFISH 3.1

实验内容:

1、 使用代理调用WEB服务

2、 使用Dispatch动态调用WEB服务 3、 异步调用WEB服务

实验步骤:

一、 使用代理调用WEB服务

创建调用WEB服务的客户端,最简便的方法是使用与实验2中相同的方法来创建SEI,并利用该SEI调用WEB服务。虽然SEI的生成方法与服务器端相同,但是它们所起的作用是不同的。在服务器端,SEI的作用是确定了服务实现Bean的接口,需要开发者自己提供SEI的实现。但是在客户端,实现SEI的类的实例是JAX-WS通过Java的动态代理类(参考java.lang.reflect.Proxy类的Javadoc文档)在运行时动态创建的,该实例会根据程序中的标注,将对SEI方法的调用转换成通过SOAP请求对服务器端WEB服务的调用,并将服务器端返回的SOAP响应转换成方法的返回值,从而完成对WEB服务的调用。

使用代理的客户端开发步骤如下 : 第一步:

运行实验2中的服务端WSServer。该项目会将RequestOrder服务发布在地址http://localhost:8088/RequestOrderService上。

第二步: 运行命令

/bin/wsimport -s ./src -d ./bin -p client.sei http://localhost:8088/RequestOrderService?wsdl

该命令作用与实验2中相同,只是用于生成SEI的WSDL使用了服务器端的WSDL而不是本地的WSDL。打开文件夹./client/sei,可以看到实验2中相比,除了个别文件命名有差别,生成的文件基本相同。

第三步:

在Eclipse中新建项目WSClient,将生成的包client.sei导入。并在包client.main下新建类Client。在该类中,使用如下方法来获得一个RequestOrderPort的动态代理实例:

50. RequestOrder_Service service = new RequestOrder_Service(); 51. RequestOrderPort port = service.getRequestOrderPort();

打开类RequestOrder_Service,可以看到该类是通过Service#getPort方法获得了RequestOrderPort的动态代理实例。然后就可以通过调用类RequestOrderPort动态代理实例的requestOrder方法来完成对WEB服务的调用。

第四步:

在Client中增加一个将调用RequestOrderPort#requestOrder方法返回的OrderType转换成XML并打印的方法以便于调试。该方法如下所示:

132. private static void printReturnedOrder(OrderType order) { 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. }

Marshaller m = null; try { }

if (m != null) { }

JAXBElement orderElement = new JAXBElement( }

System.out.println(\System.out.println(\

+ order.getOrderHeader().getPURCHDATE());

System.out.println(\

new QName(\OrderType.class, order);

JAXBContext jc = JAXBContext.newInstance(OrderType.class); m = jc.createMarshaller();

m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); m.setProperty(Marshaller.JAXB_ENCODING,\

} catch (Exception e) {

try {

m.marshal(orderElement, System.out); e.printStackTrace(); } catch (JAXBException e) {

} else {

由于在生成SEI的时候,JAXB也将服务中涉及到的Schema与Java类建立了绑定关系,因此可以直接通过JAXB的接口将OrderType对象封送为XML片段并使用标准输出打印。为了完成封送过程,首先使用

JAXBContext.newInstance(java.lang.Class...)

创建一个新的JAXB上下文,这样创建的上下文可以识别在参数中指定的类以及这些类中以静态方式直接或间接引用的类。然后,使用该上下文创建一个Marshaller实例并设置相应的参数,最后将要封送的对象封装到JAXBElement中并使用Marshaller的marshal方法来完成封送过程。

第五步:

假设客户使用银行卡购买两件商品,对该用例进行编码如下: 54. BUSOBJCCARD ccard = createCreditCard();

55. ArrayList itemList = createItemList(); 56.

57. OrderType order; 58. try {

59. System.out.println(\60. order = port.requestOrder(\

61. printReturnedOrder(order);

运行该用例,可以看到控制台输入如下:

7828546468

NE

2011-02-17 CUS001 CC

2011-03-03

GOODS001 NE02 50 KG

7.95 这是商品1

GOODS002 5 20 这是商品2

VISA

01234567890123456789 2011-10-31 GY

497.50

WEB Order placed via Order Management System.

即服务器端成功返回了客户的订单。 第六步:

假设客户端发送的消息中客户编号为空,此时服务器端会抛出一个输入参数错误异常。该异常在WSDL文件是映射过的,即对应WSDL文件夹中的如下wsdl:fault映射:

68. try { 69. 70. 71. 72.

System.out.println(\order = port.requestOrder(\

itemList);

}catch (InputFault e) { // a checked exception

...

运行代码,发现程序并未按预想的抛出InputFault异常,而是抛出了SOAPFaultException。查看接收到的SOAP消息,发现在detail信息中,异常被映射到如下XML片段:

xmlns:ns4=\

xmlns:ns8=\be null.\

其中inputMessageValidationFault和msg属性的命名空间为http:// www.example.com/oms,而不是WSDL中定义的http://www.example.com/fault。查看代码,可以发现,InputMessageValidationFaultType类中的msg属性值的@XmlAttribute标注未添加命令空间映射,从而使用了在package-info.java中对包中映射的全局命令空间,造成错误。因此,修改客户端与服务器端的InputMessageValidationFaultType中的msg属性的标注,添加namespace定义: @XmlAttribute(namespace=\

重新运行服务器端与客户端,可以发现此果输出了正确的报错信息:

Running test with null customer number. Mapped Exception (InputFault) InputFault.getMessage() =

Input parameter failed validation.

InputFault.getFaultInfo.getMsg() = Customer Number cannot be null.

第七步:

假设客户端发送的信用卡数据已经过期,服务器端就会返回一个异常,但是这个异常并未在WSDL进行映射。这时,在客户端对RequestOrderPort#requestOrder的调用时就会抛出SOAPFaultException。此时服务器端返回的SOAP消息如下:

xmlns:S=\Business logic exception

class=\note=\disable this feature, set com.sun.xml.internal.ws.fault.SOAPFaultBuilder.disableCaptureStackTrace system property to false\

Business logic exception

二、 使用Dispatch动态调用WEB服务

尽管通过动态代理可以让WEB服务调用看起来就像调用Java方法一样简单,但是有的时候这种方法不足以满足我的要求。

? 代理方法必须使用从WSDL生成的SEI,而其生成方法必须按照标准的JAX-WS

WSDL到JAVA的映射方式。如果本来已经有一个基于JAVA的订单管理系统,么很有可能这个系统已经有一个信用卡类,但是使用SEI就必须使用从WSDL中生成的信用卡类,从而不得不在这两个类之间进行一些转化。

? 有的时候需要直接使用XML来调用WEB服务,比如要发送的文档是XSLT处理的

结果,这时就可以直接使用Dispatch 来提供 SOAP 信封然后发送消息。

? 有的时候可以希望可以动态调用WEB服务。此时调用WEB服务不用提前生成和编

译SEI。比如可以通过服务注册机制,先查询需要的WEB服务,这样的情况就会发生。

对于直接使用原始的XML进行消息传递的情况,程序的开发步骤如下: 第一步:

在Eclipse中新建项目XMLMessageClient。在包client.main下新建类 。 第二步:

客户端可以使用 Service#create 方法来创建一个 Service 实例。当客户端使用这种方法创建一个 Service 实例时,该实例就是一个动态 Service 实例,这表示它是动态配置的,而不是像使用SEI的方法中那样是静态配置的。可以通过两种方式创建动态

的服务实例:

? create(QName serviceName) 为带有给定名称的服务返回一个服务对象。没

有向该服务附加任何 WSDL 文档。 之后调用service#addPort为其添加端口并指定服务端点地址。

? create(URL wsdlLocation, QName serviceName) 为指定的 WSDL 文档

返回服务对象并返回服务名。

创建Service对象的代码如下所示:

33. URL wsdlURL = new URL(\

\

34. QName serviceQName = new QName(\35.

\

36. Service service = Service.create(wsdlURL, serviceQName);

第三步:

现在可以使用Service对象创建Dispatch的实例:

39. QName portQName = new QName(\40. 42.

\

Source.class, Service.Mode.PAYLOAD);

41. Dispatch dispatch = service.createDispatch(portQName,

Service#createDispatch方法接收三个参数,wsdl:portType的QName/类型参数T的Class及服务模式。类型参数T指定了用于封装要发送的XML数据的Class。Dispatch支持;javax.xml.transform.Source(用于XML文档)、Object(用于JAXB映射类)、javax.xml.soap.SOAPMessage及javax.activation. Datasource(用于MIME类型的消息)。在本例中使用了javax.xml.transform. Source。服务模式分为消息(Message,程序处理整条消息)及负载(Payload,程序只处理消息的body部分)模式, 本例中使用了负载模式。

第四步:

用来调用服务的XML文档如下所示,它与使用SEI方法的例子中的用例一是等价的。

CUST001

VISA

01234567890123456789 2011-10-31 GY

GOODS001 NE02 50 KG

7.95 这是商品1

GOODS002 5 20 这是商品2

调用代码如下所示:

(\

46. InputStream xmlFile = Client.class.getResourceAsStream 47. StreamSource xmlSource = new StreamSource(xmlFile); 48. Source orderSource = dispatch.invoke(xmlSource);

服务器端返回的XML文档格式化后如下所示:

xmlns:S=\xmlns:ns3=\

7853006687

NE

2011-02-17 CUST001 CC

2011-03-03

xmlns=\

GOODS001 NE02 50 KG

7.95 这是商品1

GOODS002 5 20 这是商品2

VISA

01234567890123456789 2011-10-31 GY

497.50

WEB Order placed via Order Management System.

另一种使用Dispatch类来进行XML消息传递的方法,就是编写自己的JAXB标注类来表示Web服务发送和接收的XML消息。这里使用了JAXB,但是并没有使用由JAX-WS生成的SEI,而是使用JAXB标注将自己现有的Java类映射到目标Web服务接收和发送的消息有效负载。

程序开发步骤如下: 第一步:

在Eclipse中新建项目JAXBXMLMessageClient。 第二步:

创建使用JAXB标注与XML请求消息绑定的Java类。比如对于请求消息的包装器元素:

9. @XmlAccessorType(XmlAccessType.FIELD)

10. @XmlType(namespace = \11. @XmlRootElement(name = \12. namespace = \13. public class MyRequestOrder { 14. 15. 16. 17.

protected String CUST_NO; protected String PURCH_ORD_NO; protected MyCreditCard ccard; protected List item;

18.}

MyRequestOrder是四个元素的容器,每个参数分别与req:requestOrder的子元素相对应。类似的使用实验2中介绍的JAXB的标注来创建其它的类。 第三步:

创建Client类。Client类中使用与直接发送XML的相同的方法来创建Service对象的实例。使用Service对象创建Dispatch的方法如下:

30. JAXBContext ctxt = JAXBContext.newInstance(MyRequestOrder.class, 31. 33.

MyRequestOrderResponse.class); \

32. QName portQName = new QName(\

34. Dispatch dispatchJAXB = service.createDispatch(portQName, ctxt, 35.

Service.Mode.PAYLOAD);

这里创建Dispatch的实例的时候,createDispatch方法接收的是JAXBContext实例,而不是类型参数。底层的Dispatch对象将使用该对象来完成Java与消息有效负载之间的封送/拆收转换。 第四步:

在创建Dispatch对象后,配置一个RequestOrder类实例,然后使用Dispatch#invoke方法调用目标WEB服务。响应消息的有效负载被拆收为一个MyRequestOrderResponse实例。

44. MyRequestOrderResponse resp = (MyRequestOrderResponse) dispatchJAXB 45.

.invoke(myReq);

第五步:

运行项目,可以看到与直接使用XML方法相同的输出。

三、 异步调用WEB服务

对于SOA编程来说,异步调用是一个非常强大的工具。有的情况下,Web服务的调用执行要比在本地地址空间的处理慢很多。此时可以使用单独的线程运行WEB服务调用,而且本地执行代码在等待这些线程调用完成的同时,还能继续自己的工作,从而能很大的提高代码运行的速度。

JAX-WS为异步处理提供了两种方法:轮询和回调。在异步调用的轮询方式中,代码不断轮询一个java.xml.ws.Response实例,以便决定Web服务调用何时完成。另一方面,使用回调方式,代码应该提供一个javax.xml.ws.Asynchandle实例来处理Web 服务调用的结果。

轮询的方式是通过dispatch#invokeAsync实现的,代码示例如下:

Response responseSource = dispatch.invokeAsync(xmlSource); long startTime = (new Date()).getTime(); while (!responseSource.isDone()) { Thread.sleep(10); }

long elapsed = (new Date()).getTime() - startTime;

在幕后,该方法使用java.util.concurrent.Executor和Service实例,用一个单独的线程来调用WEB服务。异步调用将返回Response实例,它是Future的包装,提供了一个额外的方法getContext(),用于接收响应消息的上下文。在以上的例子中,不断测试isDone()方法以判断异步调用是否可以完成,这就是轮询的过程。

下面将说明如何使用SEI代理进行异步调用。 第一步:

使用wsimport工具生成SEI。假设目标wsdl:operation的同步映射方法的形式如下所示:

Xxx port.yyyZzz()

其中,Xxx为返回类型,yyyZzz为属性名称。那么,对于相同的wsdl:operation,其轮询方式的异步映射的方法名,应该类似于以下形式: Response port.yyyZzzAsync()

其回调方式的的方法名,应该类似于以下形式:

Future port.yyyZzzAsync(… , AsyncHandler asynchandler)

用来生成这些接口的绑定语言声明,可以要WSDL内部以内联方式指定,但是,WSDL会随着WEB服务一起发布,因此WSDL中不应该包含只有Java客户端才使用的定制绑定语言声明,而是将其放在单独的文件中,使用XPath表达式来指定wsdl:port以及wsdl:operation。在本例中,文件内容如下:

1.

3. wsdlLocation=\4. xmlns=\

5. true 8. 9. 10.

该文件说明了RequestOrderPort下的requestOrder应该被映射成异步方法。 在使用wsimport的时候,可以使用-b选项来指明绑定文件。

运行以下命令生成SEI:

\\bin\\wsimport

-s

./src

-d

./bin

-p

client.sei

-b

async-binding-customizations.xml

http://localhost:8088/RequestOrderService?wsdl

在生成的SEI RequestOrderPort.java中,增加了两个异步调用的方法。

第二步:

在Eclipse中新建项目AsyncClient并将生成的包client.sei导入。 第三步:

为异步调用创建回调处理类RequestOrderCallbackHandler,其处理代码如下:

27. long elapsed = (new Date()).getTime() - startTime; 28. Marshaller m; 29. try { 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42.

JAXBContext jc = JAXBContext

.newInstance(RequestOrderResponse.class); m = jc.createMarshaller();

m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

System.out.println(); System.out

.println(\System.out.println(\

System.out.println(\System.out

.println(\System.out.println(elapsed + \

本文来源:https://www.bwwdw.com/article/aq66.html

Top