CXF入门教程(3) -- webService客户端开发步骤详解

教程(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。

HelloWorld WSDL Contract
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="HelloWorld" targetNamespace="http://apache.org/hello_world_soap_http"
 
    xmlns="http://schemas.xmlsoap.org/wsdl/"
 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 
    xmlns:tns="http://apache.org/hello_world_soap_http"

     xmlns:x1="http://apache.org/hello_world_soap_http/types"

     xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <wsdl:types>
        <schema targetNamespace="http://apache.org/hello_world_soap_http/types"
 
           xmlns="http://www.w3.org/2001/XMLSchema"

	    xmlns:tns="http://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="responseType" type="string"/>
                    </sequence>
                </complexType>
            </element>
            <element name="greetMeOneWay">
                <complexType>
                    <sequence>
                        <element name="requestType" type="string"/>
                    </sequence>
                </complexType>
            </element>
            <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="in"/>
    </wsdl:message>
    <wsdl:message name="greetMeResponse">
        <wsdl:part element="x1:greetMeResponse" name="out"/>
    </wsdl:message>
    <wsdl:message name="greetMeOneWayRequest">
        <wsdl:part element="x1:greetMeOneWay" name="in"/>
    </wsdl:message>
    <wsdl:message 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="http://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="" style="document"/>
            <wsdl:input name="greetMeRequest">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="greetMeResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
        
        <wsdl:operation name="greetMeOneWay">
            <soap:operation soapAction="" style="document"/>
            <wsdl:input name="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="http://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 包中):
    • Greeter 一个可以表示Greater WSDL端口类型的Java接口。在 JAX-WS 术语中,这个Java接口被称为一个服务端点接口(service endpoint interface),简称SEI。
    • SOAPService 一个代表WSDL service元素的类 SOAPService。
    • PingMeFault 一个Java异常类(扩展了java.lang.Exception类),代表WSDL fault 元素 pingMeFault。
  • 代表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 基类。

Outline of a Generated Service Class
public class ServiceName extends javax.xml.ws.Service
{
  ...
  public ServiceName(URL wsdlLocation, QName serviceName) { }
  
  public ServiceName() { }

  public Greeter getPortName() { }
  .
  .
  .
}

ServiceName 类定义了如下方法:

  • 构造方法 - 有如下两种形式的构造方法:
    • ServiceName(URL wsdlLocation, QName serviceName) 基于WSDL契约中serviceName 服务的数据,构造一个服务对象;这个WSDL契约是从wsdlLocation 获得的。
    • ServiceName() 这是默认的构造器,基于stub代码生成时(例如,运行CeltiXfire wsdl2java时)提供的服务名和WSDL契约来构造服务对象。使用这个构造器的前提是,原始位置的WSDL契约仍然可用。
  • get_PortName_() 方法针对ServiceName 服务中定义的每一个PortName端口,CXF都会生成对应的get_PortName_()方法。所以,一个定义了多个端口的wsdl:service 元素会生成一个包含多个get_PortName_()方法的服务类。

服务端点接口

对于原始的WSDL契约中定义的每一个端口类型,我们都可以生成对应的服务端点接口Java代码。一个服务端点接口就是一个WSDL端口类型的Java映射。原始WSDL端口类型中定义的每一个操作都映射为服务端点接口中对应的一个方法。操作的参数的映射规则如下: 

  1. 输入参数被映射为方法的参数。
  2. 第一个输出参数被映射为一个返回值。
  3. 如果有多于一个的输出参数,第二个以及其后的输出参数映射为方法的参数(值得一提的是,这些参数的值必须使用Holder类型进行传递)。

例如,下面展示的是Greeter服务端点接口,它是由前面的WSDL契约中定义的Greeter端口类型生成的。为简单起见,下面的例子省略了标准的JAXB 及 JAX-WS 注解。

The Greeter Service Endpoint Interface
/* Generated by WSDLToJava Compiler. */

package org.objectweb.hello_world_soap_http;
  ...
public interface Greeter
{
  public java.lang.String sayHi();
  
  public java.lang.String greetMe(java.lang.String requestType);

  public void greetMeOneWay(java.lang.String requestType);

  public void pingMe() throws PingMeFault;
}

客户端 main 函数

下面是实现了简单客户端的Java代码。简要地说,客户端连接到SOAPService 服务的SoapPort 端口,然后调用Greeter 端口类型支持的每一个操作。

Client Implementation Code
package demo.hw.client;

import java.io.File;
import java.net.URL;
import javax.xml.namespace.QName;
import org.apache.hello_world_soap_http.Greeter;
import org.apache.hello_world_soap_http.PingMeFault;
import org.apche.hello_world_soap_http.SOAPService;

public final class Client {

  private static final QName SERVICE_NAME = 
    new QName("http://apache.org/hello_world_soap_http", 
    "SOAPService");

  private Client()
  {
  }

  public static void main(String args[]) throws Exception
  {
    if (args.length == 0)
    {
      System.out.println("please specify wsdl");
      System.exit(1);
    }

    URL wsdlURL;
    File wsdlFile = new File(args[0]);
    if (wsdlFile.exists())
    {
      wsdlURL = wsdlFile.toURL();
    }
    else
    {
      wsdlURL = new URL(args[0]);
    }

    System.out.println(wsdlURL);
    SOAPService ss = new SOAPService(wsdlURL, SERVICE_NAME);
    Greeter port = ss.getSoapPort();
    String resp;

    System.out.println("Invoking sayHi...");
    resp = port.sayHi();
    System.out.println("Server responded with: " + resp);
    System.out.println();

    System.out.println("Invoking greetMe...");
    resp = port.greetMe(System.getProperty("user.name"));
    System.out.println("Server responded with: " + resp);
    System.out.println();

    System.out.println("Invoking greetMeOneWay...");
    port.greetMeOneWay(System.getProperty("user.name"));
    System.out.println("No response from server as method is OneWay");
    System.out.println();

    try {
      System.out.println("Invoking pingMe, expecting exception...");
      port.pingMe();
    } catch (PingMeFault ex) {
      System.out.println("Expected exception: PingMeFault has occurred.");
      System.out.println(ex.toString());
    }
    System.exit(0);
  }
} 

Client.main()函数的执行过程如下:

  1. CXF运行时被隐式地初始化 —— 也就是说,假定CXF运行时类被加载了。因此,没必要调用一个特殊的函数来初始化CXF。
  2. 客户端有一个预期的字符串参数,用来给出WSDL契约的地址;该地址被保存在wsdlURL变量中。
  3. 为了能够访问远程的服务端点,我们创建一个新的端口对象;下面的代码片段展示了创建该对象的两个步骤:

    SOAPService ss = new SOAPService(wsdlURL, SERVICE_NAME);
    Greeter port = ss.getSoapPort();

    要创建一个新的端口对象,我们先创建一个服务对象(传入WSDL地址以及服务名这两个参数),然后调用合适的getPortName() 方法来获取我们需要的特定的端口的实例。本例中,SOAPService 服务仅支持SoapPort端口,也就是 Greeter类型。

  4. 接下来,客户端调用Greeter服务端点接口支持的每一个方法。
  5. 调用pingMe() 操作的实例中,展示了如何捕获PingMeFault 错误异常。

参考文档:http://cxf.apache.org/docs/developing-a-consumer.html

你可能感兴趣的:(CXF入门教程(3) -- webService客户端开发步骤详解)