CXF入門教程(5) -- webService非同步呼叫模式

不淨之心發表於2013-04-07
原文參考: [url]http://blog.csdn.net/neareast/article/details/7726503[/url]
除了教程(3)中介紹的常見的同步呼叫模式,CXF還支援如下兩種形式的非同步呼叫模式:

輪詢方法(Polling approach) - 這種情況下呼叫遠端方法,我們可以呼叫一個特殊的方法;該方法沒有輸出引數,但是返回一個 javax.xml.ws.Response 例項。可以輪詢該 Response 物件(繼承自 javax.util.concurrency.Future 介面)來檢查是否有應答訊息到達。
回撥方法(Callback approach) -這種情況下呼叫遠端方法,我們呼叫另外一個特殊的方法:該方法使用一個回撥物件(javax.xml.ws.AsyncHandler型別)的引用作為一個引數。只要有應答訊息到達客戶端,CXF執行時就會回撥該 AsyncHandler 物件,並將應答訊息的內容傳給它。
下面是兩種非同步呼叫的方法的描述和示例程式碼。

[color=red][b]非同步呼叫示例使用的契約[/b][/color]
下面展示的是非同步呼叫示例中使用的WSDL契約,為保證教程的連續性,本文使用的是前面教程(1)中生成的HelloWorld服務的WSDL契約。
<?xml version="1.0" ?>  
<wsdl:definitions name="HelloWorld"
targetNamespace="http://service.server.cxf.test.neareast.com/"
xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://service.server.cxf.test.neareast.com/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<xs:schema attributeFormDefault="unqualified"
elementFormDefault="unqualified" targetNamespace="http://service.server.cxf.test.neareast.com/"
xmlns:tns="http://service.server.cxf.test.neareast.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="IntegerUserMap" type="tns:IntegerUserMap"></xs:element>
<xs:complexType name="User">
<xs:sequence>
<xs:element minOccurs="0" name="name" type="xs:string"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="IntegerUserMap">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="entry"
type="tns:IdentifiedUser"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="IdentifiedUser">
<xs:sequence>
<xs:element name="id" type="xs:int"></xs:element>
<xs:element minOccurs="0" name="user" type="tns:User"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="sayHi" type="tns:sayHi"></xs:element>
<xs:complexType name="sayHi">
<xs:sequence>
<xs:element minOccurs="0" name="text" type="xs:string"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="sayHiResponse" type="tns:sayHiResponse"></xs:element>
<xs:complexType name="sayHiResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="xs:string"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="sayHiToUser" type="tns:sayHiToUser"></xs:element>
<xs:complexType name="sayHiToUser">
<xs:sequence>
<xs:element minOccurs="0" name="arg0" type="tns:User"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="sayHiToUserResponse" type="tns:sayHiToUserResponse"></xs:element>
<xs:complexType name="sayHiToUserResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="xs:string"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="getUsers" type="tns:getUsers"></xs:element>
<xs:complexType name="getUsers">
<xs:sequence></xs:sequence>
</xs:complexType>
<xs:element name="getUsersResponse" type="tns:getUsersResponse"></xs:element>
<xs:complexType name="getUsersResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="tns:IntegerUserMap"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="getUsers">
<wsdl:part element="tns:getUsers" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="sayHi">
<wsdl:part element="tns:sayHi" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="sayHiToUser">
<wsdl:part element="tns:sayHiToUser" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="sayHiToUserResponse">
<wsdl:part element="tns:sayHiToUserResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="sayHiResponse">
<wsdl:part element="tns:sayHiResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="getUsersResponse">
<wsdl:part element="tns:getUsersResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:portType name="iHelloWorld">
<wsdl:operation name="sayHi">
<wsdl:input message="tns:sayHi" name="sayHi">
</wsdl:input>
<wsdl:output message="tns:sayHiResponse" name="sayHiResponse">
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="sayHiToUser">
<wsdl:input message="tns:sayHiToUser" name="sayHiToUser">
</wsdl:input>
<wsdl:output message="tns:sayHiToUserResponse" name="sayHiToUserResponse">
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getUsers">
<wsdl:input message="tns:getUsers" name="getUsers">
</wsdl:input>
<wsdl:output message="tns:getUsersResponse" name="getUsersResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="HelloWorldSoapBinding" type="tns:iHelloWorld">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"></soap:binding>
<wsdl:operation name="sayHi">
<soap:operation soapAction="" style="document"></soap:operation>
<wsdl:input name="sayHi">
<soap:body use="literal"></soap:body>
</wsdl:input>
<wsdl:output name="sayHiResponse">
<soap:body use="literal"></soap:body>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="sayHiToUser">
<soap:operation soapAction="" style="document"></soap:operation>
<wsdl:input name="sayHiToUser">
<soap:body use="literal"></soap:body>
</wsdl:input>
<wsdl:output name="sayHiToUserResponse">
<soap:body use="literal"></soap:body>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getUsers">
<soap:operation soapAction="" style="document"></soap:operation>
<wsdl:input name="getUsers">
<soap:body use="literal"></soap:body>
</wsdl:input>
<wsdl:output name="getUsersResponse">
<soap:body use="literal"></soap:body>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HelloWorld">
<wsdl:port binding="tns:HelloWorldSoapBinding" name="HelloWorldImplPort">
<soap:address location="http://localhost:9000/helloWorld"></soap:address>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>


