SOAP對于Java開(kāi)發(fā)者來(lái)說(shuō)尤其重要,因為它讓平臺無(wú)關(guān)和可移植的Java程序更容易協(xié)同操作,使得Java的寶貴特性進(jìn)一步增值。事實(shí)上,如果Java 2平臺企業(yè)版(J2EE)的下一個(gè)版本讓SOAP成為一種必須遵循的有線(xiàn)協(xié)議,規定所有遵從J2EE規范的應用服務(wù)器都必須支持SOAP協(xié)議,我也不會(huì )感到奇怪。不過(guò)就現在來(lái)說(shuō),我想我的猜想應該暫停了。
這個(gè)系列的文章總共四篇,這是第二篇。在這里,我要介紹的是Apache SOAP實(shí)現。
一、Apache SOAP概述
Apache SOAP,即Apache Software Foundation對SOAP規范的實(shí)現,建立在IBM的SOAP4J的基礎上。和所有其他Apache工程類(lèi)似,Apache SOAP源代碼開(kāi)放,按照Apache許可協(xié)議發(fā)行。我覺(jué)得這是當前最好的SOAP實(shí)現之一。然而,雖然Apache SOAP遵從SOAP規范1.1版本,但它缺乏SOAP 1.1所包含的某些功能。
1.1、下載和安裝Apache SOAP
如前所述,Apache SOAP可以免費下載(參見(jiàn)“參考資源”中提供的下載鏈接)。我為我的Windows NT便攜機下載了soap-bin-2.0.zip文件,該文件包含Apache SOAP 2.0,這是寫(xiě)作本文時(shí)的最新版本。安裝Apache SOAP可謂輕而易舉,共包含如下三個(gè)簡(jiǎn)單的步驟:
1. 解開(kāi)下載所得文件的ZIP壓縮:解開(kāi)壓縮之后就得到了一個(gè)soap-2_0子目錄。我把ZIP文件的內容解壓縮到E盤(pán)的根目錄下,因此有了一個(gè)包含Apache SOAP的E:\soap-2_0目錄。
2. 配置Web環(huán)境:要有一個(gè)支持Servlet和JSP的Web服務(wù)器。至此,你可能遇到下面兩種情況之一:
o 情況1:已經(jīng)有一個(gè)支持Servlet和JSP的Web服務(wù)器,而且你覺(jué)得配置這個(gè)服務(wù)器沒(méi)有問(wèn)題。在這種情況下,請配置服務(wù)器,使得瀏覽器可以訪(fǎng)問(wèn)http://localhost:8080/apache-soap/,打開(kāi)soap-2_0 \webapps\soap\目錄下面的index.html文件。
o 情況2:沒(méi)有支持Servlet和JSP的Web服務(wù)器,或者雖然有這樣一個(gè)服務(wù)器,卻不想拿它做試驗。在這種情況下,我建議你下載Tomcat的最新版本(寫(xiě)作本文時(shí),最新版本是3.1)(參見(jiàn)“參考資源”中的鏈接)。Tomcat是Apache創(chuàng )建和免費提供給軟件開(kāi)發(fā)者的又一個(gè)優(yōu)秀軟件。下載合適的ZIP文件之后(jakarta-tomcat-3.1.1.zip),解開(kāi)壓縮時(shí)創(chuàng )建一個(gè)jakarta-tomcat子目錄。和前面相似,我把解壓縮得到的文件放入E盤(pán)的根目錄之下。在jakarta-tomcat\conf\server.xml配置文件中增加一個(gè)新的<Context>標記,如下所示:
<Context path="/apache-soap" docBase="E:/soap-2_0/webapps/soap"
debug="1" reloadable="true">
</Context>
o 在Context元素的docBase屬性中,你應該在指定soap-2_0目錄時(shí)把E:替換成合適的盤(pán)符。要啟動(dòng)Tomcat,執行startup.bat(對于Unix,執行startup.sh)。要關(guān)閉Tomcat,執行shutdown.bat(對于Unix,執行shutdown.sh)。但請稍等——現在請不要啟動(dòng)Tomcat。
3. 設置Web服務(wù)器classpath:Apache SOAP要求有1.1.2版本或更高的Apache Xerces(Java),它支持DOM(文檔對象模型)Level 2規范,支持名稱(chēng)空間。我使用1.2版本的Xerces,即Apache網(wǎng)站的Xerces-J-bin.1.2.0.zip。解開(kāi)這個(gè)壓縮文件,得到xerces-1_2_0子目錄。和前面一樣,我把解壓縮得到的文件放到了E:盤(pán)的根目錄之下。你應該配置Web服務(wù)器,使它能夠用xerces.jar(它在xerces-1_2_0子目錄下)進(jìn)行所有XML解析——而不是用服務(wù)器附帶的庫或jar解析XML。例如,Tomcat附帶了一個(gè)XML解析器(jakarta-tomcat\lib\xml.jar),支持DOM Level 1接口。即使你把xerces.jar放入了classpath,Tomcat下運行的Java代碼也可能找錯接口,因為在用來(lái)啟動(dòng)Tomcat的Shell腳本/批命令文件中,xerces.jar被放到了classpath的最后。因此,必須編輯jakarta-tomcat\bin目錄下的tomcat.bat(對于Unix,則是tomcat.sh),把xerces.jar放到classpath的前面。下面是我在jakarta-tomcat\bin\tomcat.bat文件中所作的修改:
set CLASSPATH=E:\xerces-1_2_0\xerces.jar;%CLASSPATH%;%cp%
如果你在第二個(gè)步驟中屬于情況2,也必須配置服務(wù)器,使它能夠使用xerces.jar。
現在,啟動(dòng)Web服務(wù)器,用瀏覽器打開(kāi)http://localhost:8080/apache-soap/admin,驗證安裝是否成功。這時(shí),你應該看到所示的管理屏幕。
二、實(shí)例:HelloWorld
現在你已經(jīng)設置好了Apache SOAP,我們來(lái)實(shí)際使用一下,構造一個(gè)簡(jiǎn)單的HelloWorld應用。在SOAP術(shù)語(yǔ)中,應用稱(chēng)為服務(wù)。一般地,創(chuàng )建服務(wù)分兩個(gè)步驟,這兩個(gè)步驟可能由同一個(gè)人或組織實(shí)施,也可能不是。第一個(gè)步驟是在選定的語(yǔ)言中定義和實(shí)現服務(wù)本身,這里我們選擇Java語(yǔ)言。第二個(gè)步驟是創(chuàng )建實(shí)際調用服務(wù)的客戶(hù)程序。首先我們來(lái)看HelloWorld服務(wù)。
2.1、HelloWorld服務(wù)
我在第一篇文章中討論了一個(gè)用SOAP實(shí)現的HelloWorld服務(wù)實(shí)例。這個(gè)服務(wù)要求輸入一個(gè)用戶(hù)名字,返回一個(gè)定制的Hello消息給調用者。下面的代碼顯示了HelloWorld服務(wù)的完整Java代碼。
package hello;
public class HelloServer
{
public String sayHelloTo(String name)
{
System.out.println("sayHelloTo(String name)");
return "Hello " + name + ", How are you doing?";
}
}
這就是全部的代碼嗎?如果這是真的話(huà),實(shí)在太簡(jiǎn)單了!
Apache SOAP使創(chuàng )建服務(wù)變得極其簡(jiǎn)單。服務(wù)主要由業(yè)務(wù)邏輯構成,不管服務(wù)以哪種方式提供給外界使用,業(yè)務(wù)邏輯代碼都是必須編寫(xiě)的。換句話(huà)說(shuō),服務(wù)不會(huì )和任何SOAP相關(guān)的代碼混合,因為Apache SOAP底層體系——它由rpcrouter Servlet和soap.jar構成——幫助我們完成了所有錯綜復雜的工作。我們來(lái)簡(jiǎn)要地探討一下這些錯綜復雜的工作究竟包含些什么,例如,Apache SOAP如何處理HTTP上的遠程過(guò)程調用(RPC)請求?理解這一點(diǎn)將給創(chuàng )建客戶(hù)程序帶來(lái)方便(不錯,是客戶(hù)程序)。
在A(yíng)pache SOAP中,org.apache.soap.rpc包支持在SOAP上進(jìn)行RPC調用。Apache RPC支持的關(guān)鍵在于對象ID。所有的Apache SOAP服務(wù)必須有一個(gè)唯一的ID,它被視為服務(wù)的對象ID。眾所周知,唯一性是一個(gè)相對的概念;在A(yíng)pache SOAP中,對象ID的唯一性相對于服務(wù)所部署的Apache SOAP服務(wù)器而言。也就是說(shuō),部署在不同Apache SOAP服務(wù)器上的兩個(gè)服務(wù)可能有同樣的對象ID。
想要使用服務(wù)的客戶(hù)程序設置一個(gè)org.apache.soap.rpc.Call對象,指定目標服務(wù)的對象ID、待調用方法的名字以及提供給方法的參數(如果有的話(huà))。設置好Call對象之后,客戶(hù)程序調用它的invoke()方法。invoke()方法需要兩個(gè)參數,第一個(gè)參數是一個(gè)執行rpcrouter Servlet的URL,如http://localhost:8080/apache-soap/servlet/rpcrouter;第二個(gè)參數是SOAPAction頭(請參考本系列的第一篇文章,了解SOAPAction頭的重要性和可能的取值)。
invoke()方法把Call對象轉換成XML SOAP請求(類(lèi)似第一篇文章所提供的示例),把請求發(fā)送給第一個(gè)參數中的URL所指定的rpcrouter Servlet。當Servlet返回應答,invoke()方法返回一個(gè)org.apache.soap.rpc.Response對象,這個(gè)對象包含了服務(wù)的應答(如果有的話(huà))或錯誤信息(如果出現了錯誤)。HTTP規定每一個(gè)請求都必須有一個(gè)應答;因此,即使服務(wù)本身不返回任何東西,rpcrouter Servlet總是會(huì )返回一些內容。因此,invoke()方法總是返回一個(gè)Response對象。
在服務(wù)端,Apache SOAP服務(wù)器(也就是rpcrouter Servlet)接收客戶(hù)程序發(fā)送的SOAP請求,重新構造出Call對象。Servlet使用重新構造得到的Call對象中的對象ID在服務(wù)管理器中確定具體的對象。
對于Java基本數據類(lèi)型(int,long,double,等等)及其對應的對象化形式(Integer,Long,Double,等等)來(lái)說(shuō),它們的串行化器和反串行化器已經(jīng)預先在類(lèi)型映射注冊器中注冊。因此,對于客戶(hù)程序來(lái)說(shuō),用Java基本數據類(lèi)型及其對象形式作為方法參數是無(wú)縫的。然而,如果服務(wù)所要求的參數是一個(gè)JavaBean,則必須手工在類(lèi)型映射注冊器中注冊BeanSerializer。服務(wù)永遠不會(huì )做任何額外的工作,最后客戶(hù)程序的負擔總是較多。在這個(gè)系列的第四篇文章中,我將介紹用動(dòng)態(tài)代理構造的框架,它將使創(chuàng )建客戶(hù)程序和創(chuàng )建服務(wù)程序一樣簡(jiǎn)單。
2.2、部署HelloWorld服務(wù)
部署Apache SOAP服務(wù)有兩種方法:使用Web界面的管理工具,或通過(guò)命令行進(jìn)行部署。所有這兩種方法都可以部署服務(wù),使服務(wù)可被客戶(hù)程序訪(fǎng)問(wèn)。
■ 使用管理工具部署服務(wù)
要使用管理工具,用瀏覽器打開(kāi)http://localhost:8080/apache-soap/admin。瀏覽器將顯示出圖一所示的界面。點(diǎn)擊窗口左邊的Deploy按鈕,一個(gè)帶有許多輸入框的窗口就會(huì )出現。并非所有的輸入框現在都要用到。我將在用到這些輸入框的時(shí)候介紹它們的含義。由于本文無(wú)需用到所有的輸入框,所以我們將忽略部分輸入框的含義。但不用擔心,到第三篇文章結束時(shí),我將介紹完所有的輸入框。
ID輸入框用來(lái)設置對象ID;如前所述,SOAP基礎設施利用對象ID把RPC請求綁定到SOAP服務(wù)。我在前面已經(jīng)提到,所有Apache SOAP服務(wù)必須有一個(gè)對象ID,這個(gè)對象ID在該服務(wù)器上部署的所有服務(wù)之間唯一。我通常使用“urn:<UniqueServiceID>”格式,其中UniqueServiceID是服務(wù)的唯一對象ID。在本例中,把ID設置成“urn:hello”。
Scope輸入框用來(lái)定義響應調用請求的服務(wù)實(shí)例的生存范圍和時(shí)間。Scope可以是下列值之一:
· page:服務(wù)實(shí)例一直有效,直至應答發(fā)送完畢或把請求傳遞給了另一個(gè)頁(yè)面——如果使用標準的部署機制,向前傳遞請求不太可能發(fā)生。
· request:服務(wù)實(shí)例在請求處理期間一直有效,不管是否出現請求傳遞。
· session:服務(wù)實(shí)例對于整個(gè)會(huì )話(huà)都有效。
· application:服務(wù)實(shí)例被用于所有對服務(wù)的調用請求。
Scope的值對安全有著(zhù)重要的影響,記住這一點(diǎn)很重要。page和request值確保了連續調用之間的隔離。在另一個(gè)極端,application值意味著(zhù)所有SOAP的用戶(hù)共享服務(wù)實(shí)例。細心的讀者可能已經(jīng)注意到,JSP的標記同樣要用到這些值。事實(shí)上,rpcrouter Servlet曾經(jīng)就是一個(gè)JSP頁(yè)面,這也許是這些值被選用的原因。在本例中,我們把Scope的值設置成application。
在Methods輸入框中,輸入用空白字符分隔的方法名字,這些方法名字指示出當前部署的服務(wù)上允許調用的方法。我們的服務(wù)示例只支持一個(gè)方法,即sayHelloTo()。
把Provider Type設置成Java。它意味著(zhù)服務(wù)用Java實(shí)現,而且你必須為Apache SOAP提供服務(wù)完整的類(lèi)名。這個(gè)任務(wù)在Provider Class輸入框完成,我們把它設置成hello.HelloServer。由于sayHelloTo()方法不是靜態(tài)的,保持Static輸入框原來(lái)的值,即no。
部署服務(wù)除了可以用Web界面的管理工具,還可以用命令行Java工具org.apache.soap.server.ServiceManagerClient,它是一個(gè)Apache SOAP附帶的類(lèi)。這個(gè)類(lèi)要求有兩個(gè)必不可少的參數,一個(gè)指向Apache SOAP路由Servlet(即rpcrouter)的URL,以及一個(gè)動(dòng)作。這個(gè)動(dòng)作可以是以下四者之一:deploy,undeploy,list,或query。根據指定動(dòng)作的不同,有時(shí)候還要提供額外的參數。例如,如果動(dòng)作是deploy,則必須提供XML部署描述器文件的名字。部署描述器文件應該包含Apache SOAP服務(wù)器成功部署服務(wù)所需要的全部信息。例如,描述HelloWorld部署細節的部署XML文件可以如下:
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="urn:Hello">
<isd:provider type="java" scope="Application" methods="sayHelloTo">
<isd:java class="hello.HelloServer" static="false"/>
</isd:provider>
</isd:service>
上述XML代碼所包含的信息和我們在Web界面的管理工具中所輸入的信息一樣。接下來(lái),輸入下面的命令,從命令行部署HelloWorld服務(wù):
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/apache-soap/servlet/rpcrouter
deploy DeploymentDescriptor.xml
DeploymentDescriptor.xml是上面顯示的描述部署信息的XML文件名字。要驗證服務(wù)是否部署成功,輸入以下命令:
java org.apache.soap.server.ServiceManagerClient
http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello
這時(shí),我們應該看到和DeploymentDescriptor.xml文件內一樣的XML。
2.3、HelloWorld客戶(hù)程序
編寫(xiě)客戶(hù)程序要比編寫(xiě)HelloWorld服務(wù)復雜得多。不過(guò),你應該不會(huì )對此感到奇怪,因為前面已經(jīng)提到,客戶(hù)程序(至少)必須負責設置Call對象,這需要不少工作。順便說(shuō)一下,本系列文章的第四篇將介紹一個(gè)框架,這個(gè)框架以Java 2 1.3版新引入的動(dòng)態(tài)代理類(lèi)為基礎,使創(chuàng )建客戶(hù)程序和創(chuàng )建服務(wù)一樣簡(jiǎn)單。
Listing 1顯示了完整的客戶(hù)程序。接下來(lái)我們一步一步地仔細看看這個(gè)程序。這個(gè)程序需要一個(gè)必不可少的參數:程序要向他說(shuō)Hello信息的用戶(hù)名字。
Listing 1: Client.java
package hello;
import java.net.URL;
import java.util.Vector;
import org.apache.soap.SOAPException;
import org.apache.soap.Constants;
import org.apache.soap.Fault;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
public class Client
{
public static void main(String[] args) throws Exception
{
if(args.length == 0)
{
System.err.println("Usage: java hello.Client [SOAP-router-URL] ");
System.exit (1);
}
try
{
URL url = null;
String name = null;
if(args.length == 2)
{
url = new URL(args[0]);
name = args[1];
}
else
{
url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter");
name = args[0];
}
// 構造Call對象
Call call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector params = new Vector();
params.addElement(new Parameter("name", String.class, name, null));
call.setParams(params);
// 發(fā)出調用
Response resp = null;
try
{
resp = call.invoke(url, "");
}
catch( SOAPException e )
{
System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +
e.getMessage());
System.exit(-1);
}
· 被調用服務(wù)的對象ID,它通過(guò)Call對象的setTargetObjectURI()方法設置。本例的對象ID是urn:Hello。
· 待調用方法的名字,它通過(guò)Call對象的setMethodName()方法設置。本例的方法名字是sayHelloTo()。
· 參數的編碼方式,它通過(guò)Call對象的setEncodingStyleURI()方法設置。本例我們使用標準的SOAP編碼方式,這種編碼方式由名稱(chēng)空間http://schemas.xmlsoap.org/soap/encoding/定義。
· 方法調用的參數通過(guò)Call對象的setParams()方法設置。setParams()方法的參數是一個(gè)Java Vector(向量)。這個(gè)向量包含所有的參數,向量中索引為0的參數是被調用方法從左邊數起的第一個(gè)參數,索引為1的參數是被調用方法從左邊數起的第二個(gè)參數,依此類(lèi)推。向量中的每一個(gè)元素都是一個(gè)org.apache.soap.rpc.Parameter的實(shí)例。Parameter構造函數要求指定參數的名字、Java類(lèi)型和值,還有一個(gè)可選的編碼方式。如果指定了null編碼方式(正如本例所做的那樣),則默認使用Call對象的編碼方式。雖然每一個(gè)參數對應著(zhù)一個(gè)名字,但這個(gè)名字可以設置成任何內容,Apache SOAP服務(wù)器調用方法時(shí)不會(huì )用到這個(gè)名字。因此,絕對有必要讓向量中參數的次序和被調用方法的參數次序一致。
下面的代碼片斷顯示了客戶(hù)程序創(chuàng )建Call對象的過(guò)程:
// 構造Call對象
Call call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector params = new Vector();
params.addElement(new Parameter("name", String.class, name, null));
call.setParams(params);
現在,該是實(shí)際調用HelloWorld遠程服務(wù)所提供方法的時(shí)候了。為此,客戶(hù)程序調用了Call對象的invoke()方法,這個(gè)方法返回一個(gè)org.apache.soap.rpc.Response對象,如下所示:
// 發(fā)出調用
Response resp = null;
try
{
resp = call.invoke(url, "");
}
catch( SOAPException e )
{
System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +
e.getMessage());
System.exit(-1);
}
接下來(lái),客戶(hù)程序檢查Response對象。如果方法調用過(guò)程中出現了錯誤,generateFault()方法返回一個(gè)true值,客戶(hù)程序提取并顯示實(shí)際的錯誤信息:
Fault fault = resp.getFault();
System.err.println("Generated fault: ");
System.out.println (" Fault Code = " + fault.getFaultCode());
System.out.println (" Fault String = " + fault.getFaultString());
如果方法調用成功,則客戶(hù)程序提取并顯示Hello信息:
如前所述,Apache SOAP提供了許多預先構造的串行化和反串行化方法,其中包括為利用Java Vector、Enumeration、數組、JavaBean作為參數和返回值而提供的串行化器和反串行化器。在這一部分,我將修改HelloWorld服務(wù),通過(guò)一個(gè)JavaBean傳入接收Hello信息的用戶(hù)名。
3.1、HelloWorld服務(wù)
改寫(xiě)后的HelloWorld服務(wù)完整代碼如下:
package hello;
public class HelloServer
{
public String sayHelloTo(String name)
{
System.out.println("sayHelloTo(String name)");
return "Hello " + name + ", How are you doing?";
}
public String sayHelloTo(Name theName)
{
System.out.println("sayHelloTo(Name theName)");
return "Hello " + theName.getName() + ", How are you doing?";
}
}
服務(wù)的代碼仍舊很簡(jiǎn)單,仍舊類(lèi)似于不用JavaBean時(shí)的HelloWorld服務(wù)。不過(guò),這意味著(zhù)最復雜的工作都轉移到了客戶(hù)端。事實(shí)上,這個(gè)版本的服務(wù)與以前版本的唯一差別在于,現在出現了一個(gè)重載的sayHelloTo()方法。上面的代碼中重載后的方法用粗體字顯示。
重載的方法需要一個(gè)對Name JavaBean的引用。Name JavaBean的定義如下:
package hello;
public class Name
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
3.2、部署服務(wù)
部署一個(gè)使用了JavaBean的服務(wù)時(shí),需要為Apache SOAP服務(wù)器提供一些額外的信息。因此,現在部署服務(wù)的過(guò)程稍微復雜一點(diǎn)。
使用管理工具部署服務(wù)
從命令行部署服務(wù)
要從命令行進(jìn)行部署,我們只需修改作為命令行參數傳入的XML部署描述器文件。修改后的XML文件如下所示:
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:Hello">
<isd:provider type="java" scope="Application" methods="sayHelloTo">
<isd:java class="hello.HelloServer" static="false"/>
</isd:provider>
<isd:mappings>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:Hello" qname="x:hello.Name"
javaType="hello.Name"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
</isd:mappings>
</isd:service>
正如在前一個(gè)例子中,這些XML代碼所包含的信息和通過(guò)Web界面的管理工具所提供的信息一樣。
3.3、HelloWorld客戶(hù)程序
和第一個(gè)例子一樣,客戶(hù)程序更復雜,也更令人感興趣。這里我不再仔細分析整個(gè)客戶(hù)程序,而是介紹兩個(gè)客戶(hù)程序版本的不同之處。由于調用方法的一個(gè)參數(在本例中,它是唯一的參數)是一個(gè)JavaBean,所以必須手工設置一個(gè)類(lèi)型映射注冊項。這個(gè)任務(wù)通過(guò)如下步驟完成:先創(chuàng )建org.apache.soap.encoding.SOAPMappingRegistry類(lèi)的一個(gè)實(shí)例,然后調用它的mapTypes()方法。正如mapTypes()方法名字所預示的,它用來(lái)注冊一個(gè)以前未知的類(lèi)型,比如定制的JavaBean。mapTypes()方法的參數包括要使用的編碼方式、限定的JavaBean名字、類(lèi)型的完整類(lèi)名、串行化器和反串行化器。在本例中,執行串行化任務(wù)的是標準的Bean串行化器。限定的JavaBean名字包含一個(gè)元素的名字,包括它所屬的名稱(chēng)空間。在本例中,Name JavaBean的限定名字由名稱(chēng)空間URI(urn:Hello)和本地名字(hello.Name)結合構成。請看下面的代碼片斷:
// 創(chuàng )建類(lèi)型映射注冊器
SOAPMappingRegistry smr = new SOAPMappingRegistry();
BeanSerializer beanSer = new BeanSerializer();
// 映射類(lèi)型
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName("urn:Hello", "hello.Name"),hello.Name.class, beanSer, beanSer);
接下來(lái),客戶(hù)程序必須告訴Call對象使用新的注冊器而不是默認的注冊器。為此,我們要調用Call對象的setSOAPMappingRegistry()方法,如下所示:
call.setSOAPMappingRegistry(smr);
手工設置好類(lèi)型映射注冊器之后,接下來(lái)還必須為Call對象設置參數。這一步驟可以按前面介紹的方法完成,不同之處在于,現在我們不再用字符串類(lèi)型的名字作為參數,而是用JavaBean作為參數,如下所示:
// 設置調用參數
Vector params = new Vector();
Name theName = new Name();
theName.setName(name);
params.addElement(new Parameter("name", hello.Name.class, theName, null));
call.setParams(params);
客戶(hù)程序剩下的部分和原來(lái)的版本一樣。Listing 3顯示了完整的客戶(hù)程序代碼:
Listing 3: Client2.java
package hello;
import java.net.URL;
import java.util.Vector;
import org.apache.soap.SOAPException;
import org.apache.soap.Constants;
import org.apache.soap.Fault;
import org.apache.soap.rpc.Call;
import org.apache.soap.rpc.Parameter;
import org.apache.soap.rpc.Response;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.BeanSerializer;
import org.apache.soap.util.xml.QName;
public class Client2
{
public static void main(String[] args) throws Exception
{
if(args.length == 0)
{
System.err.println("Usage: java hello.Client [SOAP-router-URL] ");
System.exit (1);
}
try
{
URL url = null;
String name = null;
if(args.length == 2)
{
url = new URL(args[0]);
name = args[1];
}
else
{
url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter");
name = args[0];
}
// 構造調用對象
Call call = new Call();
call.setTargetObjectURI("urn:Hello");
call.setMethodName("sayHelloTo");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
// 創(chuàng )建類(lèi)型映射注冊器
SOAPMappingRegistry smr = new SOAPMappingRegistry();
BeanSerializer beanSer = new BeanSerializer();
// 映射類(lèi)型
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName("urn:Hello", "hello.Name"),
hello.Name.class, beanSer, beanSer);
call.setSOAPMappingRegistry(smr);
// 設置參數
Vector params = new Vector();
Name theName = new Name();
theName.setName(name);
params.addElement(new Parameter("name", hello.Name.class,
theName, null));
call.setParams(params);
// 發(fā)出調用
Response resp = null;
try
{
resp = call.invoke(url, "");
}
catch( SOAPException e )
{
System.err.println("Caught SOAPException (" +
e.getFaultCode() + "): " + e.getMessage());
System.exit(-1);
}
現在整個(gè)程序的開(kāi)發(fā)工作已經(jīng)完成,該是運行它的時(shí)候了。不過(guò),我們首先要編譯服務(wù)程序和客戶(hù)程序。
創(chuàng )建一個(gè)hello目錄,把Client1.java、Client2.java和HelloServer.java復制到這個(gè)目錄。我把hello目錄放到了Apache SOAP的示例目錄(即E:\soap-2_0\samples)之下。編譯程序時(shí),classpath中只需包含hello目錄的父目錄(即E:\soap-2_0\samples)、soap.jar和xerces.jar。我用下面的批命令編譯程序:
set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar
javac -d .. HelloServer.java Client.java Client2.java
注意:從hello目錄執行這個(gè)批命令文件。
要使用這個(gè)服務(wù),除了部署它之外,還需要修改Web服務(wù)器的classpath,確保Web服務(wù)能夠找到hello.HelloServer類(lèi)——對于本例,這是指把E:\soap-2_0\samples加入到Web服務(wù)器的classpath。對classpath進(jìn)行必要的修改之后,重新啟動(dòng)Web服務(wù)器。接下來(lái)就可以運行客戶(hù)程序了。下面是我運行hello.Client的批命令文件:
set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar
java hello.Client Tarak
這里的classpath和編譯程序時(shí)用的classpath相同。
最后,運行hello.Client2的批命令文件可以如下:
set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar
java hello.Client2 Tarak
觀(guān)察Web服務(wù)器的控制臺窗口,看看在運行兩個(gè)不同的客戶(hù)程序時(shí),HelloWorld服務(wù)的哪些方法正在被調用。
結束語(yǔ)
在這篇文章中,我介紹了如何用Apache SOAP實(shí)現來(lái)創(chuàng )建簡(jiǎn)單的基于SOAP的服務(wù)。在SOAP實(shí)現方面,另一個(gè)重要的競爭者是Microsoft。遺憾的是,“純”Java開(kāi)發(fā)者在使用Microsoft實(shí)現的時(shí)候會(huì )有一段艱苦的時(shí)光,因為它的實(shí)現包含了COM對象。
在下一篇文章中,我將介紹Apache SOAP支持的另一種創(chuàng )建服務(wù)的方式:使用JavaScript之類(lèi)的腳本語(yǔ)言,而不是Java。另外,我還要介紹一個(gè)很不錯的JavaScript引擎,即Rhino
聯(lián)系客服