客戶端調(diào)用樁上的一個(gè)方法,而樁實(shí)現(xiàn)對(duì)該方法使用參數(shù),把Java方法調(diào)用變?yōu)閷⒒贖TTP發(fā)送給服務(wù)器的Web service請(qǐng)求。
客戶端調(diào)用樁,而樁把調(diào)用轉(zhuǎn)換為SOAP消息,然后樁使用RPC發(fā)送該消息。監(jiān)聽(tīng)服務(wù)器接收該SOAP消息,然后(十有八九)將其轉(zhuǎn)換為服務(wù)器處的一次方法調(diào)用。如果服務(wù)器是用Java編寫的,上述SOAP消息就會(huì)被轉(zhuǎn)換為Java調(diào)用。如果服務(wù)器是.Net服務(wù)器,調(diào)用將很可能是C# 或VB.Net對(duì)象。服務(wù)器的返回值被轉(zhuǎn)換回為SOAP消息,然后返回給樁,而樁會(huì)把返回的SOAP消息轉(zhuǎn)換為Java響應(yīng)。
wscompile工具生成了樁,而且它具有選項(xiàng)加載。對(duì)于此種討論,我們只需要關(guān)心運(yùn)行wscompile生成客戶端代碼的過(guò)程。對(duì)于wscompile來(lái)說(shuō),首要的輸入是配置文件(通常叫做config.xml)。wscompile讀取這個(gè)文件,用于指定一些信息,比如用于生成客戶端的WSDL文檔的位置。或者,如果我們準(zhǔn)備生成服務(wù)器,您也可以指定要用于服務(wù)器端生成的Java類名。在這種情況下,我們會(huì)生成客戶端代碼,所以配置文件應(yīng)該包含WSLD位置。
如果您計(jì)劃遵循這里開(kāi)發(fā)出來(lái)的代碼,您應(yīng)該創(chuàng)建一個(gè)目錄來(lái)包含要用到的所有部分,然后在該目錄中創(chuàng)建一個(gè)src、一個(gè)classes和一個(gè)etc目錄。src目錄將包含我們編寫的源代碼,classes目錄將包含編譯以后的代碼,而etc目錄將保存配置信息。我們將在etc目錄中創(chuàng)建config.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl location="http://soap.amazon.com/schemas3/AmazonWebServices.wsdl"
packageName="javapro.amazon" />
</configuration>
配置元素
文件包含什么內(nèi)容呢?首先,所有的元素都位于http://java.sun.com/xml/ns/jax-rpc/ri/config名稱空間中。配置元素始終是root元素,而且還有一個(gè)wsdl元素,它包含了兩個(gè)屬性:location和packageName。location屬性顯然是要讀取的WSDL文件所在的位置。wscompile使用packageName屬性作為用于保存所生成代碼的包的名稱。位置可以是本地文件或者URL.config.xml。
現(xiàn)在從命令行運(yùn)行wscompile,同時(shí)傳遞-help標(biāo)志(參見(jiàn)圖2)。正如您看到的那樣,有很多可用的標(biāo)志和特性?,F(xiàn)在我們關(guān)心的是-gen(或者-gen:client,二者是一樣的);-d,告訴wscompile在哪里存放編譯以后的文件;還有-s,是生成源代碼的位置。您還可以使用-verbose,它讓您知道wscompile正在做什么,以及-keep,它指定保存所生成的源文件。您還需要把配置文件作為一個(gè)參數(shù)傳遞給wscompile。
特性列表
wscompile 命令有很多可用的標(biāo)志和特性,您可以通過(guò)使用-help標(biāo)志來(lái)查看它們。與這里的討論有著密切關(guān)系的是-gen (或-gen:client)、-d和-s。
[client]$ wscompile.sh
-verbose -gen -d classes
-s src -keep etc/config.xml
這個(gè)參數(shù)會(huì)生成大量的輸出,詳細(xì)描述為Web service生成的代碼。wscompile工具生成源代碼并將其編譯為類。如果沒(méi)有傳遞-keep標(biāo)志,源代碼就會(huì)丟失。這里有著大量的代碼,但是我們只會(huì)對(duì)客戶端使用其中的一小部分。
現(xiàn)在從命令行運(yùn)行wscompile,同時(shí)傳遞-help標(biāo)志(參見(jiàn)圖2)。正如您看到的那樣,有很多可用的標(biāo)志和特性?,F(xiàn)在我們關(guān)心的是-gen(或者-gen:client,二者是一樣的);-d,告訴wscompile在哪里存放編譯以后的文件;還有-s,是生成源代碼的位置。您還可以使用-verbose,它讓您知道wscompile正在做什么,以及-keep,它指定保存所生成的源文件。您還需要把配置文件作為一個(gè)參數(shù)傳遞給wscompile。
[client]$ wscompile.sh
-verbose -gen -d classes
-s src -keep etc/config.xml
這個(gè)參數(shù)會(huì)生成大量的輸出,詳細(xì)描述為Web service生成的代碼。wscompile工具生成源代碼并將其編譯為類。如果沒(méi)有傳遞-keep標(biāo)志,源代碼就會(huì)丟失。這里有著大量的代碼,但是我們只會(huì)對(duì)客戶端使用其中的一小部分。
WSDL文檔包含一個(gè)服務(wù)元素。這個(gè)元素提供對(duì)Web service的最高級(jí)描述——實(shí)質(zhì)上就是它的名稱。(WSDL中)一個(gè)服務(wù)是端口的集合,而端口是對(duì)于如何調(diào)用服務(wù)的高級(jí)描述。特別地,端口包含Web service的地址。如果查看生成的類,您將看到兩個(gè)文件:AmazonSearchService.java和AmazonSearchService_Impl.java。這兩個(gè)文件分別是一個(gè)接口和一個(gè)實(shí)現(xiàn)類,代表了Amazon的WSLD中定義的服務(wù)。還有一個(gè)叫做AmazonSearchPort.java的接口,它對(duì)應(yīng)于WSDL端口定義。如果查看Port接口,您將看到它包含Amazon Web service所能執(zhí)行的所有操作的列表。樁類實(shí)現(xiàn)了這個(gè)服務(wù)接口。該樁叫做AmazonSearchPort_Stub.java,而且這是我們的客戶端將會(huì)使用的類(參見(jiàn)清單1)。
查找樁
清單1. 服務(wù)接口是由叫做AmazonSearchPort_Stub.java 的樁類實(shí)現(xiàn)的,Web service客戶端將會(huì)使用這個(gè)樁類。
public class AmazonSearchService_Impl extends
com.sun.xml.rpc.client.BasicService implements
AmazonSearchService {
private static final QName serviceName =
new QName("http://soap.amazon.com",
"AmazonSearchService");
private static final QName
ns1_AmazonSearchPort_QNAME = new QName(
"http://soap.amazon.com", "AmazonSearchPort");
private static final Class
amazonSearchPort_PortClass =
javapro.amazon.AmazonSearchPort.class;public java.rmi.Remote getPort(QName portName, Class serviceDefInterface)throws javax.xml.rpc.ServiceException{// implementation elided}public java.rmi.Remote getPort(Class serviceDefInterface)throws javax.xml.rpc.ServiceException{// implementation elided}public javapro.amazon.AmazonSearchPortgetAmazonSearchPort() {String[] roles = new String[] {};javapro.amazon.AmazonSearchPort_Stub stub =new javapro.amazon.AmazonSearchPort_Stub(
handlerChain);return stub;}}
為了使重要的數(shù)據(jù)更加顯而易見(jiàn),我們省略了很多實(shí)現(xiàn)代碼。注意,正是getAmazonSearchPort() 方法創(chuàng)建了AmazonSearchport_Stub(就是樁)的一個(gè)實(shí)例。樁是使用handlerChain進(jìn)行初始化的(參見(jiàn)清單2)。
信息結(jié)構(gòu)
清單2. AmazonSearchPort_Stub是使用handlerChain初始化的樁。我們傳遞給方法一個(gè)AuthorRequest結(jié)構(gòu),而該方法返回一個(gè)ProductInfo結(jié)構(gòu)。
public class AmazonSearchPort_Stubextends com.sun.xml.rpc.client.StubBaseimplements javapro.amazon.AmazonSearchPort {public AmazonSearchPort_Stub(HandlerChain handlerChain) {super(handlerChain);_setProperty(ENDPOINT_ADDRESS_PROPERTY,"http://soap.amazon.com/onca/soap3");}public ProductInfoauthorSearchRequest(AuthorRequest authorSearchRequest)throws java.rmi.RemoteException {try {StreamingSenderState _state = _start(_handlerChain);InternalSOAPMessage _request =_state.getRequest();_request.setOperationCode(AuthorSearchRequest_OPCODE);AmazonSearchPort_AuthorSearchRequest_RequestStructrequestStruct =new AmazonSearchPort_AuthorSearchRequest_RequestStruct();requestStruct.setAuthorSearchRequest(authorSearchRequest);SOAPBlockInfo _bodyBlock =new SOAPBlockInfo(ns1_AuthorSearchRequest_AuthorSearchRequest_QNAME);_bodyBlock.setValue(requestStruct);_bodyBlock.setSerializer(ns1requestStruct_SOAPSerializer);_request.setBody(_bodyBlock);_state.getMessageContext().setProperty(HttpClientTransport.HTTP_SOAPACTION_PROPERTY,"http://soap.amazon.com");_send((String) _getProperty(ENDPOINT_ADDRESS_PROPERTY), _state);AmazonSearchPort_AuthorSearchRequest_ResponseStructresponseStruct = null;Object _responseObj = _state.getResponse().getBody().getValue();if (_responseObj instanceofSOAPDeserializationState) {responseStruct =(AmazonSearchPort_AuthorSearchRequest_ResponseStruct)((SOAPDeserializationState)_responseObj).getInstance();} else {responseStruct = (AmazonSearchPort_AuthorSearchRequest_ResponseStruct)_responseObj;}return responseStruct.get_return();} catch (RemoteException e) {// let this one through unchangedthrow e;} catch (JAXRPCException e) {throw new RemoteException(e.getMessage(), e);} catch (Exception e) {if (e instanceof RuntimeException) {throw (RuntimeException)e;} else {throw new RemoteException(e.getMessage(), e);}}}}
同樣,您可以安全地忽略這里的很多代碼;它就是實(shí)現(xiàn)細(xì)節(jié)。然而,注意傳遞給該方法的是一個(gè)AuthorRequest結(jié)構(gòu),而該方法返回的是一個(gè)ProductInfo結(jié)構(gòu)。此外還要注意,我們將會(huì)發(fā)送請(qǐng)求至的URL被設(shè)置為帶有下列調(diào)用的方法的一部分:
setPropertyHttpClientTransport.HTTP_SOAPACTION_PROPERTY,"http://soap.amazon.com");
創(chuàng)建客戶端
現(xiàn)在,讓我們編寫客戶端代碼,這實(shí)際上是相當(dāng)容易的事情(參見(jiàn)清單3)。通常,JAX-RPC看起來(lái)就像是您在清單3中看見(jiàn)的代碼那樣:創(chuàng)建服務(wù)實(shí)現(xiàn),向它要求所需的Web service端口,并調(diào)用適當(dāng)?shù)姆椒?。其中的特殊之處在于調(diào)用authorSearchRequest()方法時(shí),您必須使用作者名初始化AuthorRequest對(duì)象。在這種情況下,您想查看書(shū)籍部分,您想要概要情況,而不是詳細(xì)數(shù)據(jù),而且您限制了搜索,讓它包含關(guān)鍵字“Web”。您需要設(shè)置的其他重要值是開(kāi)發(fā)人員令牌。出于明顯的理由,我還沒(méi)有在這里進(jìn)行賦值!
客戶端代碼
清單3. JAX-RPC客戶端創(chuàng)建了服務(wù)實(shí)現(xiàn),向它要求所需的Web service端口,并調(diào)用適當(dāng)?shù)姆椒ā?
public class Client{public static void main(String[] args){AmazonSearchService_Impl impl =new AmazonSearchService_Impl();AmazonSearchPort port =impl.getAmazonSearchPort();AuthorRequest authorRequest = new AuthorRequest();authorRequest.setAuthor("Kevin Jones");authorRequest.setDevtag("[developer token here]");authorRequest.setMode("books");authorRequest.setKeywords("Web");authorRequest.setType("lite");try{ProductInfo productInfo =port.authorSearchRequest(authorRequest);Details[] details = productInfo.getDetails();for (int ndx = 0; ndx < details.length; ndx++){Details detail = details[ndx];System.out.println(detail.getProductName());}}catch (RemoteException e){e.printStackTrace();}}}
URL硬編碼在樁中。URL不會(huì)經(jīng)常變化,但是假定您需要發(fā)送請(qǐng)求給另外一個(gè)URL,那該怎么辦呢?您可以讓一個(gè)服務(wù)位于多臺(tái)服務(wù)器上,或許是出于故障恢復(fù)的理由或負(fù)載平衡的目的,或者可能您想通過(guò)跟蹤工具(可以顯示每個(gè)方向上正在發(fā)送的SOAP數(shù)據(jù))發(fā)送請(qǐng)求和響應(yīng)。要完成這些任務(wù),您可以在樁中修改Web service地址:
AmazonSearchPort_Stub port = (AmazonSearchPort_Stub)impl.getAmazonSearchPort();port._setProperty(AmazonSearchPort_Stub.ENDPOINT_ADDRESS_PROPERTY,"http://localhost:9090/onca/soap3");
注意,getAmazonSearchPort()的返回值被轉(zhuǎn)給實(shí)現(xiàn)類而不是接口,所以我們可以設(shè)置端點(diǎn)地址,以便調(diào)用localhost。
然后,您可以運(yùn)行一個(gè)跟蹤工具,比如來(lái)自Axis的tcpmon或者我編寫的HttpProxy(下載HttpProxy的代碼)。這個(gè)工具被設(shè)置為監(jiān)聽(tīng)客戶端要調(diào)用的端口上的請(qǐng)求,并把它們轉(zhuǎn)發(fā)給原來(lái)的端口(參見(jiàn)圖3)。
HttpProxy跟蹤工具被設(shè)置為監(jiān)聽(tīng)客戶端要調(diào)用的端口上的請(qǐng)求,并把它們轉(zhuǎn)發(fā)給原來(lái)的端口。
如果有些問(wèn)題難于跟蹤,您可以使用這些工具來(lái)檢驗(yàn)SOAP和HTTP。如果使用了代理,您需要告訴Java運(yùn)行時(shí)通過(guò)該代理進(jìn)行調(diào)用。您可以通過(guò)設(shè)置Java系統(tǒng)屬性來(lái)做到這一點(diǎn):
public static void main(String[] args){System.setProperty("proxySet", "true" );System.setProperty("http.proxyHost","myproxy" );System.setProperty("http.proxyPort", "8080" );System.setProperty("http.proxyUser", "xxx");System.setProperty("http.proxyPassword","yyy");...}
Web service正在快速成為構(gòu)建客戶端/服務(wù)器應(yīng)用程序的一項(xiàng)標(biāo)準(zhǔn)。編寫Web service有多種方式,包括手寫和使用工具包。有各種可用于Java的工具包,包括開(kāi)源的Axis和Sun編寫的JAX-RPC。關(guān)于通過(guò)使用wscompile讀取WSDL和生成必需的客戶端代碼,并使用JAX-RPC來(lái)實(shí)現(xiàn)Web service客戶端,我們已經(jīng)接觸到了其方方面面。下一步將是了解如何編寫服務(wù)器。請(qǐng)參見(jiàn)“編寫一個(gè)Web service服務(wù)器”一文。
我們尚未討論的還有一些其他的方面,比如管理HTTP會(huì)話、HTTP身份驗(yàn)證和管理錯(cuò)誤,以及各種Java規(guī)范,比如SOAP with Attachments API for Java (SAAJ)和Java Architecture for XML Binding (JAXB),在Web service領(lǐng)域中所扮演的角色。對(duì)于Web service也有講解不夠深入的方面,比如管理附件,理解WSDL,以及rpc/encoded和document/literal風(fēng)格服務(wù)之間的差別。我們將來(lái)會(huì)對(duì)這些主題進(jìn)行更為深入的探討。
關(guān)于作者
Kevin Jones是一名開(kāi)發(fā)人員,他的主要工作是研究和教學(xué)Java編程,并探討HTTP和XML。他生活在U.K,為Developmentor工作。Developmentor是一家培訓(xùn)公司,總部位于美國(guó)和歐洲,專門致力于Java和Microsoft平臺(tái)方面技術(shù)培訓(xùn)。您可以通過(guò)他的電子郵箱kevinj@develop.com與他聯(lián)系。
原文出處
http://www.fawcette.com/javapro/2005_01/magazine/features/kjones_client/
作者簡(jiǎn)介 | |
Kevin Jones是一位開(kāi)發(fā)人員,主要從事 Java 編程的研究和教學(xué),以及研究 HTTP 和 XML 。他生活在英國(guó),在 Developmentor 公司工作,該公司是一家在美國(guó)和歐洲都設(shè)有辦事處的培訓(xùn)機(jī)構(gòu),主要從事有關(guān) Java 和 Microsoft 平臺(tái)的技術(shù)培訓(xùn)。 |
聯(lián)系客服