Java代理機製
1 引言
我們書寫執行一個功能的函數時,經常需要在其中寫入與功能不是直接相關但很有必要的代 碼,如日誌記錄,信息發送,安全和事務支持等,這些枝節性代碼雖然是必要的,但它會帶 來以下麻煩:
- 枝節性代碼遊離在功能性代碼之外,它下是函數的目的,這是對OO是一種破壞
- 枝節性代碼會造成功能性代碼對其它類的依賴,加深類之間的耦合,而這是OO係統所竭 力避免的
- 枝節性代碼帶來的耦合度會造成功能性代碼移植困難,可重用性降低
- 從法理上說,枝節性代碼應該`監視'著功能性代碼,然後采取行動,而不是功能性代碼 `通知'枝節性代碼采取行動,這好比吟遊詩人應該是主動記錄騎士的功績而不是騎士主 動要求詩人記錄自己的功績
2 常見的代理
毫無疑問,枝節性代碼和功能性代碼需要分開來才能降低耦合程度,符合現代OO係統的要 求,我們可以使用代理模式完成這個要求。
代理模式的作用是:為其它對象提供一種代理以控製對這個對象的訪問。在某些情況下,一 個客戶不想直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介作用。 代理模式一般涉及到三個角色:
- 抽象角色:聲明真實對象和代理對象的共同接口
- 代理角色:代理對象內部包含有真實角色的引用,從而可以操作真實角色,同時代理對象 與真實對象有相同的接口,能在任何時候代替真實對象,同時代理對象可以在執行真實對 象前後加入特定的邏輯以實現功能的擴展。
- 真實角色:代理角色所代表的真實對象,是我們最終要引用的對象
常見的代理有:
- 遠程代理(Remote Proxy):對一個位於不同的地址空間對象提供一個局域代表對象,如RMI中的stub
- 虛擬代理(Virtual Proxy):根據需要將一個資源消耗很大或者比較複雜的對象,延遲加 載,在真正需要的時候才創建
- 保護代理(Protect or Access Proxy):控製對一個對象的訪問權限。
- 智能引用(Smart Reference Proxy):提供比目標對象額外的服務和功能。
通過代理類這一中間層,能夠有效控製對實際委托類對象的直接訪問,也可以很好地隱藏和 保護實際對象,實施不同的控製策略,從而在設計上獲得了更大的靈活性。
如果你想學習Java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裏麵有大量的學習資料可以下載。
4 代理模式實例
以下以《Java與模式》中的示例為例:
// 抽象角色: abstract public class Subject { abstract public void request(); } // 真實角色:實現了Subject的request()方法 public class RealSubject extends Subject { public RealSubject() { } public void request() { System.out.println( " From real subject. " ); } } // 代理角色: public class ProxySubject extends Subject { // 以真實角色作為代理角色的屬性 private Subject realSubject; public ProxySubject(Subject realSubject) {this.realSubject = realSubject } // 該方法封裝了真實對象的request方法 public void request() { preRequest(); realSubject.request(); // 此處執行真實對象的request方法 postRequest(); } ... } // 客戶端調用: RealSubject real = new RealSubject(); Subject sub = new ProxySubject(real); Sub.request();
由以上代碼可以看出,客戶實際需要調用的是RealSubject類的request()方法,現在用 ProxySubject來代理 RealSubject類,同樣達到目的,同時還封裝了其他方法 (preRequest(),postRequest()),可以處理一些其他問題。
另外,如果要按照上述的方法使用代理模式,那麼真實角色必須是事先已經存在的,並將其 作為代理對象的內部屬性。但是實際使用時,如果某一個代理要應用於-批真實角色,毎個 真實對象必須對應一個代理角色,如果大量使用會導致類的急劇膨脹;此外,如果事先並不 知道真實角色,該如何使用編寫代理類呢?這個問題可以通過java的動態代理類來解決。
5 java動態代理
所謂Dynamic Proxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一 組interface給它,然後該class就宣稱它實現了這些 interface。你當然可以把該class的實 例當作這些interface中的任何一個來用。當然啦,這個Dynamic Proxy其實就是一個Proxy, 它不會替你作實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工 作。
5.1 java動態代理UML圖
java動態代理類位於java.lang.reflect包下,一般主要涉及到以下兩個類:
- Interface InvocationHandler:該接口中僅定義了一個方法Object:invoke(Object obj,Method method, Object[] args)。在實際使用時,第一個參數obj一般是指代理 類,method是被代理的方法,如上例中的request(),args為該方法的參數數組。 這個抽 象方法在代理類中動態實現。
- Proxy:該類即為動態代理類,作用類似於上例中的ProxySubject。
- Protected Proxy(InvocationHandler h):構造函數,估計用於給內部的h賦值。
- Static Class getProxyClass (ClassLoader loader, Class[] interfaces):獲得一個 代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部接口的數組。
- Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理類的一個實例,返回後的代理類可以當作被代理類使用 (可使用被代理類的在Subject接口中聲明過的方法)。
在使用動態代理類時,我們必須實現InvocationHandler接口,以第一節中的示例為例:
// 抽象角色(之前是抽象類,此處應改為接口): public interface Subject { abstract public void request(); } // 具體角色RealSubject: public class RealSubject implements Subject { public RealSubject() {} public void request() { System.out.println( " From real subject. " ); } } // 代理處理器: import java.lang.reflect.Method; import java.lang.reflect.InvocationHandler; public class DynamicSubject implements InvocationHandler { private Object sub; public DynamicSubject() {} public DynamicSubject(Object obj) { sub = obj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( " before calling " + method); method.invoke(sub,args); System.out.println( " after calling " + method); return null ; } }
該代理類的內部屬性為Object類,實際使用時通過該類的構造函數DynamicSubject(Object obj)對其賦值;此外,在該類還實現了invoke方法,該方法中的
method.invoke(sub,args);
其實就是調用被代理對象的將要被執行的方法,方法參數sub是實際的被代理對象,args為執 行被代理對象相應操作所需的參數。通過動態代理類,我們可以在調用之前或之後執行一些 相關操作。
// 客戶端: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class Client { static public void main(String[] args) throws Throwable { RealSubject rs = new RealSubject(); // 在這裏指定被代理類 InvocationHandler ds = new DynamicSubject(rs); Class cls = rs.getClass(); // 以下是一次性生成代理 Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),ds ); subject.request(); } } // 程序運行結果: before calling public abstract void Subject.request() From real subject. after calling public abstract void Subject.request()
通過這種方式,被代理的對象(RealSubject)可以在運行時動態改變,需要控製的接口 (Subject接口)可以在運行時改變,控製的方式(DynamicSubject類)也可以動態改變,從而實 現了非常靈活的動態代理關係。
6 代理模式與裝飾者模式的區別
代理模式和裝飾者模式很像,在典型的例子上,如spring的AOP、遠程代理類、JDK的proxy, 都是代理模式。JDK裏的輸入/輸出器是很典型的裝飾器模式!但在有些場景上,對設計模式 入門的新手,還是有點難區分,UML類圖基本沒區別,都是實現同一個接口,一個類包裝另一 個類。 兩者的定義:
- 裝飾器模式:能動態的新增或組合對象的行為
- 代理模式:為其他對象提供一種代理以控製對這個對象的訪問
裝飾模式是“新增行為”,而代理模式是“控製訪問”。關鍵就是我們如何判斷是“新增行 為”還是“控製訪問”。你在一個地方寫裝飾,大家就知道這是在增加功能,你寫代理,大 家就知道是在限製。
6.1 裝飾者模式UML圖
其中類的職責如下:
- 抽象構件角色(Project):給出一個接口,以規範準備接收附加責任的對象
- 具體構件角色(Employe):定義一個將要接收附加責任的類
- 裝飾角色(Manager):持有一個構件對象的實例,並定義一個與抽象構件接口一致的接口
- 具體裝飾角色(ManagerA、ManagerB):負責給構件對象“貼上”附加的責任
6.2 形象說明
代理模式:在不改變接口的前提下,控製對象的訪問
例子:孫悟空扮演並代替高家三小姐
孫悟空扮演高家三小姐,所以可以說孫悟空與高家三小姐具有共同的接口。如果豬八戒隻想 見見高家三小姐的嬌好麵容,或者談談天說說地,那麼高家三小姐的“代理”孫悟空是允許 的,但豬八戒想親親嘴,那麼是不行的。這是保護代理模式的應用。隻有代理對象認為合適 時,才會將客戶端的請求傳遞給真實主題對象。
裝飾模式:在不改變接口的前提下,動態擴展對象的功能
孫悟空有七十二般變化,在二郎神眼裏,他永遠是那隻猢猻。裝飾模式以對客戶透明的方式 動態地給一個對象附加上更多的責任。換言之,客戶端並不會覺得對象在裝飾前和裝飾後有 什麼不同。裝飾模式可以在不使用創造更多子類的情況下,將對象的功能加以擴展。他的每 一種變化都給他帶來一種附加的本領。他變成魚兒時,就可以到水裏遊泳;他變成雀兒時, 就可以在天上飛行。而不管悟空怎麼變化,在二郎神眼裏,他永遠是那隻猢猻。裝飾模式以 對客戶透明的方式動態地給一個對象附加上更多的責任。換言之,客戶端並不會覺得對象在 裝飾前和裝飾後有什麼不同。裝飾模式可以在不使用創造更多子類的情況下,將對象的功能 加以擴展。
最後更新:2017-04-24 21:32:59