版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、Java 理論與實(shí)踐: 用動(dòng)態(tài)代理進(jìn)行修飾動(dòng)態(tài)代理為實(shí)現(xiàn)許多常見設(shè)計(jì)模式(包括 Facade、Bridge、Interceptor、Decorator、Proxy(包括遠(yuǎn)程和虛擬代理)和 Adapter 模式)提供了替代的動(dòng)態(tài)機(jī)制。雖然這些模式不使用動(dòng)態(tài)代理,只用普通的類就能夠?qū)崿F(xiàn),但是在許多情況下,動(dòng)態(tài)代理方式更方便、更緊湊,可以清除許多手寫或生成的類。Proxy 模式Proxy 模式中要?jiǎng)?chuàng)建“stub”或“surrogate”對象,它們的目的是接受請求并把請求轉(zhuǎn)發(fā)到實(shí)際執(zhí)行工作的其他對象。遠(yuǎn)程方法調(diào)用(RMI)利用 Proxy模式,使得在其他 JVM 中執(zhí)行的對象就像本地對象一樣;企業(yè) J
2、avaBeans(EJB)利用 Proxy 模式添加遠(yuǎn)程調(diào)用、安全性和事務(wù)分界;而 JAX-RPC Web服務(wù)則用 Proxy 模式讓遠(yuǎn)程服務(wù)表現(xiàn)得像本地對象一樣。在每一種情況中,潛在的遠(yuǎn)程對象的行為是由接口定義的,而接口本質(zhì)上接受多種實(shí)現(xiàn)。調(diào)用者(在大多數(shù)情況下)不能區(qū)分出它們只是持有一個(gè)對 stub 而不是實(shí)際對象的引用,因?yàn)槎邔?shí)現(xiàn)了相同的接口;stub 的工作是查找實(shí)際的對象、封送參數(shù)、把參數(shù)發(fā)送給實(shí)際對象、解除封送返回值、把返回值返回給調(diào)用者。代理可以用來提供遠(yuǎn)程控制(就像在 RMI、EJB 和 JAX-RPC 中那樣),用安全性策略包裝對象(EJB)、為昂貴的對象(EJB 實(shí)體 B
3、ean)提供惰性裝入,或者添加檢測工具(例如日志記錄)。在 5.0 以前的 JDK 中,RMI stub(以及它對等的 skeleton)是在編譯時(shí)由 RMI 編譯器(rmic)生成的類,RMI 編譯器是 JDK 工具集的一部分。對于每個(gè)遠(yuǎn)程接口,都會生成一個(gè) stub(代理)類,它代表遠(yuǎn)程對象,還生成一個(gè)skeleton 對象,它在遠(yuǎn)程 JVM 中做與 stub 相反的工作 解除封送參數(shù)并調(diào)用實(shí)際的對象。類似地,用于 Web 服務(wù)的 JAX-RPC 工具也為遠(yuǎn)程 Web 服務(wù)生成代理類,從而使遠(yuǎn)程 Web 服務(wù)看起來就像本地對象一樣。不管 stub 類是以源代碼還是以字節(jié)碼生成的,代碼生成仍
4、然會向編譯過程添加一些額外步驟,而且因?yàn)槊嗨频念惖姆簽E,會帶來意義模糊的可能性。另一方面,動(dòng)態(tài)代理機(jī)制支持在編譯時(shí)沒有生成 stub 類的情況下,在運(yùn)行時(shí)創(chuàng)建代理對象。在 JDK 5.0 及以后版本中,RMI 工具使用動(dòng)態(tài)代理代替了生成的 stub,結(jié)果 RMI 變得更容易使用。許多 J2EE 容器也使用動(dòng)態(tài)代理來實(shí)現(xiàn) EJB。EJB 技術(shù)嚴(yán)重地依靠使用攔截(interception)來實(shí)現(xiàn)安全性和事務(wù)分界;動(dòng)態(tài)代理為接口上調(diào)用的所有方法提供了集中的控制流程路徑。動(dòng)態(tài)代理機(jī)制動(dòng)態(tài)代理機(jī)制的核心是 InvocationHandler 接口,如清單 1 所示。調(diào)用句柄的工作是代表動(dòng)態(tài)代理實(shí)際執(zhí)
5、行所請求的方法調(diào)用。傳遞給調(diào)用句柄一個(gè)Method 對象(從 java.lang.reflect 包),參數(shù)列表則傳遞給方法;在最簡單的情況下,可能僅僅是調(diào)用反射性的方法 Method.invoke() 并返回結(jié)果。清單 1. InvocationHandler 接口public interface InvocationHandler Object invoke(Object proxy, Method method, Object args)throws Throwable;每個(gè)代理都有一個(gè)與之關(guān)聯(lián)的調(diào)用句柄,只要代理的方法被調(diào)用時(shí)就會調(diào)用該句柄。根據(jù)通用的設(shè)計(jì)原則:接口定義類型、類定義實(shí)現(xiàn)
6、,代理對象可以實(shí)現(xiàn)一個(gè)或多個(gè)接口,但是不能實(shí)現(xiàn)類。因?yàn)榇眍悰]有可以訪問的名稱,它們不能有構(gòu)造函數(shù),所以它們必須由工廠創(chuàng)建。清單 2 顯示了動(dòng)態(tài)代理的最簡單的可能實(shí)現(xiàn),它實(shí)現(xiàn) Set 接口并把所有 Set 方法(以及所有 Object 方法)分派給封裝的 Set 實(shí)例。清單 2. 包裝 Set 的簡單的動(dòng)態(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 類包含一個(gè)靜態(tài)工廠方法 getSetProxy(),它返回一個(gè)實(shí)現(xiàn)了 Set 的動(dòng)態(tài)代理。代理對象實(shí)際實(shí)現(xiàn) Set 調(diào)用者無法區(qū)分(除非通過反射)返回的對象是動(dòng)態(tài)代理。SetProxyFactory 返回的代理只做一件事,把方法分派給傳遞給工廠方法的 Set 實(shí)例。雖
8、然反射代碼通常比較難讀,但是這里的內(nèi)容很少,跟上控制流程并不難 只要某個(gè)方法在 Set 代理上被調(diào)用,它就被分派給調(diào)用句柄,調(diào)用句柄只是反射地調(diào)用底層包裝的對象上的目標(biāo)方法。當(dāng)然,絕對什么都不做的代理可能有點(diǎn)傻,是不是呢?什么都不做的適配器對于像 SetProxyFactory 這樣什么都不做的包裝器來說,實(shí)際有個(gè)很好的應(yīng)用 可以用它安全地把對象引用的范圍縮小到特定接口(或接口集)上,方式是,調(diào)用者不能提升引用的類型,使得可以更安全地把對象引用傳遞給不受信任的代碼(例如插件或回調(diào))。清單 3 包含一組類定義,實(shí)現(xiàn)了典型的回調(diào)場景。從中會看到動(dòng)態(tài)代理可以更方便地替代通常用手工(或用 IDE 提供
9、的代碼生成向?qū)В?shí)現(xiàn)的 Adapter 模式。清單 3. 典型的回調(diào)場景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 類實(shí)現(xiàn)了 ServiceCallback(這通常是支持回調(diào)的一個(gè)方便途徑)并把 this 引用傳遞給 serviceMethod() 作為回調(diào)引用。這種方法的問題是沒有機(jī)制可以阻止 Service 實(shí)現(xiàn)把 ServiceCallback 提升為ServiceConsumer,并調(diào)用 ServiceConsumer 不希望 Service 調(diào)用的方法。有時(shí)對這個(gè)風(fēng)險(xiǎn)并不關(guān)心 但有時(shí)卻關(guān)心。如果關(guān)心,那么可以把回調(diào)對象作為內(nèi)部類,或者編寫一個(gè)什么都不做的適配器類(請參閱清單 4 中的ServiceCallbackAdapter)并用 ServiceCal
11、lbackAdapter 包裝ServiceConsumer。ServiceCallbackAdapter 防止 Service 把ServiceCallback 提升為 ServiceConsumer。清單 4. 用于安全地把對象限制在一個(gè)接口上以便不被惡意代碼不能的適配器類public class ServiceCallbackAdapter implements ServiceCallback private final ServiceCallback cb;public ServiceCallbackAdapter(ServiceCallback cb) this.cb = cb;pu
12、blic void doCallback() cb.doCallback();編寫 ServiceCallbackAdapter 這樣的適配器類簡單卻乏味。必須為包裝的接口中的每個(gè)方法編寫重定向類。在 ServiceCallback 的示例中,只有一個(gè)需要實(shí)現(xiàn)的方法,但是某些接口,例如 Collections 或 JDBC 接口,則包含許多方法。現(xiàn)代的 IDE 提供了“Delegate Methods”向?qū)?,降低了編寫適配器類的工作量,但是仍然必須為每個(gè)想要包裝的接口編寫一個(gè)適配器類,而且對于只包含生成的代碼的類,也有一些讓人不滿意的地方??雌饋響?yīng)當(dāng)有一種方式可以更緊湊地表示“什么也不做的限制
13、適配器模式”。通用適配器類清單 2 中的 SetProxyFactory 類當(dāng)然比用于 Set 的等價(jià)的適配器類更緊湊,但是它仍然只適用于一個(gè)接口:Set。但是通過使用泛型,可以容易地創(chuàng)建通用的代理工廠,由它為任何接口做同樣的工作,如清單 5 所示。它幾乎與SetProxyFactory 相同,但是可以適用于任何接口?,F(xiàn)在再也不用編寫限制適配器類了!如果想創(chuàng)建代理對象安全地把對象限制在接口 T,只要調(diào)用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););動(dòng)態(tài)代理作為 Decorator當(dāng)然,動(dòng)態(tài)代理工具能做的,遠(yuǎn)不僅僅是把
15、對象類型限制在特定接口上。從 清單 2 和 清單 5 中簡單的限制適配器到 Decorator 模式,是一個(gè)小的飛躍,在 Decorator 模式中,代理用額外的功能(例如安全檢測或日志記錄)包裝調(diào)用。清單 6 顯示了一個(gè)日志 InvocationHandler,它在調(diào)用目標(biāo)對象上的方法之外,還寫入一條日志信息,顯示被調(diào)用的方法、傳遞的參數(shù),以及返回值。除了反射性的 invoke() 調(diào)用之外,這里的全部代碼只是生成調(diào)試信息的一部分 還不是太多。代理工廠方法的代碼幾乎與 GenericProxyFactory相同,區(qū)別在于它使用的是 LoggingInvocationHandler 而不是匿名
16、的調(diào)用句柄。清單 6. 基于代理的 Decorator,為每個(gè)方法調(diào)用生成調(diào)試日志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í)行下面這個(gè)簡單的測試程序: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這種方式是給對象添加調(diào)試包裝器的一種好的而且容易的方式。它當(dāng)然比生成代理類并手工創(chuàng)建大量 println() 語句容易得多(也更通用)。我進(jìn)一步改進(jìn)了這一方法;不必?zé)o條件地生成調(diào)試輸出,相反,代理可以查詢動(dòng)態(tài)配置存儲(從配置文件初始化,可以由 JMX MBean 動(dòng)態(tài)修改),確定是否需要生成調(diào)試語句,甚至可能在逐個(gè)類或逐個(gè)實(shí)例的基礎(chǔ)上進(jìn)行。在這一點(diǎn)上,我認(rèn)為讀者中的 AOP 愛好者們幾乎要跳出來說“這正是 AOP擅長
19、的??!”是的,但是解決問題的方法不止一種 僅僅因?yàn)槟稠?xiàng)技術(shù)能解決某個(gè)問題,并不意味著它就是最好的解決方案。在任何情況下,動(dòng)態(tài)代理方式都有完全在“純 Java”范圍內(nèi)工作的優(yōu)勢,不是每個(gè)公司都用(或應(yīng)當(dāng)用) AOP 的。動(dòng)態(tài)代理作為適配器代理也可以用作真正的適配器,提供了對象的一個(gè)視圖,導(dǎo)出與底層對象實(shí)現(xiàn)的接口不同的接口。調(diào)用句柄不需要把每個(gè)方法調(diào)用都分派給相同的底層對象;它可以檢查名稱,并把不同的方法分派給不同的對象。例如,假設(shè)有一組表示持久實(shí)體(Person、Company 和 PurchaseOrder) 的 JavaBean 接口,指定了屬性的 getter 和 setter,而且正在編
20、寫一個(gè)持久層,把數(shù)據(jù)庫記錄映射到實(shí)現(xiàn)這些接口的對象上?,F(xiàn)在不用為每個(gè)接口編寫或生成類,可以只用一個(gè) JavaBean 風(fēng)格的通用代理類,把屬性保存在 Map 中。清單 7 顯示的動(dòng)態(tài)代理檢查被調(diào)用方法的名稱,并通過查詢或修改屬性圖直接實(shí)現(xiàn) getter 和 setter 方法?,F(xiàn)在,這一個(gè)代理類就能實(shí)現(xiàn)多個(gè)JavaBean 風(fēng)格接口的對象。清單 7. 用于把 getter 和 setter 分派給 Map 的動(dòng)態(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);雖然因?yàn)榉瓷湓?Object 上工作會有潛在的類型安全性上的損失,但是,JavaBeanProxyFactory 中的 getter 處理會進(jìn)行一些必要的額外的類型檢測,就像我在這里用 isInstance() 對 getter 進(jìn)行的檢測一樣。性能成本正如已經(jīng)看到的,動(dòng)態(tài)代理擁有簡化大量代碼的潛力 不僅能替代許多生成的代碼,而且一個(gè)代理類還
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年醫(yī)院財(cái)務(wù)工作計(jì)劃模版(二篇)
- 2024年小學(xué)下半年工作計(jì)劃模版(二篇)
- 2024年發(fā)電機(jī)租賃協(xié)議經(jīng)典版(二篇)
- 2024年四年級班主任工作總結(jié)范本(四篇)
- 2024年單位勞務(wù)合同參考樣本(二篇)
- 【《不同建筑材料在老城保護(hù)和更新基建項(xiàng)目中的應(yīng)用探究:以成都太古里項(xiàng)目為例》8500字(論文)】
- 2024年學(xué)校后勤工作職責(zé)范文(二篇)
- 2024年醫(yī)院食品衛(wèi)生安全管理制度范本(五篇)
- 散學(xué)典禮的講話稿(6篇)
- 2024年地理教師工作總結(jié)參考樣本(二篇)
- 國企紀(jì)委業(yè)務(wù)培訓(xùn)課件
- 2022-2023學(xué)年揚(yáng)州市寶應(yīng)縣五年級上學(xué)期期中測試數(shù)學(xué)試卷(含答案解析)
- 保安服務(wù)針對本項(xiàng)目的服務(wù)特點(diǎn)、難點(diǎn)分析及解決措施
- 《團(tuán)購產(chǎn)品目錄》課件
- 逆向工程在汽車設(shè)計(jì)中的應(yīng)用
- 致心律失常性右室心肌病的診斷與治療
- 健康評估練習(xí)題大全(含答案)
- 新北師大版小學(xué)數(shù)學(xué)二年級上冊《六-測量:課桌有多長》-公開課教案-1
- 新時(shí)代青年的使命與擔(dān)當(dāng)
- 配電房保養(yǎng)方案
- 2020農(nóng)田灌溉建設(shè)項(xiàng)目水資源論證導(dǎo)則
評論
0/150
提交評論