JaxWs 基于消息编程
1 两种消息模式
2 三种数据类型
2.1 Source
2.2 SOAPMessage
2.3 DataSource
3 服务端访问底层信息
4 客户端访问底层消息
4.1 Dispatch的三种请求方式
5 统一入口
6 注意
6.1 MESSAGE和PAYLOAD的区别
通过SEI(Service Endpoint Interface)在服务端和客户端进行操作时,我们是直接使用的对应实现类或代理类对象。表面上看我们是使用的对象在服务端和客户端进行通讯,而实际上底层还是通过发送消息和解析消息进行的。有时候我们可能会希望或者需要直接访问这些消息,这个时候我们就可以通过Provider和Dispatch来实现了。Provider是应用在服务端的,而Dispatch是应用在客户端的。
1 两种消息模式
在使用Provider和Dispatch时,我们可以使用两种消息模式,MESSAGE和PAYLOAD。
当使用MESSAGE模式时我们可以访问整个的消息,包括绑定的任何header和wrapper。而使用PAYLOAD模式时我们仅仅可以访问payload的消息。如当我们的Dispatch在以PAYLOAD模式工作时,它只能访问到返回的SOAPMessage的body部分,而binding层将处理任何绑定的header和wrapper。
2 三种数据类型
Provider和Dispatch在进行信息传递时只能使用三种数据类型:
l javax.xml.transform.Source
l javax.xml.soap.SOAPMessage
l javax.activation.DataSource
2.1 Source
Source是一个接口,它持有一个XML文档对象。每一个Source接口的实现类都提供了一系列的方法来访问和操纵其持有的XML文档的内容。Source接口的实现类有DOMSource、SAXSource和StreamSource等。
2.2 SOAPMessage
SOAPMessage是一个抽象类,使用SOAPMessage的时候需要满足两个条件:
第一:Provider实现类使用的是SOAP绑定,即SOAPBinding;
第二:Provider实现类使用的是MESSAGE Mode。
SOAPMessage持有一个SOAP消息。
2.3 DataSource
DataSource是一个接口,使用时需要满足以下两个条件:
第一:Provider实现类使用的是Http绑定,即HttpBinding;
第二:Provider实现使用的是MESSAGE Mode。
DataSource是对数据集合的抽象,在适当的时候可以通过InputStream和OutputStream的形式提供对该数据的访问。其实现类有FileDataSource和URLDataSource。
3 服务端访问底层信息
服务端访问底层信息是通过Provider接口进行的。通过实现Provider接口并且把实现类发布为一个WebService,我们就可以在客户端发起请求时访问到其发送过来的底层消息对象,Source、SOAPMessage或者DataSource。Provider接口只定义了一个invoke方法,该方法接收一个消息对象,并返回一个同类型的消息对象,而且消息对象的类型只能是上面介绍的三种类型之一。
在使用Provider的时候我们需要在其实现类上使用@WebServiceProvider进行标记(使用@WebService标记好像也行),并且Provider<T>指定的消息对象类型必须是上面提到的三种数据类型之一。WebService使用的消息模式默认为PAYLOAD,我们可以在Provider实现类上使用@ServiceMode来指定其它值,如@ServiceMode(Service.Mode.MESSAGE)。
下面我们来看一个使用Provider的例子:
import javax.xml.soap.SOAPMessage; import javax.xml.ws.Provider; import javax.xml.ws.Service.Mode; import javax.xml.ws.ServiceMode; import javax.xml.ws.WebServiceProvider; @WebServiceProvider(serviceName = "SOAPMessageService", portName = "SOAPMessagePort", targetNamespace = "http://provider.jaxws.sample.cxftest.tiantian.com/") @ServiceMode(Mode.MESSAGE) public class SOAPMessageModeProvider implements Provider<SOAPMessage> { public SOAPMessage invoke(SOAPMessage request) { SOAPMessage response = null; try { System.out.println("客户端以SOAPMessage通过MESSAGE Mode请求如下: "); request.writeTo(System.out); response = MessageUtil.getInstance().create(null, "/provider/SOAPMessageResp.xml"); } catch (Exception ex) { ex.printStackTrace(); } return response; } }
在上面代码中,我们的SOAPMessageModeProvider:
l 实现了Provider接口;
l 通过Provider接口定义的泛型指定使用的消息对象数据类型为SOAPMessage;
l 通过@WebServiceProvider标注其为一个WebService,并指定了serviceName等属性;
l 通过@ServiceMode指定其使用的消息模式为MESSAGE;
l 在invoke方法中接收了一个SOAPMessage,并返回了一个SOAPMessage。
其中MessageUtil类的代码为:
import java.io.IOException; import java.io.InputStream; import javax.xml.soap.MessageFactory; import javax.xml.soap.MimeHeaders; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; public class MessageUtil { private static MessageUtil instance = new MessageUtil(); private MessageFactory factory; private MessageUtil() { try { factory = MessageFactory.newInstance(); } catch (SOAPException e) { e.printStackTrace(); thrownew RuntimeException(e); } } public static MessageUtil getInstance() { returninstance; } /** * 创建一个默认的SOAPMessage * @return * @throws SOAPException */ public SOAPMessage create() throws SOAPException { returnfactory.createMessage(); } /** * 根据MimeHeaders和soap格式文件路径创建一个SOAPMessage * @param headers * @param filePath * @return * @throws IOException * @throws SOAPException */ public SOAPMessage create(MimeHeaders headers, String filePath) throws IOException, SOAPException { InputStream is = MessageUtil.class.getResourceAsStream(filePath); SOAPMessage message = factory.createMessage(headers, is); is.close(); return message; } /** * 获取MessageFactory * @return */ public MessageFactory getMessageFactory() { returnfactory; } }
文件SOAPMessageResp.xml的内容为:
<?xml version="1.0" encoding="utf-8" ?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <ns4:sayHiResponse xmlns:ns4="http://provider.jaxws.sample.cxftest.tiantian.com/"> <ns4:responseType>SOAPMessage Response</ns4:responseType> </ns4:sayHiResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
有了Provider实现类之后我们就可以把它发布为一个WebService了,如:
Object service = new SOAPMessageModeProvider(); Endpoint.publish("http://localhost:8080/test/jaxws/services/SOAPMessage", service);
4 客户端访问底层消息
客户端访问底层消息是通过Dispatch接口进行的,跟服务端的Provider接口一样,Dispatch接口中同样定义了一个invoke方法,该方法负责向服务端发送一种数据类型的消息,并返回一个对应类型的消息。不同的是Dispatch接口的实现类可以不需要我们自己定义和实现。我们可以通过创建代表服务端对应WebService对象的Service对象来创建一个Dispatch对象。Service类中定义了一系列的createDispatch重载方法,但比较常用的还是如下方法:
public <T> Dispatch<T> createDispatch(QName portName, Class<T> type, Mode mode)
该方法接收三个参数:
l 第一个参数QName类型的portName代表目标Service中对应的portName;
l 第二个参数表示底层发送和接收消息时使用的数据类型,根据配置的不同可以是前面提到的三种数据类型中的一种;
l 第三个参数表示使用的消息模式。
下面我们来看一个创建Dispatch,并使用它来与服务端进行交互的例子:
public static void main(String args[]) throws Exception { //定义serviceName对应的QName,第一个参数是对应的namespace QName serviceName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "SOAPMessageService"); //定义portName对应的QName QName portName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "SOAPMessagePort"); //使用serviceName创建一个Service对象,该对象还不能直接跟WebService对象进行交互 Service service = Service.create(serviceName); //创建一个port,并指定WebService的地址,指定地址后我们就可以创建Dispatch了。 service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, "http://localhost:8080/test/jaxws/services/SOAPMessage"); //创建一个Dispatch对象 Dispatch<SOAPMessage> dispatch = service.createDispatch(portName, SOAPMessage.class, Mode.MESSAGE); //创建一个SOAPMessage SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml"); //调用Dispatch的invoke方法,发送一个SOAPMessage请求,并返回一个SOAPMessage响应。 SOAPMessage response = dispatch.invoke(request); System.out.println("服务端返回如下: "); response.writeTo(System.out); }
在上面的代码中,我们先通过serviceName创建了一个Service对象,然后再通过addPort方法指定其对应的WebService地址。其实,我们也可以像下面这样,通过WebService对应的wsdl文件和serviceName创建对应的Service对象。
//指定wsdl文件的位置 URL wsdl = new URL("http://localhost:8080/test/jaxws/services/SOAPMessage?wsdl"); Service service = Service.create(wsdl, serviceName);
上述例子中对应的SOAPMessageReq.xml文件的内容如下:
<?xml version="1.0" encoding="utf-8" ?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <ns4:sayHi xmlns:ns4="http://provider.jaxws.sample.cxftest.tiantian.com/"> <ns4:requestType>SOAPMessage Request</ns4:requestType> </ns4:sayHi> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
4.1 Dispatch的三种请求方式
同步请求
上述Dispatch的invoke方法请求是同步的,也就是阻塞式的,程序在调用了invoke方法之后会一直等待服务端的返回。
异步请求
异步请求是通过Dispatch的invokeAsync方法进行的。Dispatch的异步请求有两种方式,一种是通过定期轮询Dispatch调用invokeAsync方法返回的Response对象是否已经可以返回,另一种是通过回调函数的形式。所以,针对于这两种方式,Dispatch的invokeAsync有两个重载方法:
public Response<T> invokeAsync(T msg); public Future<?> invokeAsync(T msg, AsyncHandler<T> handler);
使用定期轮询的方式时,我们在执行完invokeAsync之后会返回一个Response对象,该对象会定期轮询判断invokeAsync方法是否已经完成。当invokeAsync方法调用完成之后,Response对象的isDone()方法会返回true,但是这种调用的完成并不一定是成功的完成,有可能是出异常了,或者其他什么问题。在调用完成,也就是isDone()方法的结果为true之后,我们就可以通过Response对象的get()方法尝试获取对应的返回对象了,之所以说是尝试获取,是因为我们的invokeAsync方法不一定是正常的完成了,如果没有正常完成,调用get()方法将抛出异常。上面Dispatch调用的例子如果我们把它改为定期轮询的异步请求的话,其调用过程的代码可以是这样子:
SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml"); Response<SOAPMessage> response = dispatch.invokeAsync(request); System.out.println("开始判断调用是否已完成"); while (!response.isDone()) { Thread.sleep(200l); } SOAPMessage responseMsg = null; try { responseMsg = response.get(); } catch (Exception e) { System.out.println("调用失败"); } if (responseMsg != null) { System.out.println("服务端返回如下: "); responseMsg.writeTo(System.out); }
使用回调函数的方式时,我们需要给invokeAsync方法传递一个AsyncHandler接口的实现类作为回调对象。AsyncHandler接口中定义了一个handleResponse方法可以处理服务端返回的结果。当请求完成以后,Dispatch后端的线程会调用AsyncHandler对象的handleResponse方法。前面Dispatch调用的例子如果我们把它改为使用回调函数异步调用的话,其核心代码可以是如下这个样子:
SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml"); Future<?> future = dispatch.invokeAsync(request, new AsyncHandler<SOAPMessage>() { @Override public void handleResponse(Response<SOAPMessage> res) { try { System.out.println("回调函数被调用了……"); SOAPMessage responseMsg = res.get(); responseMsg.writeTo(System.out); } catch (Exception e) { e.printStackTrace(); } } }); System.out.println("可以开始做其他事情了……"); while (!future.isDone()) { System.out.println("在请求完成之前,整个程序不能结束,否则回调函数不会被调用"); Thread.sleep(200l); }
注意,在使用回调函数方式使用Dispatch的异步请求时,请求结果未返回前整个程序不能停止。如果在请求结果返回以前,整个程序结束了,回调函数不会被调用。当然这种情况只会出现在如上单次执行的测试环境下,我们经常使用的Web环境是不会出现此问题的。由此看来,回调函数应该是被Dispatch内部的守护线程调用的。
一次请求
一次请求是通过invokeOneWay方法来进行的。它表示我们的客户端只需要发送请求,而不需要等待服务端的返回。
SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml"); dispatch.invokeOneWay(request);
5 统一入口
JaxWs基于消息编程的一个好处是我们可以在服务端使用一个Provider来接收和处理所有的WebService请求,使用一个Dispatch或多个Dispatch来发送请求,从而达到对WebService的统一管理;另一个好处是客户端可以不定义或者说是不需要使用SEI接口及其相关的类。下面我们来看一个客户端和服务端之间直接通过消息编程的简单示例。
在服务端定义一个Provider<DOMSource>的实现类UniteServiceProvider。
@WebServiceProvider(serviceName="UniteService", portName="UniteServicePort", targetNamespace="http://provider.jaxws.sample.cxftest.tiantian.com/") @ServiceMode(Service.Mode.MESSAGE) @BindingType(HTTPBinding.HTTP_BINDING) public class UniteServiceProvider implements Provider<DOMSource> { @Override public DOMSource invoke(DOMSource request) { DOMSource response = null; MessageUtil.getInstance().printSource(request); Document requestDoc = (Document)request.getNode(); Element commandEle = (Element)requestDoc.getElementsByTagName("command").item(0); Element paramEle = (Element)requestDoc.getElementsByTagName("param").item(0); String command = commandEle.getTextContent(); try { response = this.getResponse(command, paramEle); } catch (Exception e) { e.printStackTrace(); } MessageUtil.getInstance().printSource(response); return response; } /** * 根据指令和对应的参数进行相关操作并返回对应的操作结果 * @param command * @param paramEle * @return * @throws Exception */ private DOMSource getResponse(String command, Element paramEle) throws Exception { String responseContent = "<response><product><id>1</id><name>Apple</name></product></response>"; InputStream is = new ByteArrayInputStream(responseContent.getBytes("UTF-8")); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is); DOMSource response = new DOMSource(doc); return response; } }
在上述代码中我们的服务端接收到一个DOMSource的request请求之后,通过DOMSource的getNode方法取到存放在其中的Document对象。之后我们对该Document的内容作了一个解析,然后把对应的返回结果封装成一个DOMSource进行返回。
上述使用到的MessageUtil类里面的printSource方法的代码为:
/** * 输出Source的内容 * @param source */ public void printSource(Source source) { StreamResult result = new StreamResult(System.out); try { TransformerFactory.newInstance().newTransformer().transform(source, result); System.out.println(); } catch (Exception e) { e.printStackTrace(); } }
发布上述Provider的过程这里就不再赘述了。接着来看一下客户端调用的代码:
public class UniteServiceClient { public static void main(String args[]) throws Exception { String requestContent = "<request><command>10001</command><parameter><id>1</id></parameter></request>"; InputStream is = new ByteArrayInputStream(requestContent.getBytes("UTF-8")); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is); DOMSource requestMsg = new DOMSource(doc); MessageUtil.getInstance().printSource(requestMsg); QName serviceName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "UniteService"); QName portName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "UniteServicePort"); Service service = Service.create(serviceName); //指定绑定方式为HTTPBinding service.addPort(portName, HTTPBinding.HTTP_BINDING, "http://localhost:8080/test/jaxws/services/UniteService"); Dispatch<DOMSource> dispatch = service.createDispatch(portName, DOMSource.class, Mode.MESSAGE); DOMSource responseMsg = dispatch.invoke(requestMsg); System.out.println("服务端返回来的信息是:"); MessageUtil.getInstance().printSource(responseMsg); } }
6 注意
6.1 MESSAGE和PAYLOAD的区别
在文章的开始部分介绍了两种消息模式,以及它们之间的区别。MESSAGE模式是访问的整个消息,而PAYLOAD模式访问的只是消息的部分内容。我们也知道,当我们使用DOMSource作为消息对象时我们可以使用MESSAGE和PAYLOAD这两种模式。所以接下来我们就来说说使用DOMSource作为消息对象时使用MESSAGE模式和PAYLOAD模式的区别。
为了具有可比性,我们指定使用的BindingType为SOAPBinding,(未指定BindingType时默认也为SOAPBinding),下面来看看使用MESSAGE模式和PAYLOAD模式的区别。
MESSAGE模式
使用MESSAGE模式,我们在发送DOMSource消息对象时,如果我们的DOMSource消息对象里面持有的Document不是一个SOAPPart(SOAPPart是一个实现了Document接口的抽象类),那么系统会先生成一个SOAPPart,然后把我们的DOMSource里面持有的Document作为SOAPPart关联的SOAPEnvelope对象的SOAPBody部分。然后再把该SOAPPart作为DOMSource持有的Document对象。这个时候如果我们只想获取到最原始的document,也就是SOAPBody包裹的那一段文档,我们得这样来取:
public DOMSource invoke(DOMSource request) { SOAPPart soapPart = (SOAPPart) request.getNode(); try { SOAPEnvelope soapEnvelop= soapPart.getEnvelope(); SOAPBody soapBody = soapEnvelop.getBody(); Document preDoc = soapBody.extractContentAsDocument(); } catch (SOAPException e1) { e1.printStackTrace(); } returnnull; }
如果DOMSource本身持有的Document对象就是一个SOAPPart的话就可以直接发送了,不需要再做转换了。当我们的DOMSource持有的不是一个SOAPPart时,系统在生成SOAPPart时很可能会抛出异常信息:HIERARCHY_REQUEST_ERR: 尝试在不允许的位置插入节点。所以当我们配合使用SOAPBinding、DOMSource消息对象和MESSAGE模式时,我们最好给DOMSource传入一个SOAPPart对象或者是SOAPPart格式的Document对象。
PAYLOAD模式
使用PAYLOAD模式时,我们发送的DOMSource消息会直接发送过去。对方接收到的内容和发送时的内容是一样的,注意只是内容是一样的,其持有的Document对象还是会当做一个普通的Document对象处理,如DocumentImpl。比如发送的时候DOMSource持有的是一个SOAPPart,那么接收的时候接收到的DOMSource里面的Document的内容还是发送时SOAPPart的内容,但是对象却是一个普通的Document对象,而不是发送时的SOAPPart对象;而如果发送的时候发送的是一个普通的Document对象,那么接收到的内容也只是一个普通Document的内容,不会像MESSAGE模式那样会有多余的SOAPHeader等信息。
相关推荐
本教程“5天学会jaxws-webservice编程”旨在帮助你快速掌握JAX-WS的核心概念和技术,以便在短短五天内能够熟练地运用到实际项目中。 首先,我们需要了解Web服务的基础。Web服务基于开放标准,如SOAP(Simple Object...
JAX-WS提供了基于注解的编程模型,使得Web服务的开发更加简洁,无需手动编写复杂的XML配置文件。 2. **SOAP**:SOAP是一种基于XML的协议,用于交换结构化和类型化的信息。在Web服务中,SOAP消息作为HTTP请求的主体...
1. **基于注解的编程模型**:JAX-WS 2.2引入了更多的注解,如`@WebService`、`@WebMethod`、`@SOAPBinding`等,使得开发人员能够更方便地声明服务接口和实现。这些注解允许在Java类上直接定义Web服务行为,减少了XML...
1. **JAX-WS基础**:JAX-WS基于SOAP协议,通过XML来定义服务接口和消息交换格式。它通过注解如`@WebService`、`@WebMethod`等,将Java类声明为Web服务的端点。客户端则可以通过WSDL(Web Service Description ...
这个包主要用于实现基于SOAP(Simple Object Access Protocol)的Web服务,提供了一套编程模型和框架,使得开发者能够方便地创建、部署和消费Web服务。 **核心知识点:** 1. **JAX-WS**: JAX-WS是Java平台的一个...
- 规范详细描述了如何使用Java开发基于XML的Web服务,包括但不限于服务端和客户端的编程模型、SOAP消息处理、安全性和事务处理等方面。 - 规范还涵盖了如何使用JAX-WS与现有的Java EE环境集成,如EJB和JPA。 4. *...
这个版本号为2.1-1的jar包提供了开发和运行基于SOAP(Simple Object Access Protocol)的Web服务所需的核心接口和类。在Java EE环境中,JAX-WS是标准的一部分,用于创建和消费Web服务,它简化了客户端和服务端的交互...
这个API使得开发人员可以通过使用Java接口来定义服务端点,从而简化了Web服务的编程模型。JAX-WS还支持WS-I Basic Profile,确保跨平台的互操作性。`jaxws-api-2.1.jar`包含了JAX-WS的核心API,使得开发者能够利用...
WebServices服务接口调用是分布式系统中常见的技术手段,它允许不同系统...这涉及到对SOAP消息结构、WSDL规范、JAX-WS API以及Java网络编程的深入理解。熟练运用这些知识,可以构建出高效、灵活的跨平台通信解决方案。
Spring框架以其强大的依赖注入和面向切面编程能力,与CXF结合可以提供一种优雅的方式实现Web服务。本文将详细讲解如何基于Spring注解来利用CXF实现Web服务。 首先,我们需要理解Spring注解的基本概念。Spring注解是...
在IT行业中,Spring框架是Java开发中的一个核心组件,它提供了强大的依赖注入、AOP(面向切面编程)以及各种企业级服务。JAX-WS(Java API for XML Web Services)则是用于创建和消费Web服务的标准,它允许开发者...
WebService,另一方面,是一种基于XML的协议,它定义了一种在不同操作系统、编程语言之间交换数据的标准。Web服务通常通过SOAP(Simple Object Access Protocol)协议进行通信,使用WSDL(Web Services Description ...
它提供了从Java接口到SOAP消息的绑定,简化了Web服务的开发过程。JAX-WS通过注解和WSDL(Web服务描述语言)使得服务定义和实现更加直观。 **Spring框架与Web Service** Spring框架以其强大的依赖注入和面向切面编程...
它们允许不同系统之间的数据交换,跨越不同的操作系统和编程语言。 2. **JAX-WS和JAX-RS**:JAX-WS是Java API for XML Web Services的缩写,用于构建SOAP Web服务。JAX-RS,即Java API for RESTful Web Services,...
总的来说,基于JAX-WS的Web服务开发涉及Java编程、SOAP协议、WSDL描述、服务部署和客户端调用等多个方面,是Java开发者掌握Web服务技术的重要一环。通过学习和实践,开发者可以更好地理解和利用这一强大工具来构建...
SOAP是一种基于XML的消息传递协议,用于在不同系统间交换结构化和类型化的信息。JAXWS提供了一套工具和API,使得开发人员可以轻松地创建SOAP服务端点(Endpoint)和客户端代理(Proxy),支持WSDL(Web Services ...
3. **jbossws-jaxrpc.jar**:这是基于Java API for XML Processing (JAX-RPC)的实现,它提供了一种编程模型,允许Java开发者创建和使用SOAP Web服务。虽然JAX-RPC是JAX-WS的前身,但在某些场景下,它仍然被用作兼容...
【描述】中提到,该示例使用了Spring 4.1.6版本,这是一个稳定且广泛使用的版本,提供了强大的依赖注入、AOP(面向切面编程)以及全面的IoC( inversion of control,控制反转)支持。同时,它采用了CXF 3.0.8版本,...
`jaxws:server`标签是`cxf.xml`中的核心元素,它定义了一个基于JAX-WS的Web服务服务器。 在压缩包中的文件列表中,虽然没有列出具体文件内容,但可以假设包含了一些必要的配置文件,如`cxf.xml`,以及可能的Java源...
"webservices"标签表明内容聚焦于Web服务开发,这是一种基于开放标准的互联网通信方法,允许不同系统间的互操作性。"example code"则意味着这里有实际可运行的代码片段,有助于开发者亲手实践,加深对Web服务编程的...