Java理論與實踐 用動態(tài)代理進行修飾_第1頁
Java理論與實踐 用動態(tài)代理進行修飾_第2頁
Java理論與實踐 用動態(tài)代理進行修飾_第3頁
Java理論與實踐 用動態(tài)代理進行修飾_第4頁
Java理論與實踐 用動態(tài)代理進行修飾_第5頁
已閱讀5頁,還剩3頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、Java 理論與實踐: 用動態(tài)代理進行修飾動態(tài)代理為實現許多常見設計模式(包括 Facade、Bridge、Interceptor、Decorator、Proxy(包括遠程和虛擬代理)和 Adapter 模式)提供了替代的動態(tài)機制。雖然這些模式不使用動態(tài)代理,只用普通的類就能夠實現,但是在許多情況下,動態(tài)代理方式更方便、更緊湊,可以清除許多手寫或生成的類。Proxy 模式Proxy 模式中要創(chuàng)建“stub”或“surrogate”對象,它們的目的是接受請求并把請求轉發(fā)到實際執(zhí)行工作的其他對象。遠程方法調用(RMI)利用 Proxy模式,使得在其他 JVM 中執(zhí)行的對象就像本地對象一樣;企業(yè) J

2、avaBeans(EJB)利用 Proxy 模式添加遠程調用、安全性和事務分界;而 JAX-RPC Web服務則用 Proxy 模式讓遠程服務表現得像本地對象一樣。在每一種情況中,潛在的遠程對象的行為是由接口定義的,而接口本質上接受多種實現。調用者(在大多數情況下)不能區(qū)分出它們只是持有一個對 stub 而不是實際對象的引用,因為二者實現了相同的接口;stub 的工作是查找實際的對象、封送參數、把參數發(fā)送給實際對象、解除封送返回值、把返回值返回給調用者。代理可以用來提供遠程控制(就像在 RMI、EJB 和 JAX-RPC 中那樣),用安全性策略包裝對象(EJB)、為昂貴的對象(EJB 實體 B

3、ean)提供惰性裝入,或者添加檢測工具(例如日志記錄)。在 5.0 以前的 JDK 中,RMI stub(以及它對等的 skeleton)是在編譯時由 RMI 編譯器(rmic)生成的類,RMI 編譯器是 JDK 工具集的一部分。對于每個遠程接口,都會生成一個 stub(代理)類,它代表遠程對象,還生成一個skeleton 對象,它在遠程 JVM 中做與 stub 相反的工作 解除封送參數并調用實際的對象。類似地,用于 Web 服務的 JAX-RPC 工具也為遠程 Web 服務生成代理類,從而使遠程 Web 服務看起來就像本地對象一樣。不管 stub 類是以源代碼還是以字節(jié)碼生成的,代碼生成仍

4、然會向編譯過程添加一些額外步驟,而且因為命名相似的類的泛濫,會帶來意義模糊的可能性。另一方面,動態(tài)代理機制支持在編譯時沒有生成 stub 類的情況下,在運行時創(chuàng)建代理對象。在 JDK 5.0 及以后版本中,RMI 工具使用動態(tài)代理代替了生成的 stub,結果 RMI 變得更容易使用。許多 J2EE 容器也使用動態(tài)代理來實現 EJB。EJB 技術嚴重地依靠使用攔截(interception)來實現安全性和事務分界;動態(tài)代理為接口上調用的所有方法提供了集中的控制流程路徑。動態(tài)代理機制動態(tài)代理機制的核心是 InvocationHandler 接口,如清單 1 所示。調用句柄的工作是代表動態(tài)代理實際執(zhí)

5、行所請求的方法調用。傳遞給調用句柄一個Method 對象(從 java.lang.reflect 包),參數列表則傳遞給方法;在最簡單的情況下,可能僅僅是調用反射性的方法 Method.invoke() 并返回結果。清單 1. InvocationHandler 接口public interface InvocationHandler Object invoke(Object proxy, Method method, Object args)throws Throwable;每個代理都有一個與之關聯(lián)的調用句柄,只要代理的方法被調用時就會調用該句柄。根據通用的設計原則:接口定義類型、類定義實現