[color=red][b]生成非同步 stub 程式碼[/b][/color]
非同步呼叫需要額外的stub程式碼(例如,服務端點介面中定義的專用的非同步方法)。然而,這些特殊的stub程式碼不是預設生成的。要想開啟非同步特性,並生成必不可少的stub程式碼,我們必須使用WSDL 2.0規範的自定義對映特性。

自定義使我們能夠改變 wsdl2java 工具生成stub程式碼的方式。特別地,它允許我們修改WSDL到Java的對映,並開啟某些特性。在這裡,自定義的作用是開啟非同步呼叫特性。自定義是用一個繫結宣告規定的,該宣告是我們用一個 jaxws:bindings 標籤(jaxws 字首繫結到 http://java.sun.com/xml/ns/jaxws 名稱空間)定義的。指定一個繫結宣告有兩種可選的方式:

外部繫結宣告 - jaxws:bindings 元素被定義在WSDL契約之外的一個單獨的檔案。生成stub程式碼的時候,我們需要對wsdl2java 工具指定繫結宣告檔案的位置。
嵌入式繫結宣告 - 我們也可以直接把jaxws:bindings 元素嵌入到 WSDL 契約中,把它當做WSDL的擴充套件。在這種情況下,jaxws:bindings 的設定僅對直接的父元素起作用。
本文只考慮第一種方法,即外部繫結宣告。一個開啟了非同步呼叫開關的繫結宣告檔案的模版如下所示:
<bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"  
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
wsdlLocation="http://localhost:9000/helloWorld?wsdl"
xmlns="http://java.sun.com/xml/ns/jaxws">
<bindings node="wsdl:definitions">
<enableAsyncMapping>true</enableAsyncMapping>
</bindings>
</bindings>

其中的wsdlLocation指定了該繫結宣告影響的WSDL檔案的位置,可以是本地檔案或一個URL。node節點是一個XPath 值,指定該繫結宣告影響所影響的WSDL契約中的節點。 此處把node設為“wsdl:definitions”,表示我們希望對整個WSDL契約起作用。{jaxws:enableAsyncMapping}} 元素設定為true,用來使能非同步呼叫特性。

如果我們只想對一個埠“iHelloWorld”生成非同步方法,我們可以在前面的繫結宣告中指定<bindings node="wsdl:definitions/wsdl:portType[@name='iHelloWorld']"> 。

接下來我們就可以使用wsdl2java命令來生成相應的帶非同步支援的stub程式碼了。為簡單起見,假設繫結宣告檔案儲存在本地檔案async_binding.xml中,我們可以使用類似下面的命令:
[color=blue]wsdl2java -b async_binding.xml hello_world.wsdl[/color]

