CXF入門教程(3) -- webService客戶端開發步驟詳解

不淨之心發表於2013-04-06
原文參考:[url]http://blog.csdn.net/neareast/article/details/7724662[/url]
教程(2)依據教程(1)中提供的WSDL契約,對其釋出的webService建立了一個簡單的客戶端;本文詳細介紹一下webService客戶端開發的一般步驟。
[color=red][b]生成Stub程式碼[/b][/color]
在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。

[b]基本的 HelloWorld WSDL 契約[/b]
下面是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工具自動生成。

[b]生成 stub 程式碼[/b]
有了WSDL契約之後,我們可以用CXF的wsdl2java工具來生成客戶端程式碼。在命令列提示符下輸入類似下面這樣的命令:

[color=red]wsdl2java -ant -client -d D:/temp -p com.neareast.test.cxf.client.WSDL2Java -frontend jaxws21 hello_world.wsdl[/color]

最後的 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 例子中,所謂的型別就是請求和應答訊息的各種包裝器,其中一些型別會在非同步呼叫模式中用到。


[color=red][b]實現一個CXF客戶端[/b][/color]
這一章節描述瞭如何基於上述的WSDL契約,來寫一個簡單的Java客戶端。要實現客戶端,我們需要使用以下 stub 類:
[color=blue]Service class 服務類,(也就是SOAPService)。
Service endpoint interface 服務端點介面(也就是 Greeter)。[/color]

[b]生成的服務類[/b]
下面是生成名為的服務類的一個典型的輪廓,我們暫且叫它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_()方法的服務類。


[b]服務端點介面[/b]
對於原始的WSDL契約中定義的每一個埠型別,我們都可以生成對應的服務端點介面Java程式碼。一個服務端點介面就是一個WSDL埠型別的Java對映。原始WSDL埠型別中定義的每一個操作都對映為服務端點介面中對應的一個方法。操作的引數的對映規則如下:

輸入引數被對映為方法的引數。
第一個輸出引數被對映為一個返回值。
如果有多於一個的輸出引數,第二個以及其後的輸出引數對映為方法的引數(值得一提的是,這些引數的值必須使用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;
}



[b]客戶端 main 函式[/b]
下面是實現了簡單客戶端的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()函式的執行過程如下:

CXF執行時被隱式地初始化 —— 也就是說,假定CXF執行時類被載入了。因此,沒必要呼叫一個特殊的函式來初始化CXF。
客戶端有一個預期的字串引數,用來給出WSDL契約的地址;該地址被儲存在wsdlURL變數中。
為了能夠訪問遠端的服務端點,我們建立一個新的埠物件;下面的程式碼片段展示了建立該物件的兩個步驟:
SOAPService ss = new SOAPService(wsdlURL, SERVICE_NAME);
Greeter port = ss.getSoapPort();

要建立一個新的埠物件,我們先建立一個服務物件(傳入WSDL地址以及服務名這兩個引數),然後呼叫合適的getPortName() 方法來獲取我們需要的特定的埠的例項。本例中,SOAPService 服務僅支援SoapPort埠,也就是 Greeter型別。

接下來,客戶端呼叫Greeter服務端點介面支援的每一個方法。
呼叫pingMe() 操作的例項中,展示瞭如何捕獲PingMeFault 錯誤異常。
參考文件:[url]http://cxf.apache.org/docs/developing-a-consumer.html[/url]

相關文章