SOAP淨化有線協議(四):簡化客戶程式

來源:互聯網
上載者:User
程式   Java 2平台1.3版本為Java映像API(Reflection API)增加了一個極其實用的擴充:動態代理類。一個動態代理類就是一個實現了一系列運行時指定的介面的類。這個代理可以象它真正實現了這些介面一樣使用。換句話說,可以直接在代理對象上調用任意介面的任意方法——當然,必須先進行必要的類型定型(casting)。由此,我們可以用動態代理類為一組介面建立一個型別安全的代理對象,且不必象使用編譯時間工具一樣預先組建代理程式(有關動態代理類更詳細的說明,請參見本文最後的參考資源)。

接下來我將介紹一個以動態代理類為基礎的架構,這個架構使得SOAP(簡易物件存取通訊協定 (SOAP))客戶程式的建立更加簡單和直觀。SOAP是一種用XML編碼資料的有線協議。在本系列文章的第二篇、第三篇構造SOAP服務的過程中,我們發現客戶程式的開發人員必須多做許多原來不必做的工作。為協助回憶,你可以看一下第二篇文章中的SOAP服務代碼,看看和客戶程式碼相比較時,服務程式的SOAP代碼是多麼微不足道。本系列文章前幾篇所建立的簡單SOAP服務顯示出,基於SOAP的服務只包含無論用不用SOAP都必須提供的代碼。服務程式的開發人員要編寫的額外代碼很少,而客戶程式開發人員卻有許多額外工作要做。本文介紹的類將把這些額外工作減到最少。

一、介紹SOAP代理類
首先,我要給出如果客戶程式使用了本文建立的架構,它將變成什麼樣子:

