Java 2平臺1.3版本為Java映像API(Reflection API)增加了一個極其實(shí)用的擴(kuò)展:動態(tài)代理類。一個動態(tài)代理類就是一個實(shí)現(xiàn)了一系列運(yùn)行時指定的接口的類。這個代理可以象它真正實(shí)現(xiàn)了這些接口一樣使用。換句話說,可以直接在代理對象上調(diào)用任意接口的任意方法——當(dāng)然,必須先進(jìn)行必要的類型定型(casting)。由此,我們可以用動態(tài)代理類為一組接口創(chuàng)建一個類型安全的代理對象,且不必象使用編譯時工具一樣預(yù)先生成代理(有關(guān)動態(tài)代理類更詳細(xì)的說明,請參見本文最后的參考資源)。
接下來我將介紹一個以動態(tài)代理類為基礎(chǔ)的框架,這個框架使得SOAP(簡單對象訪問協(xié)議)客戶程序的創(chuàng)建更加簡單和直觀。SOAP是一種用XML編碼數(shù)據(jù)的有線協(xié)議。在本系列文章的第二篇、第三篇構(gòu)造SOAP服務(wù)的過程中,我們發(fā)現(xiàn)客戶程序的開發(fā)者必須多做許多原來不必做的工作。為幫助回憶,你可以看一下第二篇文章中的SOAP服務(wù)代碼,看看和客戶程序代碼相比較時,服務(wù)程序的SOAP代碼是多么微不足道。本系列文章前幾篇所創(chuàng)建的簡單SOAP服務(wù)顯示出,基于SOAP的服務(wù)只包含無論用不用SOAP都必須提供的代碼。服務(wù)程序的開發(fā)者要編寫的額外代碼很少,而客戶程序開發(fā)者卻有許多額外工作要做。本文介紹的類將把這些額外工作減到最少。
一、介紹SOAP代理類
首先,我要給出如果客戶程序使用了本文創(chuàng)建的框架,它將變成什么樣子:
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));
// 調(diào)用sayHelloTo方法
// 這個sayHelloTo方法需要一個字符串參數(shù)
System.out.println(hello.sayHelloTo("John"));
// 調(diào)用sayHelloTo方法
// 這個sayHelloTo方法需要一個Name JavaBean參數(shù)
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
|
也許是出于我的個人愛好,我認(rèn)為上面的客戶代碼比第二篇和第三篇文章中的客戶代碼更好。如果你現(xiàn)在不能理解上面的代碼,這很正常,但我想待到本文結(jié)束時你會理解的。
要理解客戶程序的代碼,你必須深入了解SOAP Proxy類,它在soapproxy包內(nèi),可以在Proxy.java內(nèi)找到(參見本文最后的參考資源)。Proxy類有一個私有的構(gòu)造函數(shù),它意味著Proxy實(shí)例不能從Proxy之外創(chuàng)建;新建Proxy實(shí)例的唯一方法是通過靜態(tài)的newInstance()方法。newInstance()方法有兩個參數(shù):SOAP服務(wù)的對象ID,以及一個數(shù)組,數(shù)組中包含一組該代理要實(shí)現(xiàn)的接口的名字。對象ID很簡單,但這些接口名字是什么?從哪里去得到這些接口的名字?SOAP服務(wù)的開發(fā)者直接把服務(wù)上所有可被客戶程序調(diào)用的方法堆在一起得到一個接口。相當(dāng)簡單,不是嗎?
現(xiàn)在我們?yōu)镠elloWorld服務(wù)定義一個接口。第二篇文章中,這個服務(wù)的最終版本有sayHelloTo()方法的兩個重載版本:一個版本的參數(shù)是一個字符串,另一個版本的參數(shù)是一個Name JavaBean。這兩個方法就可以構(gòu)成一個接口,稱為Hello,如下所示:
package hello;
public interface Hello
{
public String sayHelloTo(String name);
public String sayHelloTo(Name name);
} |
服務(wù)開發(fā)者決定要創(chuàng)建多少接口,以及為這些接口取什么樣的名字。例如,你可以為HelloWorld服務(wù)創(chuàng)建兩個接口,每一個接口包含一個方法。一般地,你應(yīng)該避免創(chuàng)建方法數(shù)量大于七個的接口。另外,注意只把那些看來有必要放在一起的方法用一個接口組織起來。例如,如果HelloWorld服務(wù)還有一個返回定制的Good-Bye消息給調(diào)用者的sayByeTo()方法,設(shè)計兩個獨(dú)立的接口也許比較明智:一個接口用于sayHelloTo()方法,一個接口用于sayByeTo()方法。
現(xiàn)在我們有了定義HelloWorld服務(wù)和客戶程序之間契約的接口,下面返回來看newInstance()方法。如前所述,newInstance()方法創(chuàng)建Proxy類的一個新實(shí)例。newInstance()方法可以創(chuàng)建新實(shí)例是因為它屬于Proxy類,能夠訪問私有的構(gòu)造函數(shù)。newInstance()方法為新創(chuàng)建的實(shí)例調(diào)用initialize()方法。initialize()值得關(guān)注,因為動態(tài)代理就是在這里創(chuàng)建和返回。initialize()的代碼如下所示:
private Object initialize(Class[] interfaces)
{
return(java.lang.reflect.Proxy.newProxyInstance(getClass().getClassLoader()
,interfaces,this));
} |
注意newProxyInstance()方法的應(yīng)用。創(chuàng)建動態(tài)代理類實(shí)例的唯一辦法是調(diào)用該類(即java.lang.reflect.Proxy類)靜態(tài)的newProxyInstance()方法。java.lang.reflect.Proxy類為創(chuàng)建動態(tài)代理類提供了靜態(tài)方法,而且它還是所有由這些方法創(chuàng)建的動態(tài)代理類的超類。換句話說,它不僅是一個創(chuàng)建動態(tài)代理類的工廠,而且它本身也是一個動態(tài)代理類!因此,在我們的例子中,SOAP代理不是動態(tài)代理;相反,這個動態(tài)代理實(shí)際上是newProxyInstance靜態(tài)方法返回的java.lang.reflect.Proxy類的一個實(shí)例。從本文后面可以看到,這個動態(tài)代理實(shí)際上通過SOAP代理實(shí)現(xiàn)的invoke()方法完成它的所有工作。那么,這個動態(tài)代理如何建立和SOAP代理的聯(lián)系呢?因為有一個對SOAP代理的引用傳遞給了newProxyInstance()方法。也許現(xiàn)在這聽起來有點(diǎn)費(fèi)解,但只要你分析一下invoke()方法,這一切就很明白了。
java.lang.reflect.Proxy類構(gòu)造函數(shù)的第一個參數(shù)是一個類裝載器實(shí)例,第二個參數(shù)是需要動態(tài)實(shí)現(xiàn)的接口的數(shù)組(它就是客戶程序傳遞給newInstance()的數(shù)組),第三個參數(shù)是一個實(shí)現(xiàn)了java.lang.reflect.InvocationHandler接口的類的實(shí)例。因為SOAP Proxy類實(shí)現(xiàn)了InvocationHandler接口,所以第三個參數(shù)是代理實(shí)例本身(即this)。InvocationHandler接口有一個方法invoke()。當(dāng)動態(tài)代理的動態(tài)實(shí)現(xiàn)的接口被調(diào)用時,Java運(yùn)行時環(huán)境調(diào)用invoke()方法。因此,舉例來說,當(dāng)客戶程序調(diào)用動態(tài)代理的Hello接口的sayHelloTo()方法時,Java運(yùn)行時環(huán)境將調(diào)用SOAP代理的invoke()方法。
你可能已經(jīng)發(fā)現(xiàn),SOAP代理的newInstance()方法不返回SOAP代理的實(shí)例;相反,它返回newInsance()剛剛創(chuàng)建的動態(tài)代理,而動態(tài)代理動態(tài)地實(shí)現(xiàn)客戶程序傳入的接口數(shù)組??蛻舫绦蚩梢詫⑦@個返回的動態(tài)代理定型為傳入newInstance()的任意接口類型,在動態(tài)代理上調(diào)用接口所定義的各個方法,就象動態(tài)代理真地實(shí)現(xiàn)了那些接口一樣。
.
.
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
// 調(diào)用參數(shù)為字符串的sayHelloTo方法
System.out.println(hello.sayHelloTo("John"));
// 調(diào)用參數(shù)為Name JavaBean的sayHelloTo方法
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
.
. |
在上面的代碼中,invoke()方法將被調(diào)用兩次,每次調(diào)用sayHelloTo()方法時執(zhí)行一次。現(xiàn)在我們來看看invoke()方法。簡而言之,invoke()方法的工作正是第二篇文章中每一個客戶程序必須手工完成的工作,其中包括:用合適的調(diào)用參數(shù)設(shè)置一個Call對象,定制的調(diào)用參數(shù)所需要的類型映射。由于SOAP代理中的invoke()方法擔(dān)負(fù)了所有這些任務(wù),客戶程序釋放了這份負(fù)擔(dān)。
在invoke()方法接收到的三個參數(shù)中,我們只對后面兩個感興趣。第二個參數(shù),即Method對象,給出了被調(diào)用方法的名字。記住,被調(diào)用方法的名字對應(yīng)著一個SOAP服務(wù)導(dǎo)出的已知方法。服務(wù)的對象ID作為參數(shù)傳遞給newInstance()方法,所以invoke()方法已經(jīng)擁有該對象ID。invoke()方法利用這些信息,按照如下方式設(shè)置Call對象:
Call call = new Call();
call.setTargetObjectURI(urn);
call.setMethodName(m.getName());
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); |
現(xiàn)在要做的是為遠(yuǎn)程服務(wù)調(diào)用設(shè)置參數(shù)。為此,我們要用到invoke()方法的第三個參數(shù):傳入動態(tài)代理上被調(diào)用方法的一個參數(shù)數(shù)組。數(shù)組中索引為0的參數(shù)是方法調(diào)用中最左邊的參數(shù),索引為1的參數(shù)是方法的第二個參數(shù),依此類推。舉例來說,如果客戶程序調(diào)用了sayHelloTo(String name)方法,那么參數(shù)數(shù)組就是包含一個字符串的數(shù)組。invoke()方法處理該數(shù)組的每一個元素,創(chuàng)建一個由Parameter對象構(gòu)成的向量(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));
}
// 如果這個數(shù)組的元素不屬于Java基本數(shù)據(jù)類型
// 則假定這是一個JavaBean的數(shù)組
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()來確定參數(shù)的類型。如果參數(shù)是一個JavaBean或者一個數(shù)組,那么,程序必須設(shè)置一個定制的SOAP映射注冊項,并通過setSOAPMappingRegistry()方法對Call對象作相應(yīng)的設(shè)置(參見第二篇文章)。SOAP代理假定,當(dāng)出現(xiàn)JavaBean時,SOAP服務(wù)用到的所有JavaBean按照如下方式映射:NameSpace URI設(shè)置成對象ID,Local Part設(shè)置成JavaBean完整的類名。我們部署HelloWorld服務(wù)時正是按照這個要求進(jìn)行,所以一切都不存在問題。
invoke()方法的剩余部分相當(dāng)簡單:設(shè)置Call對象參數(shù),設(shè)置定制SOAP映射注冊項(如果有必要的話),發(fā)出調(diào)用,接收方法調(diào)用的返回值。如下所示:
if( params.size() != 0 )
call.setParams(params);
if( smr != null )
call.setSOAPMappingRegistry(smr);
// 發(fā)出調(diào)用
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服務(wù)
下面是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?";
}
}
|
回憶一下,Name是一個簡單的JavaBean,代碼如下:
package hello;
public class Name
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
|
事實(shí)上,這里服務(wù)的代碼與第二篇文章中的服務(wù)程序代碼完全一樣。對于服務(wù)開發(fā)者來說,唯一增加的工作是創(chuàng)建Java接口。部署服務(wù)的方法也和第二篇文章中討論的完全一樣,所以這里我不再介紹。相同的地方還不止如此,編譯和運(yùn)行客戶程序的方法也和第二篇文章介紹的一樣。為什么有這么多相同之處呢?因為我們創(chuàng)建的代理是一個非插入式的框架,它不會修改和干涉任何Apache SOAP部件的內(nèi)部工作——無論是客戶端還是服務(wù)端。
三、其他說明
本文討論的SOAP代理(可以從文章后面下載)支持以下參數(shù)類型:
⑴ 下面的Java基本數(shù)據(jù)類型及其對應(yīng)的對象形式。
boolean, Boolean,
double, Double,
float, Float,
long, Long,
int, Integer,
short, Short,
byte, Byte
|
注:服務(wù)器端總是接收基本數(shù)據(jù)類型。
⑵ 任何JavaBean
注:
該JavaBean不能包含其他JavaBean。
如果數(shù)組或向量包含除字符串或1列出數(shù)據(jù)類型之外的類型,則JavaBean不能包含這類數(shù)組或向量。
⑶ 下面的類:String, Vector
注:
Vector可以包含1、2列出的所有類型和字符串。
服務(wù)器端把Vector作為一個對象的數(shù)組接收。
⑷ 數(shù)組。數(shù)組元素可以是在1、2中列出的所有類型和字符串(上面已注明的除外)。
■ 結(jié)束語
在這個四篇文章構(gòu)成的系列中,我不僅介紹了SOAP的基礎(chǔ)知識,而且介紹了SOAP 1.1標(biāo)準(zhǔn)的一個優(yōu)秀的實(shí)現(xiàn):Apache SOAP。在本文中,我提供了一個以動態(tài)代理類為基礎(chǔ)的框架,這個框架極大地簡化了使用Apache SOAP的客戶程序開發(fā)者的工作。
我深切地感到SOAP有著美好的前景,至少有兩個理由使我這么認(rèn)為:首先,SOAP以一些開放的標(biāo)準(zhǔn)為基礎(chǔ),比如XML。這使得無論是Microsoft,還是反Microsoft的企業(yè),都廣泛地接受了SOAP。對于開發(fā)者來說,這無疑是一個天大的好消息。第二,SOAP正在成為其他許多標(biāo)準(zhǔn)的基礎(chǔ),比如UDDI(Universal Description,Discovery,and Integration)。許多人認(rèn)為,Web服務(wù)代表著下一代的Web應(yīng)用開發(fā),而SOAP和UDDI都是Web服務(wù)的關(guān)鍵組成部分。
■ 參考資源
下載本文的完整代碼:JavaAndSOAP4_code.zip
W3C的SOAP 1.1規(guī)范:
http://www.w3.org/TR/SOAP/
有關(guān)動態(tài)代理類的更多信息:
http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html
關(guān)于IBM SOAP工程的更多信息:
http://www.alphaworks.ibm.com/tech/soap4j
下載Apache SOAP:
http://xml.apache.org/dist/soap/