6、,代理對象可以實現一個或多個接口,但是不能實現類。因為代理類沒有可以訪問的名稱,它們不能有構造函數,所以它們必須由工廠創(chuàng)建。清單 2 顯示了動態(tài)代理的最簡單的可能實現,它實現 Set 接口并把所有 Set 方法(以及所有 Object 方法)分派給封裝的 Set 實例。清單 2. 包裝 Set 的簡單的動態(tài)代理public class SetProxyFactory public static Set getSetProxy(final Set s) return (Set) Proxy.newProxyInstance(s.getClass().getClassLoader(),new Cl

7、ass Set.class ,new InvocationHandler() public Object invoke(Object proxy, Method method,Object args) throws Throwable return method.invoke(s, args););SetProxyFactory 類包含一個靜態(tài)工廠方法 getSetProxy(),它返回一個實現了 Set 的動態(tài)代理。代理對象實際實現 Set 調用者無法區(qū)分(除非通過反射)返回的對象是動態(tài)代理。SetProxyFactory 返回的代理只做一件事,把方法分派給傳遞給工廠方法的 Set 實例。雖

8、然反射代碼通常比較難讀,但是這里的內容很少,跟上控制流程并不難 只要某個方法在 Set 代理上被調用,它就被分派給調用句柄,調用句柄只是反射地調用底層包裝的對象上的目標方法。當然,絕對什么都不做的代理可能有點傻,是不是呢?什么都不做的適配器對于像 SetProxyFactory 這樣什么都不做的包裝器來說,實際有個很好的應用 可以用它安全地把對象引用的范圍縮小到特定接口(或接口集)上,方式是,調用者不能提升引用的類型,使得可以更安全地把對象引用傳遞給不受信任的代碼(例如插件或回調)。清單 3 包含一組類定義,實現了典型的回調場景。從中會看到動態(tài)代理可以更方便地替代通常用手工(或用 IDE 提供

9、的代碼生成向導)實現的 Adapter 模式。清單 3. 典型的回調場景public interface ServiceCallback public void doCallback();public interface Service public void serviceMethod(ServiceCallback callback);public class ServiceConsumer implements ServiceCallback private Service service;.public void someMethod() .service.serviceMethod(

10、this);ServiceConsumer 類實現了 ServiceCallback(這通常是支持回調的一個方便途徑)并把 this 引用傳遞給 serviceMethod() 作為回調引用。這種方法的問題是沒有機制可以阻止 Service 實現把 ServiceCallback 提升為ServiceConsumer,并調用 ServiceConsumer 不希望 Service 調用的方法。有時對這個風險并不關心 但有時卻關心。如果關心,那么可以把回調對象作為內部類,或者編寫一個什么都不做的適配器類(請參閱清單 4 中的ServiceCallbackAdapter)并用 ServiceCal

11、lbackAdapter 包裝ServiceConsumer。ServiceCallbackAdapter 防止 Service 把ServiceCallback 提升為 ServiceConsumer。清單 4. 用于安全地把對象限制在一個接口上以便不被惡意代碼不能的適配器類public class ServiceCallbackAdapter implements ServiceCallback private final ServiceCallback cb;public ServiceCallbackAdapter(ServiceCallback cb) this.cb = cb;pu

12、blic void doCallback() cb.doCallback();編寫 ServiceCallbackAdapter 這樣的適配器類簡單卻乏味。必須為包裝的接口中的每個方法編寫重定向類。在 ServiceCallback 的示例中,只有一個需要實現的方法,但是某些接口,例如 Collections 或 JDBC 接口,則包含許多方法?,F代的 IDE 提供了“Delegate Methods”向導,降低了編寫適配器類的工作量,但是仍然必須為每個想要包裝的接口編寫一個適配器類,而且對于只包含生成的代碼的類,也有一些讓人不滿意的地方??雌饋響斢幸环N方式可以更緊湊地表示“什么也不做的限制

13、適配器模式”。通用適配器類清單 2 中的 SetProxyFactory 類當然比用于 Set 的等價的適配器類更緊湊,但是它仍然只適用于一個接口:Set。但是通過使用泛型,可以容易地創(chuàng)建通用的代理工廠,由它為任何接口做同樣的工作,如清單 5 所示。它幾乎與SetProxyFactory 相同,但是可以適用于任何接口?,F在再也不用編寫限制適配器類了!如果想創(chuàng)建代理對象安全地把對象限制在接口 T,只要調用getProxy(T.class,object) 就可以了,不需要一堆適配器類的額外累贅。清單 5. 通用的限制適配器工廠類public class GenericProxyFactory pu

14、blic static T getProxy(Class intf,final T obj) return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),new Class intf ,new InvocationHandler() public Object invoke(Object proxy, Method method,Object args) throws Throwable return method.invoke(obj, args););動態(tài)代理作為 Decorator當然,動態(tài)代理工具能做的,遠不僅僅是把

