10.3 代理模式 代理模式是常用的Java設(shè)計模式,它的特征是代理類與委托類有同樣的接口,如圖10-2所示。代理類主要負責為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理消息等。代理類與委托類之間通常會存在關(guān)聯(lián)關(guān)系,一個代理類的對象與一個委托類的對象關(guān)聯(lián),代理類的對象本身并不真正實現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,來提供特定的服務(wù)。 圖10-2 代理模式 按照代理類的創(chuàng)建時期,代理類可分為兩種。 ◆靜態(tài)代理類:由程序員創(chuàng)建或由特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經(jīng)存在了。 ◆動態(tài)代理類:在程序運行時,運用反射機制動態(tài)創(chuàng)建而成。 10.3.1 靜態(tài)代理類 如圖10-3所示,HelloServiceProxy類(如例程10-13所示)是代理類,HelloServiceImpl類(如例程10-12所示)是委托類,這兩個類都實現(xiàn)了HelloService接口(如例程10-11所示)。其中HelloServiceImpl類是HelloService接口的真正實現(xiàn)者,而HelloServiceProxy類是通過調(diào)用HelloServiceImpl類的相關(guān)方法來提供特定服務(wù)的。HelloServiceProxy類的echo()方法和getTime()方法會分別調(diào)用被代理的HelloServiceImpl對象的echo()方法和getTime()方法,并且在方法調(diào)用前后都會執(zhí)行一些簡單的打印操作。由此可見,代理類可以為委托類預(yù)處理消息、把消息轉(zhuǎn)發(fā)給委托類和事后處理消息等。 圖10-3 HelloServiceProxy類是HelloService的代理類 例程10-11 HelloService.java
例程10-12 HelloServiceImpl.java
例程10-13 HelloServiceProxy.java
在Client1類(如例程10-14所示)的main()方法中,先創(chuàng)建了一個HelloServiceImpl對象,又創(chuàng)建了一個HelloServiceProxy對象,最后調(diào)用HelloServiceProxy對象的echo()方法。 例程10-14 Client1.java package proxy; 運行Client1類,打印結(jié)果如下: before calling echo() 如圖10-4所示顯示了Client1調(diào)用HelloServiceProxy類的echo()方法的時序圖。 圖10-4 Client1調(diào)用HelloServiceProxy類的echo()方法的時序圖 例程10-13的HelloServiceProxy類的源代碼是由程序員編寫的,在程序運行前,它的.class文件就已經(jīng)存在了,這種代理類稱為靜態(tài)代理類。 10.3.2 動態(tài)代理類 與靜態(tài)代理類對照的是動態(tài)代理類,動態(tài)代理類的字節(jié)碼在程序運行時由Java反射機制動態(tài)生成,無需程序員手工編寫它的源代碼。動態(tài)代理類不僅簡化了編程工作,而且提高了軟件系統(tǒng)的可擴展性,因為Java反射機制可以生成任意類型的動態(tài)代理類。java.lang.reflect包中的Proxy類和InvocationHandler接口提供了生成動態(tài)代理類的能力。 Proxy類提供了創(chuàng)建動態(tài)代理類及其實例的靜態(tài)方法。 (1)getProxyClass()靜態(tài)方法負責創(chuàng)建動態(tài)代理類,它的完整定義如下: public static Class getProxyClass(ClassLoader loader, Class[] interfaces) 參數(shù)loader指定動態(tài)代理類的類加載器,參數(shù)interfaces指定動態(tài)代理類需要實現(xiàn)的所有接口。 (2)newProxyInstance()靜態(tài)方法負責創(chuàng)建動態(tài)代理類的實例,它的完整定義如下: public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 參數(shù)loader指定動態(tài)代理類的類加載器,參數(shù)interfaces指定動態(tài)代理類需要實現(xiàn)的所有接口,參數(shù)handler指定與動態(tài)代理類關(guān)聯(lián)的 InvocationHandler對象。 以下兩種方式都創(chuàng)建了實現(xiàn)Foo接口的動態(tài)代理類的實例: /**** 方式一 ****/ 由Proxy類的靜態(tài)方法創(chuàng)建的動態(tài)代理類具有以下特點: ◆動態(tài)代理類是public、final和非抽象類型的; 由Proxy類的靜態(tài)方法創(chuàng)建的動態(tài)代理類的實例具有以下特點: ◆假定變量foo是一個動態(tài)代理類的實例,并且這個動態(tài)代理類實現(xiàn)了Foo接口,那么“foo instanceof Foo”的值為true。把變量foo強制轉(zhuǎn)換為Foo類型是合法的: (Foo) foo //合法 ◆每個動態(tài)代理類實例都和一個InvocationHandler實例關(guān)聯(lián)。Proxy類的getInvocationHandler(Object proxy)靜態(tài)方法返回與參數(shù)proxy指定的代理類實例所關(guān)聯(lián)的InvocationHandler對象。 InvocationHandler接口為方法調(diào)用接口,它聲明了負責調(diào)用任意一個方法的invoke()方法: Object invoke(Object proxy,Method method,Object[] args) throws Throwable 參數(shù)proxy指定動態(tài)代理類實例,參數(shù)method指定被調(diào)用的方法,參數(shù)args指定向被調(diào)用方法傳遞的參數(shù),invoke()方法的返回值表示被調(diào)用方法的返回值。 如圖10-5所示,HelloServiceProxyFactory類(如例程10-15所示)的getHello- ServiceProxy()靜態(tài)方法負責創(chuàng)建實現(xiàn)了HelloService接口的動態(tài)代理類的實例。 圖10-5 HelloServiceProxyFactory類創(chuàng)建動態(tài)代理類實例 例程10-15 HelloServiceProxyFactory.java package proxy; Class classType=HelloService.class; 如例程10-16所示的Client2類先創(chuàng)建了一個HelloServiceImpl實例,然后創(chuàng)建了一個動態(tài)代理類實例helloServiceProxy,最后調(diào)用動態(tài)代理類實例的echo()方法。 例程10-16 Client2.java package proxy; 運行Client2,打印結(jié)果如下: 動態(tài)代理類的名字為$Proxy0 從以上打印結(jié)果看出,動態(tài)代理類的名字為$Proxy0。如圖10-6所示顯示了Client2調(diào)用動態(tài)代理類$Proxy0的實例helloServiceProxy的echo()方法的時序圖。 圖10-6 Client2調(diào)用動態(tài)代理類$Proxy0的echo()方法的時序圖 10.3.3 在遠程方法調(diào)用中運用代理類 如圖10-7所示,SimpleClient客戶端通過HelloService代理類來調(diào)用SimpleServer服務(wù)器端的HelloServiceImpl對象的方法??蛻舳说腍elloService代理類也實現(xiàn)了HelloService接口,這可以簡化SimpleClient客戶端的編程。對于SimpleClient客戶端而言,與遠程服務(wù)器的通信的細節(jié)被封裝到HelloService代理類中。SimpleClient客戶端可以按照以下方式調(diào)用遠程服務(wù)器上的HelloServiceImpl對象的方法: //創(chuàng)建HelloService代理類的對象 從以上程序代碼可以看出,SimpleClient客戶端調(diào)用遠程對象的方法的代碼與調(diào)用本地對象的方法的代碼很相似,由此可以看出,代理類簡化了客戶端的編程。 圖10-7 SimpleClient通過HelloService代理類調(diào)用遠程對象的方法 Connector類負責建立與遠程服務(wù)器的連接,以及接收和發(fā)送Socket對象。如例程10-17所示是Connector類的源程序。 例程10-17 Connector.java package proxy1; public Connector(String host,int port)throws Exception{ public void send(Object obj)throws Exception{ //發(fā)送對象 HelloService代理類有兩種創(chuàng)建方式:一種方式是創(chuàng)建一個HelloServiceProxy靜態(tài)代理類,如例程10-18所示;還有一種方式是創(chuàng)建HelloService的動態(tài)代理類,如例程10-19所示ProxyFactory類的靜態(tài)getProxy()方法就負責創(chuàng)建HelloService的動態(tài)代理類,并且返回它的一個實例。 例程10-18 HelloServiceProxy.java(靜態(tài)代理類) package proxy1; public Date getTime()throws RemoteException{ 例程10-19 ProxyFactory.java(負責創(chuàng)建動態(tài)代理類及其實例) package proxy1; return Proxy.newProxyInstance(classType.getClassLoader(), 無論HelloService的靜態(tài)代理類還是動態(tài)代理類,都通過Connector類來發(fā)送和接收Call對象。ProxyFactory工廠類的getProxy()方法的第一個參數(shù)classType指定代理類實現(xiàn)的接口的類型,如果參數(shù)classType的取值為HelloService.class,那么getProxy()方法就創(chuàng)建HelloService動態(tài)代理類的實例。如果參數(shù)classType的取值為Foo.class,那么getProxy()方法就創(chuàng)建Foo動態(tài)代理類的實例。由此可見,getProxy()方法可以創(chuàng)建任意類型的動態(tài)代理類的實例,并且它們都具有調(diào)用被代理的遠程對象的方法的能力。 如果使用靜態(tài)代理方式,那么對于每一個需要代理的類,都要手工編寫靜態(tài)代理類的源代碼;如果使用動態(tài)代理方式,那么只要編寫一個動態(tài)代理工廠類,它就能自動創(chuàng)建各種類型的動態(tài)代理類,從而大大簡化了編程,并且提高了軟件系統(tǒng)的可擴展性和可維護性。 如例程10-20所示的SimpleClient類的main()方法中,分別通過靜態(tài)代理類和動態(tài)代理類去訪問SimpleServer服務(wù)器上的HelloServiceImpl對象的各種方法。 例程10-20 SimpleClient.java package proxy1; public class SimpleClient { //創(chuàng)建動態(tài)代理類實例 先運行命令“java proxy1.SimpleServer”,再運行命令“java proxy1.SimpleClient”,SimpleClient端的打印結(jié)果如下: echo:hello proxy1.SimpleServer類的源程序與本章10.2節(jié)的例程10-9的remotecall.Simple- Server類相同。如圖10-8和圖10-9所示分別顯示了SimpleClient通過HelloService靜態(tài)代理類和動態(tài)代理類訪問遠程HelloServiceImpl對象的echo()方法的時序圖。 圖10-8 SimpleClient通過HelloService靜態(tài)代理類(HelloServiceProxy)訪問遠程對象的方法的時序圖 圖10-9 SimpleClient通過HelloService動態(tài)代理類($Proxy0)訪問遠程對象的方法的時序圖 10.4 小結(jié) Java反射機制是Java語言的一個重要特性??紤]實現(xiàn)一個newInstance(String className)方法,它的作用是根據(jù)參數(shù)className指定的類名,通過該類的不帶參數(shù)的構(gòu)造方法創(chuàng)建這個類的對象,并將其返回。如果不運用Java反射機制,必須在newInstance()方法中羅列參數(shù)className所有可能的取值,然后創(chuàng)建相應(yīng)的對象: public Object newInstance(String className) throws Exception{ 以上程序代碼很冗長,而且可維護性差。如果在以后軟件的升級版本中去除了一個HelloService4類,或者增加了一個HelloService1001類,都需要修改以上newInstance()方法。 如果運用反射機制,就可以簡化程序代碼,并且提高軟件系統(tǒng)的可維護性和可擴展性: public Object newInstance(String className) throws Exception{ Java反射機制在服務(wù)器程序和中間件程序中得到了廣泛運用。在服務(wù)器端,往往需要根據(jù)客戶的請求,動態(tài)調(diào)用某一個對象的特定方法。此外,有一種ORM(Object-Relation Mapping,對象-關(guān)系映射)中間件能夠把任意一個JavaBean持久化到關(guān)系數(shù)據(jù)庫中。在ORM中間件的實現(xiàn)中,運用Java反射機制來讀取任意一個JavaBean的所有屬性,或者給這些屬性賦值。在作者的另一本書《精通Hibernate:Java對象持久化技術(shù)詳解》中闡述了Java反射機制在Hibernate(一種ORM中間件)的實現(xiàn)中的運用。 在JDK類庫中,主要由以下類來實現(xiàn)Java反射機制,這些類都位于java.lang.reflect包中。 ◆Class類:代表一個類。 本章還介紹了Java反射機制、靜態(tài)代理模式和動態(tài)代理模式在遠程方法調(diào)用中的運用。本章以SimpleClient客戶調(diào)用SimpleServer服務(wù)器上的HelloServiceImpl對象的方法為例,探討了實現(xiàn)遠程方法調(diào)用的一些技巧。本書第11章介紹的RMI框架是JDK類庫提供的一個現(xiàn)成的完善的遠程方法調(diào)用框架。即使程序員不了解這個框架本身的實現(xiàn)細節(jié),也能使用這個框架。不過,熟悉框架本身的實現(xiàn)原理,可以幫助程序員更嫻熟地運用RMI框架。本章對實現(xiàn)遠程方法調(diào)用所作的初步探討,有助于程序員去進一步探索RMI框架本身的實現(xiàn)原理。 10.5 練習題 1.假定Tester類有如下test方法: public int test(int p1, Integer p2) 以下哪段代碼能正確地動態(tài)調(diào)用一個Tester對象的test方法?(單選) A. Class classType=Tester.class; B. Class classType=Tester.class; Class classType=Tester.class; D. Class classType=Tester.class; A.getConstructors() B.getPrivateMethods() C.getDeclaredFields() 3.以下哪些說法正確?(多選) A.動態(tài)代理類與靜態(tài)代理類一樣,必須由開發(fā)人員編寫源代碼,并編譯成.class文件 4.以下哪些屬于動態(tài)代理類的特點?(多選) A.動態(tài)代理類是public、final和非抽象類型的 5.在本章10.3.3節(jié)(在遠程方法調(diào)用中運用代理類)介紹的例子中,Connector類位于服務(wù)器端還是客戶端?(單選) A.服務(wù)器端 B.客戶端 6.在本章10.3.3節(jié)(在遠程方法調(diào)用中運用代理類)介紹的例子中,HelloServiceProxy類位于服務(wù)器端還是客戶端?(單選) A.服務(wù)器端 B.客戶端 7.運用本章介紹的動態(tài)代理機制,重新實現(xiàn)第1章的EchoServer服務(wù)器與EchoClient客戶,具體實現(xiàn)方式參照本章10.3.3節(jié)(在遠程方法調(diào)用中運用代理類)所介紹的例子。 答案:1.C 2.AC 3.BCD 4.ABCE 5.B 6.B |
|
來自: dengxianzhi > 《java》