package hello;
import soapproxy.*;
public class Client
{
public static void main(String[] args)
{
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
// 調用sayHelloTo方法
// 這個sayHelloTo方法需要一個字串參數
System.out.println(hello.sayHelloTo("John"));
// 調用sayHelloTo方法
// 這個sayHelloTo方法需要一個Name JavaBean參數
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
也許是出於我的個人愛好,我認為上面的客戶代碼比第二篇和第三篇文章中的客戶代碼更好。如果你現在不能理解上面的代碼,這很正常,但我想待到本文結束時你會理解的。

要理解客戶程式的代碼,你必須深入瞭解SOAP Proxy類,它在soapproxy包內,可以在Proxy.java內找到(參見本文最後的參考資源)。Proxy類有一個私人的建構函式,它意味著Proxy執行個體不能從Proxy之外建立;建立Proxy執行個體的唯一方法是通過靜態newInstance()方法。newInstance()方法有兩個參數:SOAP服務的對象ID,以及一個數組,數組中包含一組該代理要實現的介面的名字。對象ID很簡單,但這些介面名字是什嗎?從哪裡去得到這些介面的名字?SOAP服務的開發人員直接把服務上所有可被客戶程式調用的方法堆在一起得到一個介面。相當簡單,不是嗎?

現在我們為HelloWorld服務定義一個介面。第二篇文章中,這個服務的最終版本有sayHelloTo()方法的兩個重載版本:一個版本的參數是一個字串,另一個版本的參數是一個Name JavaBean。這兩個方法就可以構成一個介面,稱為Hello,如下所示:

package hello;
public interface Hello
{
public String sayHelloTo(String name);
public String sayHelloTo(Name name);
}
服務開發人員決定要建立多少介面,以及為這些介面取什麼樣的名字。例如,你可以為HelloWorld服務建立兩個介面,每一個介面包含一個方法。一般地,你應該避免建立方法數量大於七個的介面。另外,注意只把那些看來有必要放在一起的方法用一個介面組織起來。例如,如果HelloWorld服務還有一個返回定製的Good-Bye訊息給調用者的sayByeTo()方法,設計兩個獨立的介面也許比較明智:一個介面用於sayHelloTo()方法,一個介面用於sayByeTo()方法。

現在我們有了定義HelloWorld服務和客戶程式之間契約的介面,下面返回來看newInstance()方法。如前所述,newInstance()方法建立Proxy類的一個新執行個體。newInstance()方法可以建立新執行個體是因為它屬於Proxy類,能夠訪問私人的建構函式。newInstance()方法為新建立的執行個體調用initialize()方法。initialize()值得關注,因為動態代理就是在這裡建立和返回。initialize()的代碼如下所示:

private Object initialize(Class[] interfaces)
{
return(java.lang.reflect.Proxy.newProxyInstance(getClass().getClassLoader()
,interfaces,this));
}
注意newProxyInstance()方法的應用。建立動態代理類執行個體的唯一辦法是調用該類(即java.lang.reflect.Proxy類)靜態newProxyInstance()方法。java.lang.reflect.Proxy類為建立動態代理類提供了靜態方法,而且它還是所有由這些方法建立的動態代理類的超類。換句話說,它不僅是一個建立動態代理類的工廠,而且它本身也是一個動態代理類!因此,在我們的例子中,SOAP代理不是動態代理;相反,這個動態代理實際上是newProxyInstance靜態方法返回的java.lang.reflect.Proxy類的一個執行個體。從本文後面可以看到,這個動態代理實際上通過SOAP代理實現的invoke()方法完成它的所有工作。那麼,這個動態代理如何建立和SOAP代理的聯絡呢?因為有一個對SOAP代理的引用傳遞給了newProxyInstance()方法。也許現在這聽起來有點費解,但只要你分析一下invoke()方法,這一切就很明白了。

java.lang.reflect.Proxy類建構函式的第一個參數是一個類裝載器執行個體,第二個參數是需要動態實現的介面的數組(它就是客戶程式傳遞給newInstance()的數組),第三個參數是一個實現了java.lang.reflect.InvocationHandler介面的類的執行個體。因為SOAP Proxy類實現了InvocationHandler介面,所以第三個參數是代理執行個體本身(即this)。InvocationHandler介面有一個方法invoke()。當動態代理的動態實現的介面被調用時,Java運行時環境調用invoke()方法。因此,舉例來說,當客戶程式調用動態代理的Hello介面的sayHelloTo()方法時,Java運行時環境將調用SOAP代理的invoke()方法。

你可能已經發現,SOAP代理的newInstance()方法不返回SOAP代理的執行個體;相反,它返回newInsance()剛剛建立的動態代理,而動態代理動態地實現客戶程式傳入的介面數組。客戶程式可以將這個返回的動態代理定型為傳入newInstance()的任意介面類型,在動態代理上調用介面所定義的各個方法,就象動態代理真地實現了那些介面一樣。

.
.
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
// 調用參數為字串的sayHelloTo方法
System.out.println(hello.sayHelloTo("John"));
// 調用參數為Name JavaBean的sayHelloTo方法
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
.
.
在上面的代碼中,invoke()方法將被調用兩次,每次調用sayHelloTo()方法時執行一次。現在我們來看看invoke()方法。簡而言之,invoke()方法的工作正是第二篇文章中每一個客戶程式必須手工完成的工作,其中包括:用合適的調用參數設定一個Call對象,定製的調用參數所需要的類型映射。由於SOAP代理中的invoke()方法擔負了所有這些任務,客戶程式釋放了這份負擔。

在invoke()方法接收到的三個參數中,我們只對後面兩個感興趣。第二個參數,即Method對象,給出了被呼叫者法的名字。記住,被呼叫者法的名字對應著一個SOAP服務匯出的已知方法。服務的對象ID作為參數傳遞給newInstance()方法,所以invoke()方法已經擁有該對象ID。invoke()方法利用這些資訊,按照如下方式設定Call對象:

Call call = new Call();
call.setTargetObjectURI(urn);
call.setMethodName(m.getName());
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
現在要做的是為遠程服務調用設定參數。為此,我們要用到invoke()方法的第三個參數:傳入動態代理上被呼叫者法的一個參數數組。數組中索引為0的參數是方法調用中最左邊的參數,索引為1的參數是方法的第二個參數,依此類推。舉例來說,如果客戶程式調用了sayHelloTo(String name)方法,那麼參數數組就是包含一個字串的數組。invoke()方法處理該數組的每一個元素,建立一個由Parameter對象構成的向量(Vector)(正如第二篇文章中客戶程式所做的那樣):

java.util.Vector params = new java.util.Vector();
for( int i=0; i<args.length; i++ )
{
if( isSimple(args[i]) || isSimpleArray(args[i]) )
{
params.add(new Parameter(_paramName+(i+1),
args[i].getClass(),args[i],null));
}
else if( isVector(args[i]) )
{
addMapping((java.util.Vector)args[i]);
params.add(new
Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));
}
// 如果這個數組的元素不屬於Java基礎資料型別 (Elementary Data Type)
// 則假定這是一個JavaBean的數組
else if( isArray(args[i]) )
{
if( smr == null )
smr = new SOAPMappingRegistry();
if( beanSer == null )
beanSer = new BeanSerializer();
ArraySerializer arraySer = new ArraySerializer();
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
null, null, beanSer, beanSer);
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
null,args[i].getClass(), arraySer, arraySer);
params.add(new Parameter(_paramName+(i+1),
args[i].getClass(),args[i],null));
}
// 假定這是一個Bean
else
{
if( smr == null )
smr = new SOAPMappingRegistry();
if( beanSer == null )
beanSer = new BeanSerializer();
String qnamePart = args[i].getClass().getName();
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName(urn, qnamePart),args[i].getClass(), beanSer,
beanSer);
params.add(new Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));
}
}
invoke()方法用到了許多私人的輔助方法,比如用isSimple()來確定參數的類型。如果參數是一個JavaBean或者一個數組,那麼,程式必須設定一個定製的SOAP映射註冊項,並通過setSOAPMappingRegistry()方法對Call對象作相應的設定(參見第二篇文章)。SOAP代理假定,當出現JavaBean時,SOAP服務用到的所有JavaBean按照如下方式映射:NameSpace URI設定成對象ID,Local Part設定成JavaBean完整的類名。我們部署HelloWorld服務時正是按照這個要求進行,所以一切都不存在問題。