15、對象類型限制在特定接口上。從 清單 2 和 清單 5 中簡單的限制適配器到 Decorator 模式,是一個小的飛躍,在 Decorator 模式中,代理用額外的功能(例如安全檢測或日志記錄)包裝調用。清單 6 顯示了一個日志 InvocationHandler,它在調用目標對象上的方法之外,還寫入一條日志信息,顯示被調用的方法、傳遞的參數,以及返回值。除了反射性的 invoke() 調用之外,這里的全部代碼只是生成調試信息的一部分 還不是太多。代理工廠方法的代碼幾乎與 GenericProxyFactory相同,區(qū)別在于它使用的是 LoggingInvocationHandler 而不是匿名

16、的調用句柄。清單 6. 基于代理的 Decorator,為每個方法調用生成調試日志private static class LoggingInvocationHandlerimplements InvocationHandler final T underlying;public LoggingHandler(T underlying) this.underlying = underlying;public Object invoke(Object proxy, Method method,Object args) throws Throwable StringBuffer sb = new

17、StringBuffer();sb.append(method.getName(); sb.append();for (int i=0; args != null & i ); sb.append(ret);System.out.println(sb);return ret;如果用日志代理包裝 HashSet,并執(zhí)行下面這個簡單的測試程序:Set s = newLoggingProxy(Set.class, new HashSet();s.add(three);if (!s.contains(four)s.add(four);System.out.println(s);會得到以下輸出:

18、add(three) - truecontains(four) - falseadd(four) - truetoString() - four, threefour, three這種方式是給對象添加調試包裝器的一種好的而且容易的方式。它當然比生成代理類并手工創(chuàng)建大量 println() 語句容易得多(也更通用)。我進一步改進了這一方法;不必無條件地生成調試輸出,相反,代理可以查詢動態(tài)配置存儲(從配置文件初始化,可以由 JMX MBean 動態(tài)修改),確定是否需要生成調試語句,甚至可能在逐個類或逐個實例的基礎上進行。在這一點上,我認為讀者中的 AOP 愛好者們幾乎要跳出來說“這正是 AOP擅長

19、的?。 笔堑?,但是解決問題的方法不止一種 僅僅因為某項技術能解決某個問題,并不意味著它就是最好的解決方案。在任何情況下,動態(tài)代理方式都有完全在“純 Java”范圍內工作的優(yōu)勢,不是每個公司都用(或應當用) AOP 的。動態(tài)代理作為適配器代理也可以用作真正的適配器,提供了對象的一個視圖,導出與底層對象實現的接口不同的接口。調用句柄不需要把每個方法調用都分派給相同的底層對象;它可以檢查名稱,并把不同的方法分派給不同的對象。例如,假設有一組表示持久實體(Person、Company 和 PurchaseOrder) 的 JavaBean 接口,指定了屬性的 getter 和 setter,而且正在編

20、寫一個持久層,把數據庫記錄映射到實現這些接口的對象上。現在不用為每個接口編寫或生成類,可以只用一個 JavaBean 風格的通用代理類,把屬性保存在 Map 中。清單 7 顯示的動態(tài)代理檢查被調用方法的名稱,并通過查詢或修改屬性圖直接實現 getter 和 setter 方法?,F在,這一個代理類就能實現多個JavaBean 風格接口的對象。清單 7. 用于把 getter 和 setter 分派給 Map 的動態(tài)代理類public class JavaBeanProxyFactory private static class JavaBeanProxy implements Invocatio

21、nHandler Map properties = new HashMap();public JavaBeanProxy(Map properties) perties.putAll(properties);public Object invoke(Object proxy, Method method,Object args)throws Throwable String meth = method.getName();if (meth.startsWith(get) String prop = meth.substring(3);Object o = properties.

22、get(prop);if (o != null & !method.getReturnType().isInstance(o)throw new ClassCastException(o.getClass().getName() + is not a + method.getReturnType().getName();return o;else if (meth.startsWith(set) / Dispatch setters similarlyelse if (meth.startsWith(is) / Alternate version of get for boolean

23、propertieselse / Can dispatch non get/set/is methods as desiredpublic static T getProxy(Class intf,Map values) return (T) Proxy.newProxyInstance(JavaBeanProxyFactory.class.getClassLoader(),new Class intf , new JavaBeanProxy(values);雖然因為反射在 Object 上工作會有潛在的類型安全性上的損失,但是,JavaBeanProxyFactory 中的 getter 處理會進行一些必要的額外的類型檢測,就像我在這里用 isInstance() 對 getter 進行的檢測一樣。性能成本正如已經看到的,動態(tài)代理擁有簡化大量代碼的潛力 不僅能替代許多生成的代碼,而且一個代理類還

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論