教程(2)依据教程(1)中提供的WSDL契约,对其发布的webService创建了一个简单的客户端;本文详细介绍一下webService客户端开发的一般步骤。
生成Stub代码
在CXF中,开发一个service消费者(或客户端)的起点是一个WSDL契约,连同端口类型、绑定以及service定义。然后我们就可以使用 wsdl2java 工具来根据WSDL契约生成Java stub 代码。stub代码提供了调用远端服务方法所需的支持代码。
wsdl2java 工具可以为CXF客户端生成下列代码:
- Stub code - 实现一个CXF客户端需要的支持文件。
- Client starting point code - 样例客户端代码,可以连到远程服务并调用每一个操作。
- Ant build file - 一个ant构建工具可以使用的 build.xml 文件,其中包含构建和运行样例程序的targets。
基本的 HelloWorld WSDL 契约
下面是HelloWorld WSDL 契约的示例。该契约定义了一个单一的端口类型,Greeter;一个SOAP绑定,Greater_SOAPBinding;以及一个服务,SOAPService,它有一个单独的端口,SoapPort。
<?xml version="1.0" encoding="UTF-8"?> <wsdl:deFinitions name="HelloWorld" targetNamespace="http://apache.org/hello_world_soap_http"
xmlns=//schemas.xmlsoap.org/wsdl/"
xmlns:soap=//schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns=//apache.org/hello_world_soap_http"
xmlns:x1="http://apache.org/hello_world_soap_http/types"
xmlns:wsdl= xmlns:xsd=//www.w3.org/2001/XMLSchema">
<wsdl:types> <schema targetNamespace=//apache.org/hello_world_soap_http/types"
xmlns=//www.w3.org/2001/XMLSchema"
xmlns:tns=//apache.org/hello_world_soap_http/types"
elementFormDefault="qualified"> <simpleType name="MyStringType"> <restriction base="string"> <maxLength value="30" /> </restriction> </simpleType> <element name="sayHi"> <complexType/> </element> <element name="sayHiResponse"> <complexType> <sequence> <element name="responseType" type="string"/> </sequence> </complexType> </element> <element name="greetMe"> <complexType> <sequence> <element name="requestType" type="tns:MyStringType"/> </sequence> </complexType> </element> <element name="greetMeResponse"> <complexType> <sequence> <element name="greetMeOneWay"> <complexType> <sequence> <element name="pingMe"> <complexType/> </element> <element name="pingMeResponse"> <complexType/> </element> <element name="faultDetail"> <complexType> <sequence> <element name="minor" type="short"/> <element name="major" type=short"/> </sequence> </complexType> </element> </schema> </wsdl:types> <wsdl:message name="sayHiRequest"> <wsdl:part element="x1:sayHi" name="in"/> </wsdl:message> <wsdl:message name="sayHiResponse"> <wsdl:part element="x1:sayHiResponse" name="out"/> </wsdl:message> <wsdl:message name="greetMeRequest"> <wsdl:part element="x1:greetMe" name="greetMeResponse"> <wsdl:part element="x1:greetMeResponse" name="greetMeOneWayRequest"> <wsdl:part element="x1:greetMeOneWay" name="pingMeRequest"> <wsdl:part name="in" element="x1:pingMe"/> </wsdl:message> <wsdl:message name="pingMeResponse"> <wsdl:part name="out" element="x1:pingMeResponse"/> </wsdl:message> <wsdl:message name="pingMeFault"> <wsdl:part name="faultDetail" element="x1:faultDetail"/> </wsdl:message> <wsdl:portType name="Greeter"> <wsdl:operation name="sayHi"> <wsdl:input message="tns:sayHiRequest" name="sayHiRequest"/> <wsdl:output message="tns:sayHiResponse" name="sayHiResponse"/> </wsdl:operation> <wsdl:operation name="greetMe"> <wsdl:input message="tns:greetMeRequest" name="greetMeRequest"/> <wsdl:output message="tns:greetMeResponse" name="greetMeResponse"/> </wsdl:operation> <wsdl:operation name="greetMeOneWay"> <wsdl:input message="tns:greetMeOneWayRequest" name="greetMeOneWayRequest"/> </wsdl:operation> <wsdl:operation name="pingMe"> <wsdl:input name="pingMeRequest" message="tns:pingMeRequest"/> <wsdl:output name="pingMeResponse" message="tns:pingMeResponse"/> <wsdl:fault name="pingMeFault" message="tns:pingMeFault"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="Greeter_SOAPBinding" type="tns:Greeter"> <soap:binding style="document" transport=//schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="sayHi"> <soap:operation soapAction="" style="document"/> <wsdl:input name="sayHiRequest"> <soap:body use="literal"/> </wsdl:input> <wsdl:output name="sayHiResponse"> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> <wsdl:operation name="greetMe"> <soap:operation soapAction="greetMeRequest"> <soap:body use="greetMeResponse"> <soap:body use="greetMeOneWay"> <soap:operation soapAction="greetMeOneWayRequest"> <soap:body use="literal"/> </wsdl:input> </wsdl:operation> <wsdl:operation name="pingMe"> <soap:operation style="document"/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> <wsdl:fault name="pingMeFault"> <soap:fault name="pingMeFault" use="literal"/> </wsdl:fault> </wsdl:operation> </wsdl:binding> <wsdl:service name="SOAPService"> <wsdl:port binding="tns:Greeter_SOAPBinding" name="SoapPort"> <soap:address location=//localhost:9000/SoapContext/SoapPort"/> </wsdl:port> </wsdl:service> </wsdl:deFinitions>
Greeter 端口类型定义了下列WSDL 操作:
- sayHi - 有一个输出参数,类型是 xsd:string。
- greetMe - 有一个 xsd:string 类型的输入参数,以及一个 xsd:string 类型的输出参数。
- greetMeOneWay - 有一个 xsd:string 类型的输入参数 。因为这个操作没有输出参数,CXF可以将其优化为一个单向的调用(也就是说,客户端不等待服务器的响应)。
- pingMe - 没有输入输出参数,但是可以引起一个错误异常。
该WSDL还为SOAP协议定义了一个绑定。实际上,这个绑定通常是自动生成的 —— 例如,通过执行CXF wsdl2soap 或 wsdl2xml 工具。同样,SOAPService 可以通过执行CXF的wsdl2service工具自动生成。
生成 stub 代码
有了WSDL契约之后,我们可以用CXF的wsdl2java工具来生成客户端代码。在命令行提示符下输入类似下面这样的命令:
wsdl2java -ant -client -d D:/temp -p com.neareast.test.cxf.client.WSDL2Java -frontend jaxws21 hello_world.wsdl
最后的 hello_world.wsdl 是一个包含上述WSDL契约的文件,(也可以指定一个服务的URL地址)。其他参数均为常用的可选参数,各参数的作用为:
- -ant 指定要生成一个ant的构建说明文档build.xml。
- -client 指定要生成一个测试客户端的“起点代码”(starting point code),其中包含了端口中所有方法的测试样例代码。
- -d 指定我们想要把生成的文件放到哪个目录下,默认是wsdl2Java命令所在的目录。
- -p 指定生成文件的包名;默认是根据WSDL文件中的命名空间相对应。
- -frontend 指定前端类型及版本;目前仅支持 JAXWS 前端,用 "jaxws21" 表明要生成JAX-WS 2.1 兼容的代码(Jre6自带的就是这个版本),使用该参数的原因请参考教程(2)。
如果没有用-p参数指定包名,上面的命令会生成下面两个包:
-
org.apache.hello_world_soap_http
这个包是根据 http://apache.org/hello_world_soap_http 目标命名空间生成的。该命名空间下的所有WSDL实体(例如 Greeter 端口类型和 SOAPService 服务)都被映射到相应的Java包中。 -
org.apache.hello_world_soap_http.types
这个包是根据 http://apache.org/hello_world_soap_http/types 目标命名空间生成的。该命名空间下的所有XML类型(也就是HelloWorld契约下wsdl:types元素中定义一切) 都被映射到相应的Java包中。
wsdl2java 命令生成的 stub 文件分成下列类型:
- 代表WSDL实体的类 (在 org.apache.hello_world_soap_http 包中):
- 代表XML类型的类 (在 org.apache.hello_world_soap_http.types 包中) - 在 HelloWorld 例子中,所谓的类型就是请求和应答消息的各种包装器,其中一些类型会在异步调用模式中用到。
实现一个CXF客户端
这一章节描述了如何基于上述的WSDL契约,来写一个简单的Java客户端。要实现客户端,我们需要使用以下 stub 类:
- Service class 服务类,(也就是SOAPService)。
- Service endpoint interface 服务端点接口(也就是 Greeter)。
生成的服务类
下面是生成名为的服务类的一个典型的轮廓,我们暂且叫它ServiceName类,可以看到它扩展了javax.xml.ws.Service 基类。
public class ServiceName extends javax.xml.ws.Service { ... public ServiceName(URL wsdlLocation,QName serviceName) { } public ServiceName() { } public Greeter getPortName() { } . . . }
- 构造方法 - 有如下两种形式的构造方法:
- get_PortName_() 方法- 针对ServiceName 服务中定义的每一个PortName端口,CXF都会生成对应的get_PortName_()方法。所以,一个定义了多个端口的wsdl:service 元素会生成一个包含多个get_PortName_()方法的服务类。
服务端点接口
对于原始的WSDL契约中定义的每一个端口类型,我们都可以生成对应的服务端点接口Java代码。一个服务端点接口就是一个WSDL端口类型的Java映射。原始WSDL端口类型中定义的每一个操作都映射为服务端点接口中对应的一个方法。操作的参数的映射规则如下:
例如,下面展示的是Greeter服务端点接口,它是由前面的WSDL契约中定义的Greeter端口类型生成的。为简单起见,下面的例子省略了标准的JAXB 及 JAX-WS 注解。
客户端 main 函数
下面是实现了简单客户端的Java代码。简要地说,客户端连接到SOAPService 服务的SoapPort 端口,然后调用Greeter 端口类型支持的每一个操作。