Java代理模式及動態(tài)代理

代理模式

代理模式就是給某個對象提供一個代理對象娇唯,并由代理對象控制對于原對象的訪問剔宪,即客戶不直接操控原對象歌粥,而是通過代理對象間接地操控原對象凯正。
優(yōu)勢:
給某個對象中的方法進行擴展,不用改變那個對象中的方法秸抚。
代理的實現分為:
靜態(tài)代理:代理類是在編譯時就實現好的速和。也就是說 Java 編譯完成后代理類是一個實際的 class 文件。
動態(tài)代理:代理類是在運行時生成的剥汤。也就是說 Java 編譯完之后并沒有實際的 class 文件颠放,而是在運行時動態(tài)生成的類字節(jié)碼,并加載到JVM中吭敢。
靜態(tài)代理示例:

public class ProxyDemo {
    public static void main(String args[]){
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}
 
interface Subject{
    void request();
}
 
class RealSubject implements Subject{
    public void request(){
        System.out.println("request");
    }
}
 
class Proxy implements Subject{
    private Subject subject;
    public Proxy(Subject subject){
        this.subject = subject;
    }
    public void request(){
        System.out.println("PreProcess");
        subject.request();
        System.out.println("PostProcess");
    }

以上示例就是擴展了request()方法碰凶,而對于被代理對象的request方法沒有進行任何改動,代理類Proxy 在編譯時就已被實現。

動態(tài)代理

Java實現動態(tài)代理的大致步驟如下:
1.定義一個委托類和公共接口痒留。
2.自己定義一個類(調用處理器類谴麦,即實現 InvocationHandler 接口),這個類的目的是指定運行時將生成的代理類需要完成的具體任務(包括Preprocess和Postprocess)伸头,即代理類調用任何方法都會經過這個調用處理器類匾效。
3.生成代理對象(當然也會生成代理類),需要為他指定委托對象恤磷,實現的一系列接口調用處理器類的實例面哼。因此可以看出一個代理對象對應一個委托對象,對應一個調用處理器實例扫步。
名詞說明:
委托類和委托對象:委托類是一個類魔策,委托對象是委托類的實例。
代理類和代理對象:代理類是一個類河胎,代理對象是代理類的實例闯袒。

Java 實現動態(tài)代理主要涉及以下幾個類:
1.java.lang.reflect.Proxy: 這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類游岳,即 DynamicProxyClass extends Proxy政敢。
2.java.lang.reflect.InvocationHandler: 這里稱他為"調用處理器",他是一個接口胚迫,我們動態(tài)生成的代理類需要完成的具體內容需要自己定義一個類喷户,而這個類必須實現 InvocationHandler 接口。

Proxy 類主要方法為:

//創(chuàng)建代理對象  
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

這個靜態(tài)函數的第一個參數是類加載器對象(即哪個類加載器來加載這個代理類到 JVM 的方法區(qū))访锻,第二個參數是接口(表明你這個代理類需要實現哪些接口)褪尝,第三個參數是調用處理器類實例(指定代理類中具體要干什么)。這個函數是 JDK 為了程序員方便創(chuàng)建代理對象而封裝的一個函數期犬,因此你調用newProxyInstance()時直接創(chuàng)建了代理對象(略去了創(chuàng)建代理類的代碼)河哑。其實他主要完成了以下幾個工作:

static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler handler)
{
    //1. 根據類加載器和接口創(chuàng)建代理類
    Class clazz = Proxy.getProxyClass(loader, interfaces); 
    //2. 獲得代理類的帶參數的構造函數
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
    //3. 創(chuàng)建代理對象停团,并制定調用處理器實例為參數傳入
    Interface Proxy = (Interface)constructor.newInstance(new Object[] {handler});
}

Proxy 類還有一些靜態(tài)方法芬位,比如:
InvocationHandler getInvocationHandler(Object proxy): 獲得代理對象對應的調用處理器對象。
Class getProxyClass(ClassLoader loader, Class[] interfaces): 根據類加載器和實現的接口獲得代理類揪阶。
Proxy 類中有一個映射表遣总,映射關系為:(<ClassLoader>,(<Interfaces>,<ProxyClass>) ),可以看出一級key為類加載器轨功,根據這個一級key獲得二級映射表旭斥,二級key為接口數組,因此可以看出:一個類加載器對象和一個接口數組確定了一個代理類古涧。


public class DynamicProxyDemo01 {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();    //1.創(chuàng)建委托對象
        ProxyHandler handler = new ProxyHandler(realSubject);    //2.創(chuàng)建調用處理器對象
        Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(), handler);    //3.動態(tài)生成代理對象
        proxySubject.request();    //4.通過代理對象調用方法
    }
}
 
