CXF入門教程(5) -- webService非同步呼叫模式
除了教程(3)中介紹的常見的同步呼叫模式,CXF還支援如下兩種形式的非同步呼叫模式:
- 輪詢方法(Polling approach) - 這種情況下呼叫遠端方法,我們可以呼叫一個特殊的方法;該方法沒有輸出引數,但是返回一個 javax.xml.ws.Response 例項。可以輪詢該 Response 物件(繼承自 javax.util.concurrency.Future 介面)來檢查是否有應答訊息到達。
- 回撥方法(Callback approach) -這種情況下呼叫遠端方法,我們呼叫另外一個特殊的方法:該方法使用一個回撥物件(javax.xml.ws.AsyncHandler型別)的引用作為一個引數。只要有應答訊息到達客戶端,CXF執行時就會回撥該 AsyncHandler
下面是兩種非同步呼叫的方法的描述和示例程式碼。
非同步呼叫示例使用的契約
下面展示的是非同步呼叫示例中使用的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>
生成非同步 stub 程式碼
非同步呼叫需要額外的stub程式碼(例如,服務端點介面中定義的專用的非同步方法)。然而,這些特殊的stub程式碼不是預設生成的。要想開啟非同步特性,並生成必不可少的stub程式碼,我們必須使用WSDL 2.0規範的自定義對映特性。
自定義使我們能夠改變 wsdl2java 工具生成stub程式碼的方式。特別地,它允許我們修改WSDL到Java的對映,並開啟某些特性。在這裡,自定義的作用是開啟非同步呼叫特性。自定義是用一個繫結宣告規定的,該宣告是我們用一個 jaxws:bindings 標籤(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中,我們可以使用類似下面的命令:
wsdl2java -b async_binding.xml hello_world.wsdl
其中-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;
}
實現一個輪詢方式的非同步呼叫客戶端
下面的程式碼演示了非同步傳送操作呼叫的輪詢方式的實現。客戶端是通過特殊的Java方法 _OperationName_Async(本例為sayHiAsync()方法)來呼叫這個操作的,該方法返回一個javax.xml.ws.Response<T> 物件,其中“T”是這個操作的響應訊息的型別(本例中為SayHiResponse型別)。我們可以稍後通過輪詢Response<T> 物件來檢查該操作的響應訊息是否已經到達。
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物件來輪詢有兩種基本方法:
- Non-blocking polling(非阻塞輪詢) - 嘗試獲得結果之前,呼叫非阻塞方法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>
- Blocking polling(阻塞輪詢) - 立即呼叫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(); }
實現一個回撥方式的非同步呼叫客戶端
發起非同步操作呼叫的另一個可選方法是實現javax.xml.ws.AsyncHandler介面,派生出一個回撥類。回撥類必須實現 handleResponse() 方法,CXF執行時呼叫這個類將響應的到達通知給客戶端。下面的程式碼給出了我們需要實現的 AsyncHandler 介面的輪廓。
The javax.xml.ws.AsyncHandler Interfacepackage 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包下,歡迎下載:http://download.csdn.net/detail/neareast/4421250。
本文參考自:http://cxf.apache.org/docs/developing-a-consumer.html
轉載自NearEast