其中-b 選項用來指定繫結宣告檔案。通過這種方法生成stub程式碼之後,HelloWorld的服務端點介面定義如下:
import java.util.concurrent.Future;  
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.Response;
import javax.xml.ws.ResponseWrapper;


@WebService(targetNamespace = "http://service.server.cxf.test.neareast.com/", name = "iHelloWorld")
@XmlSeeAlso({ObjectFactory.class})
public interface IHelloWorld {

@RequestWrapper(localName = "sayHi", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHi")
@ResponseWrapper(localName = "sayHiResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiResponse")
@WebMethod(operationName = "sayHi")
public Response<com.neareast.test.cxf.asyClient.WSDL2Java.SayHiResponse> sayHiAsync(
@WebParam(name = "text", targetNamespace = "")
java.lang.String text
);

@RequestWrapper(localName = "sayHi", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHi")
@ResponseWrapper(localName = "sayHiResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiResponse")
@WebMethod(operationName = "sayHi")
public Future<?> sayHiAsync(
@WebParam(name = "text", targetNamespace = "")
java.lang.String text,
@WebParam(name = "asyncHandler", targetNamespace = "")
AsyncHandler<com.neareast.test.cxf.asyClient.WSDL2Java.SayHiResponse> asyncHandler
);

@WebResult(name = "return", targetNamespace = "")
@RequestWrapper(localName = "sayHi", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHi")
@WebMethod
@ResponseWrapper(localName = "sayHiResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiResponse")
public java.lang.String sayHi(
@WebParam(name = "text", targetNamespace = "")
java.lang.String text
);

@RequestWrapper(localName = "sayHiToUser", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUser")
@ResponseWrapper(localName = "sayHiToUserResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUserResponse")
@WebMethod(operationName = "sayHiToUser")
public Response<com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUserResponse> sayHiToUserAsync(
@WebParam(name = "arg0", targetNamespace = "")
com.neareast.test.cxf.asyClient.WSDL2Java.User arg0
);

@RequestWrapper(localName = "sayHiToUser", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUser")
@ResponseWrapper(localName = "sayHiToUserResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUserResponse")
@WebMethod(operationName = "sayHiToUser")
public Future<?> sayHiToUserAsync(
@WebParam(name = "arg0", targetNamespace = "")
com.neareast.test.cxf.asyClient.WSDL2Java.User arg0,
@WebParam(name = "asyncHandler", targetNamespace = "")
AsyncHandler<com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUserResponse> asyncHandler
);

@WebResult(name = "return", targetNamespace = "")
@RequestWrapper(localName = "sayHiToUser", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUser")
@WebMethod
@ResponseWrapper(localName = "sayHiToUserResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUserResponse")
public java.lang.String sayHiToUser(
@WebParam(name = "arg0", targetNamespace = "")
com.neareast.test.cxf.asyClient.WSDL2Java.User arg0
);

@RequestWrapper(localName = "getUsers", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.GetUsers")
@ResponseWrapper(localName = "getUsersResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.GetUsersResponse")
@WebMethod(operationName = "getUsers")
public Response<com.neareast.test.cxf.asyClient.WSDL2Java.GetUsersResponse> getUsersAsync();

@RequestWrapper(localName = "getUsers", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.GetUsers")
@ResponseWrapper(localName = "getUsersResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.GetUsersResponse")
@WebMethod(operationName = "getUsers")
public Future<?> getUsersAsync(
@WebParam(name = "asyncHandler", targetNamespace = "")
AsyncHandler<com.neareast.test.cxf.asyClient.WSDL2Java.GetUsersResponse> asyncHandler
);

@WebResult(name = "return", targetNamespace = "")
@RequestWrapper(localName = "getUsers", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.GetUsers")
@WebMethod
@ResponseWrapper(localName = "getUsersResponse", targetNamespace = "http://service.server.cxf.test.neareast.com/", className = "com.neareast.test.cxf.asyClient.WSDL2Java.GetUsersResponse")
public com.neareast.test.cxf.asyClient.WSDL2Java.IntegerUserMap getUsers();
}

除了原來的同步方法(如sayHi方法),sayHi操作的兩個非同步呼叫方法也被同時生成了:
返回值型別為Future<?>,有一個型別為javax.xml.ws.AsyncHandler的額外引數的sayHiAsync()方法 —— 該方法可用於非同步呼叫的回撥方式。
返回值型別為Response<GreetMeSometimeResponse>的sayHiAsync()方法 —— 該方法可用於非同步呼叫的輪詢方式。


回撥方式和輪詢方式的細節將在下面的章節討論。為體現非同步呼叫的特點,筆者修改了教程(1)中Helloworld服務的部分實現,在sayHiToUser()方法中加入了3秒鐘的休眠,並增強了程式碼的魯棒性,改動如下:
public String sayHiToUser(User user) {  
String retVal = null;
if(null == user){
retVal = "Error: user object null !";
}else{
try{
System.out.println("sleep for 3 seconds before return");
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}

System.out.println("sayHiToUser called by: " + user.getName());
users.put(users.size() + 1, user);
retVal = "Hello " + user.getName();
}
return retVal;
}


[color=red][b]實現一個輪詢方式的非同步呼叫客戶端[/b][/color]
下面的程式碼演示了非同步傳送操作呼叫的輪詢方式的實現。客戶端是通過特殊的Java方法 _OperationName_Async(本例為sayHiAsync()方法)來呼叫這個操作的,該方法返回一個javax.xml.ws.Response<T> 物件,其中“T”是這個操作的響應訊息的型別(本例中為SayHiResponse型別)。我們可以稍後通過輪詢Response<T> 物件來檢查該操作的響應訊息是否已經到達。

[java] view plaincopyprint?
package com.neareast.test.cxf.asyClient.consumer;  

import java.util.concurrent.ExecutionException;

import javax.xml.ws.Response;

import com.neareast.test.cxf.asyClient.WSDL2Java.HelloWorld;
import com.neareast.test.cxf.asyClient.WSDL2Java.IHelloWorld;
import com.neareast.test.cxf.asyClient.WSDL2Java.SayHiResponse;

public class BasicClientPolling {
public static void main(String[] args) throws InterruptedException{
HelloWorld server = new HelloWorld();
IHelloWorld hello = server.getHelloWorldImplPort();

Response<SayHiResponse> sayHiResponseResp = hello.sayHiAsync(System.getProperty("user.name"));
while (!sayHiResponseResp.isDone()) {
Thread.sleep(100);
}

try {
SayHiResponse reply = sayHiResponseResp.get();
System.out.println( reply.getReturn() );
} catch (ExecutionException e) {
e.printStackTrace();
}

}
}

sayHiAsync()方法呼叫了sayHi操作,將輸入引數傳送到遠端的服務,並返回javax.xml.ws.Response<SayHiResponse> 物件的一個引用。Response 類實現了標準的 java.util.concurrency.Future<T> 介面,該類設計用來輪詢一個併發執行緒執行的任務的產出結果。本質上來說,使用Response物件來輪詢有兩種基本方法:

[b]Non-blocking polling(非阻塞輪詢)[/b] - 嘗試獲得結果之前,呼叫非阻塞方法Response<T>.isDone()來檢查響應訊息是否到達,例如:
<pre name="code" class="java">  User u = new User();  

//非阻塞式輪詢
u.setName(System.getProperty("user.name"));
Response<SayHiToUserResponse> sayHiToUserResponseResp = hello.sayHiToUserAsync(u);
while (!sayHiToUserResponseResp.isDone()) {
Thread.sleep(100);
}

try {
//如果沒有前面isDone的檢測,此處就退化為阻塞式輪詢
SayHiToUserResponse reply = sayHiToUserResponseResp.get();
System.out.println( reply.getReturn() );
} catch (ExecutionException e) {
e.printStackTrace();
}</pre><br>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>
<pre></pre>


[b]Blocking polling(阻塞輪詢)[/b] - 立即呼叫Response<T>.get(),阻塞至響應到達(可以指定一個超時時長作為可選項)。例如,輪詢一個響應,超時時長為60s:
//阻塞式輪詢  
u.setName("NearEast");
sayHiToUserResponseResp = hello.sayHiToUserAsync(u);
try {
SayHiToUserResponse reply = sayHiToUserResponseResp.get(5L,java.util.concurrent.TimeUnit.SECONDS);
System.out.println( reply.getReturn() );
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}


[color=red][b]實現一個回撥方式的非同步呼叫客戶端[/b][/color]
發起非同步操作呼叫的另一個可選方法是實現javax.xml.ws.AsyncHandler介面,派生出一個回撥類。回撥類必須實現 handleResponse() 方法,CXF執行時呼叫這個類將響應的到達通知給客戶端。下面的程式碼給出了我們需要實現的 AsyncHandler 介面的輪廓。
The javax.xml.ws.AsyncHandler Interface
package javax.xml.ws;

public interface AsyncHandler<T>
{
void handleResponse(Response<T> res);
}

本例使用一個測試用的回撥類 SayHiToUserAsyHandler,程式碼如下:
package com.neareast.test.cxf.asyClient.consumer;  

import java.util.concurrent.ExecutionException;

import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

import com.neareast.test.cxf.asyClient.WSDL2Java.SayHiToUserResponse;

public class SayHiToUserAsyHandler implements AsyncHandler<SayHiToUserResponse> {
SayHiToUserResponse reply = null;

@Override
public void handleResponse(Response<SayHiToUserResponse> res) {
try {
reply = res.get();
System.out.println( reply.getReturn() );
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public String getResponseText(){
return reply.getReturn();
}

}

上述 handleResponse() 的實現只是簡單地獲取響應資料,並把它存放到成員變數reply中。額外的getResponseText() 方法是為了方便地從響應中提煉出主要的輸出引數。

下面的程式碼演示了發起非同步操作呼叫的回撥方法。客戶端通過特定的Java方法 _OperationName_Async()來呼叫相應的操作,該方法使用一個額外的AsyncHandler<T>型別的引數,並返回一個 java.util.concurrency.Future<?> 物件。
package com.neareast.test.cxf.asyClient.consumer;  

import java.util.concurrent.Future;

import com.neareast.test.cxf.asyClient.WSDL2Java.HelloWorld;
import com.neareast.test.cxf.asyClient.WSDL2Java.IHelloWorld;
import com.neareast.test.cxf.asyClient.WSDL2Java.User;

public class BasicCallbackClient {
public static void main(String[] args) throws InterruptedException{
HelloWorld server = new HelloWorld();
IHelloWorld hello = server.getHelloWorldImplPort();

User u = new User();
//非阻塞式輪詢
u.setName(System.getProperty("user.name"));
SayHiToUserAsyHandler asyHandler = new SayHiToUserAsyHandler();
Future<?> res = hello.sayHiToUserAsync(u, asyHandler);
while (!res.isDone()) {
Thread.sleep(100);
}

String reply = asyHandler.getResponseText();
System.out.println( reply );

}
}

sayHiToUserAsync()方法返回的 Future<?> 物件只是用來檢測一個響應是否已經到達的 —— 例如,通過呼叫response.isDone()來輪詢。響應訊息的值只在回撥物件SayHiToUserAsyHandler 中可得。

本文配套的完整程式碼已經上傳,包括用到的wsdl契約檔案和繫結宣告檔案;本文涉及的非同步呼叫客戶端的程式碼放在com.neareast.test.cxf.asyClient包下,歡迎下載:[url]http://download.csdn.net/detail/neareast/4421250[/url]。

本文參考自:[url]http://cxf.apache.org/docs/developing-a-consumer.html[/url]

相關文章