invoke()方法的剩餘部分相當簡單:設定Call對象參數,設定定製SOAP映射註冊項(如果有必要的話),發出調用,接收方法調用的傳回值。如下所示:


if( params.size() != 0 )
call.setParams(params);
if( smr != null )
call.setSOAPMappingRegistry(smr);
// 發出調用
Response resp = call.invoke(serverURL, "");
if( !resp.generatedFault() )
{
Parameter ret = resp.getReturnValue();
return(ret.getValue());
}
else
{
Fault fault = resp.getFault();
throw new
SOAPException(fault.getFaultCode(),fault.getFaultString());
}
二、HelloWorld服務
下面是HelloWorld服務的完整代碼。有似曾相識的感覺嗎?


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?";
}
}
回憶一下,Name是一個簡單的JavaBean,代碼如下:


package hello;
public class Name
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
事實上,這裡服務的代碼與第二篇文章中的服務程式代碼完全一樣。對於服務開發人員來說,唯一增加的工作是建立Java介面。部署服務的方法也和第二篇文章中討論的完全一樣,所以這裡我不再介紹。相同的地方還不止如此,編譯和運行客戶程式的方法也和第二篇文章介紹的一樣。為什麼有這麼多相同之處呢?因為我們建立的代理是一個非插入式的架構,它不會修改和幹涉任何Apache SOAP組件的內部工作——無論是用戶端還是服務端。

三、其他說明
本文討論的SOAP代理(可以從文章後面下載)支援以下參數類型:

⑴ 下面的Java基礎資料型別 (Elementary Data Type)及其對應的對象形式。


boolean, Boolean,
double, Double,
float, Float,
long, Long,
int, Integer,
short, Short,
byte, Byte
註:伺服器端總是接收基礎資料型別 (Elementary Data Type)。

⑵ 任何JavaBean

註:

  • 該JavaBean不能包含其他JavaBean。
  • 如果數組或向量包含除字串或1列出資料類型之外的類型,則JavaBean不能包含這類數組或向量。
      ⑶ 下面的類:String, Vector

      註:

    • Vector可以包含1、2列出的所有類型和字串。
    • 伺服器端把Vector作為一個對象的數組接收。
        ⑷ 數組。數組元素可以是在1、2中列出的所有類型和字串(上面已註明的除外)。

        ■ 結束語
        在這個四篇文章構成的系列中,我不僅介紹了SOAP的基礎知識,而且介紹了SOAP 1.1標準的一個優秀的實現:Apache SOAP。在本文中,我提供了一個以動態代理類為基礎的架構,這個架構極大地簡化了使用Apache SOAP的客戶程式開發人員的工作。

        我深切地感到SOAP有著美好的前景,至少有兩個理由使我這麼認為:首先,SOAP以一些開放的標準為基礎,比如XML。這使得無論是Microsoft,還是反Microsoft的企業,都廣泛地接受了SOAP。對於開發人員來說,這無疑是一個天大的好訊息。第二,SOAP正在成為其他許多標準的基礎,比如UDDI(Universal Description,Discovery,and Integration)。許多人認為,Web服務代表著下一代的Web應用開發,而SOAP和UDDI都是Web服務的關鍵組成部分。

        ■ 參考資源
      • 下載本文的完整代碼:JavaAndSOAP4_code.zip
      • W3C的SOAP 1.1規範:
      • http://www.w3.org/TR/SOAP/
      • 有關動態代理類的更多資訊:
      • http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html
      • 關於IBM SOAP工程的更多資訊:
      • http://www.alphaworks.ibm.com/tech/soap4j
      • 下載Apache SOAP:
      • http://xml.apache.org/dist/soap/


        • 相關文章

          E-Commerce Solutions

          Leverage the same tools powering the Alibaba Ecosystem

          Learn more >

          Apsara Conference 2019

          The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

          Learn more >

          Alibaba Cloud Free Trial

          Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

          Learn more >

          聯繫我們

          該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

          如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。