/**
 * 接口
 */
interface Subject{
    void request();
}
 
/**
 * 委托類
 */
class RealSubject implements Subject{
    public void request(){
        System.out.println("====RealSubject Request====");
    }
}
/**
 * 代理類的調用處理器
 */
class ProxyHandler implements InvocationHandler{
    private Subject subject;
    public ProxyHandler(Subject subject){
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("====before====");//定義預處理的工作垂券,當然你也可以根據 method 的不同進行不同的預處理工作
        Object result = method.invoke(subject, args);
        System.out.println("====after====");
        return result;
    }
}

InvocationHandler 接口中有方法:

invoke(Object proxy, Method method, Object[] args)

這個函數是在代理對象調用任何一個方法時都會調用的,方法不同會導致第二個參數method不同,第一個參數是代理對象(表示哪個代理對象調用了method方法)菇爪,第二個參數是 Method 對象(表示哪個方法被調用了)算芯,第三個參數是指定調用方法的參數。

動態(tài)生成的代理類具有幾個特點:
1.繼承 Proxy 類凳宙,并實現了在Proxy.newProxyInstance()中提供的接口數組熙揍。
2.public final。
3.命名方式為 ProxyN氏涩,其中N會慢慢增加届囚,一開始是Proxy1,接下來是$Proxy2...
有一個參數為 InvocationHandler 的構造函數是尖。這個從 Proxy.newProxyInstance() 函數內部的clazz.getConstructor(new Class[] { InvocationHandler.class }) 可以看出意系。

Java 實現動態(tài)代理的缺點:因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),只能針對接口創(chuàng)建代理類饺汹,不能針對類創(chuàng)建代理類蛔添。

Java 動態(tài)代理的內部實現(利用Java反射機制):

Java 是怎么保證代理對象調用的任何方法都會調用 InvocationHandler 的 invoke() 方法,
這就涉及到動態(tài)代理的內部實現兜辞。假設有一個接口 Subject迎瞧,且里面有 int request(int i) 方法,則生成的代理類大致如下:


public final class $Proxy1 extends Proxy implements Subject{
    private InvocationHandler h;
    private $Proxy1(){}
    public $Proxy1(InvocationHandler h){
        this.h = h;
    }
    public int request(int i){
        Method method = Subject.class.getMethod("request", new Class[]{int.class});    //創(chuàng)建method對象
        return (Integer)h.invoke(this, method, new Object[]{new Integer(i)}); //調用了invoke方法
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末弦疮,一起剝皮案震驚了整個濱河市夹攒,隨后出現的幾起案子,更是在濱河造成了極大的恐慌胁塞,老刑警劉巖咏尝,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異啸罢,居然都是意外死亡编检,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門扰才,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允懂,“玉大人,你說我怎么就攤上這事衩匣±僮埽” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵琅捏,是天一觀的道長生百。 經常有香客問我,道長柄延,這世上最難降的妖魔是什么蚀浆? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上市俊,老公的妹妹穿的比我還像新娘杨凑。我一直安慰自己,他們只是感情好摆昧,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布撩满。 她就那樣靜靜地躺著,像睡著了一般据忘。 火紅的嫁衣襯著肌膚如雪鹦牛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天勇吊,我揣著相機與錄音曼追,去河邊找鬼。 笑死汉规,一個胖子當著我的面吹牛礼殊,可吹牛的內容都是我干的。 我是一名探鬼主播针史,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼晶伦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啄枕?” 一聲冷哼從身側響起婚陪,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎频祝,沒想到半個月后泌参,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡常空,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年沽一,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漓糙。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡铣缠,死狀恐怖,靈堂內的尸體忽然破棺而出昆禽,到底是詐尸還是另有隱情蝗蛙,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布醉鳖,位于F島的核電站歼郭,受9級特大地震影響,放射性物質發(fā)生泄漏辐棒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漾根。 院中可真熱鬧泰涂,春花似錦、人聲如沸辐怕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寄疏。三九已至是牢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陕截,已是汗流浹背驳棱。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留农曲,地道東北人社搅。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像乳规,于是被迫代替她去往敵國和親形葬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容