Java代理機(jī)制

#靜態(tài)代理

? ? 靜態(tài)代理是在編譯時(shí)就將接口伞广、實(shí)現(xiàn)類拣帽、代理類一股腦兒全部手動(dòng)完成,但如果我們需要很多的代理嚼锄,每一個(gè)都這么手動(dòng)的去創(chuàng)建實(shí)屬浪費(fèi)時(shí)間减拭,而且會(huì)有大量的重復(fù)代碼 。

例如:

```

? ? //聲明一個(gè)接口

? ? package com.ceshi.proxy;

? ? public interface Moveable {

? ? ? ? //提供一個(gè)move方法

void move();

}

package com.ceshi.proxy;

import java.util.Random;

//寫一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)這個(gè)接口

public class Tank implements Moveable {

@Override

public void move() {

System.out.println("Tank Moving...");

try {

Thread.sleep(new Random().nextInt(10000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

package com.ceshi.proxy;

//再寫一個(gè)tank的時(shí)間代理類實(shí)現(xiàn)Moveable接口

public class TankTimeProxy implements Moveable {

? ? public TankTimeProxy(Moveable t) {

? ? ? ? super();

? ? ? ? this.t = t;

? ? }

? ? Moveable t;

? ? @Override

? ? public void move() {

? ? ? ? //在執(zhí)行move方法前后加上有關(guān)時(shí)間的代碼邏輯区丑,以此來實(shí)現(xiàn)類似日志管理

? ? ? ? long start = System.currentTimeMillis();

? ? ? ? System.out.println("starttime:" + start);

? ? ? ? t.move();

? ? ? ? long end = System.currentTimeMillis();

? ? ? ? System.out.println("time:" + (end-start));

? ? }

}

package com.ceshi.proxy;

public class Client {

public static void main(String[] args) throws Exception {

Tank t = new Tank();

TankTimeProxy time = new TankTimeProxy(t);

time.move();

}

}

```

運(yùn)行結(jié)果為:

```

starttime:1526992632651

Tank Moving...

time:3604

```

通過上述例子拧粪,我們看到,靜態(tài)代理需要手動(dòng)的取創(chuàng)建沧侥,并且在以后我們有可會(huì)對(duì)tank這個(gè)類進(jìn)行日志管理可霎,權(quán)限管理,事務(wù)管理等等宴杀,總之癣朗,在他前后添加代碼邏輯,并且實(shí)現(xiàn)各種代理順序的調(diào)換旺罢,那我們豈不是要寫很多代理類去實(shí)現(xiàn)那個(gè)接口旷余。代碼太冗余绢记。

#動(dòng)態(tài)代理

動(dòng)態(tài)代理可以在程序運(yùn)行期間根據(jù)需要?jiǎng)討B(tài)的創(chuàng)建代理類及其實(shí)例,來完成具體的功能正卧。

接續(xù)上面的例子蠢熄,看看用動(dòng)態(tài)代理怎么實(shí)現(xiàn)

```

//聲明一個(gè)接口

? ? package com.ceshi.proxy;

? ? public interface Moveable {

? ? ? ? //提供一個(gè)move方法

void move();

}

package com.ceshi.proxy;

import java.util.Random;

//寫一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)這個(gè)接口

public class Tank implements Moveable {

@Override

public void move() {

System.out.println("Tank Moving...");

try {

Thread.sleep(new Random().nextInt(10000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

package com.ceshi.proxy.test;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

//寫一個(gè)事物操作的Handler實(shí)現(xiàn)jdk中的InvocationHandler。

public class TransactionHandler implements InvocationHandler {

private Object target;

//寫一個(gè)構(gòu)造方法將被代理的對(duì)象傳進(jìn)去

public TransactionHandler(Object target) {

super();

this.target = target;

}

@Override

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

throws Throwable {

System.out.println("Transaction Start");

try {

method.invoke(target);

} catch (Exception e) {

e.printStackTrace();

}

System.out.println("Transaction Commit");

return null;

}

}

package com.ceshi.proxy.test;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import com.ceshi.proxy.TimeHandler;

public class Client {

public static void main(String[] args) throws Exception {

Tank tank = new Tank();

InvocationHandler h = new TransactionHandler(tank );

Moveable m = (Moveable )Proxy.newProxyInstance(Moveable .class.getClassLoader(),new Class[]{Moveable .class},h);

m.move();

}

}

```

運(yùn)行結(jié)果為:

```

Transaction Start

Tank Moving...

Transaction Commit

```

#總結(jié)

通過上面的動(dòng)態(tài)代理實(shí)例我們來仔細(xì)分析研究一下動(dòng)態(tài)代理的實(shí)現(xiàn)過程

(1)首先我要說的就是接口炉旷,為什么JDK的動(dòng)態(tài)代理是基本接口實(shí)現(xiàn)的呢签孔?

  因?yàn)橥ㄟ^使用接口指向?qū)崿F(xiàn)類的實(shí)例的多態(tài)實(shí)現(xiàn)方式,可以有效的將具體的實(shí)現(xiàn)與調(diào)用之間解耦窘行,便于后期修改與維護(hù)饥追。

再具體的說就是我們在代理類中創(chuàng)建一個(gè)私有成員變量(private修飾),使用接口來指向?qū)崿F(xiàn)類的對(duì)象(純種的多態(tài)體現(xiàn)抽高,向上轉(zhuǎn)型的體現(xiàn))判耕,然后在該代理類中的方法中使用這個(gè)創(chuàng)建的實(shí)例來調(diào)用實(shí)現(xiàn)類中的相應(yīng)方法來完成業(yè)務(wù)邏輯功能。

這么說起來翘骂,我之前說的“將具體實(shí)現(xiàn)類完全隱藏”就不怎么正確了壁熄,可以改成,將具體實(shí)現(xiàn)類的細(xì)節(jié)向調(diào)用方完全隱藏(調(diào)用方調(diào)用的是代理類中的方法碳竟,而不是實(shí)現(xiàn)類中的方法)草丧。

  這就是面向接口編程,利用java的多態(tài)特性莹桅,實(shí)現(xiàn)程序代碼的解耦昌执。

(2)創(chuàng)建代理類的過程

  如果你了解靜態(tài)代理,那么你會(huì)發(fā)現(xiàn)動(dòng)態(tài)代理的實(shí)現(xiàn)其實(shí)與靜態(tài)代理類似诈泼,都需要?jiǎng)?chuàng)建代理類懂拾,但是不同之處也很明顯,創(chuàng)建方式不同铐达!

  不同之處體現(xiàn)在靜態(tài)代理我們知根知底岖赋,我們知道要對(duì)哪個(gè)接口、哪個(gè)實(shí)現(xiàn)類來創(chuàng)建代理類瓮孙,所以我們在編譯前就直接實(shí)現(xiàn)與實(shí)現(xiàn)類相同的接口唐断,直接在實(shí)現(xiàn)的方法中調(diào)用實(shí)現(xiàn)類中的相應(yīng)(同名)方法即可;而動(dòng)態(tài)代理不同杭抠,我們不知道它什么時(shí)候創(chuàng)建脸甘,也不知道要?jiǎng)?chuàng)建針對(duì)哪個(gè)接口、實(shí)現(xiàn)類的代理類(因?yàn)樗窃谶\(yùn)行時(shí)因需實(shí)時(shí)創(chuàng)建的)偏灿。

  雖然二者創(chuàng)建時(shí)機(jī)不同丹诀,創(chuàng)建方式也不相同,但是原理是相同的,不同之處僅僅是:靜態(tài)代理可以直接編碼創(chuàng)建忿墅,而動(dòng)態(tài)代理是利用反射機(jī)制來抽象出代理類的創(chuàng)建過程扁藕。

  讓我們來分析一下之前的代碼來驗(yàn)證一下上面的說辭:

    第一點(diǎn):靜態(tài)代理需要實(shí)現(xiàn)與實(shí)現(xiàn)類相同的接口,而動(dòng)態(tài)代理需要實(shí)現(xiàn)的是固定的Java提供的內(nèi)置接口(一種專門提供來創(chuàng)建動(dòng)態(tài)代理的接口)InvocationHandler接口疚脐,因?yàn)閖ava在接口中提供了一個(gè)可以被自動(dòng)調(diào)用的方法invoke,這個(gè)之后再說邢疙。

    第二點(diǎn):private Object object;

        public UserProxy(Object obj){this.object = obj;}

  這幾行代碼與靜態(tài)代理之中在代理類中定義的接口指向具體實(shí)現(xiàn)類的實(shí)例的代碼異曲同工棍弄,通過這個(gè)構(gòu)造器可以創(chuàng)建代理類的實(shí)例,創(chuàng)建的同時(shí)還能將具體實(shí)現(xiàn)類的實(shí)例與之綁定(object指的就是實(shí)現(xiàn)類的實(shí)例疟游,這個(gè)實(shí)例需要在測試類中創(chuàng)建并作為參數(shù)來創(chuàng)建代理類的實(shí)例)呼畸,實(shí)現(xiàn)了靜態(tài)代理類中private Iuser user = new UserImpl();一行代碼的作用相近,這里為什么不是相同颁虐,而是相近呢蛮原,主要就是因?yàn)殪o態(tài)代理的那句代碼中包含的實(shí)現(xiàn)類的實(shí)例的創(chuàng)建,而動(dòng)態(tài)代理中實(shí)現(xiàn)類的創(chuàng)建需要在測試類中完成另绩,所以此處是相近儒陨。

    第三點(diǎn):invoke(Object proxy, Method method, Object[] args)方法,該方法是InvocationHandler接口中定義的唯一方法笋籽,該方法在調(diào)用指定的具體方法時(shí)會(huì)自動(dòng)調(diào)用蹦漠。其參數(shù)為:代理實(shí)例、調(diào)用的方法车海、方法的參數(shù)列表

  在這個(gè)方法中我們定義了幾乎和靜態(tài)代理相同的內(nèi)容笛园,僅僅是在方法的調(diào)用上不同,不同的原因與之前分析的一樣(創(chuàng)建時(shí)機(jī)的不同侍芝,創(chuàng)建的方式的不同研铆,即反射),Method類是反射機(jī)制中一個(gè)重要的類州叠,用于封裝方法棵红,該類中有一個(gè)方法那就是invoke(Object object,Object...args)方法,其參數(shù)分別表示:所調(diào)用方法所屬的類的對(duì)象和方法的參數(shù)列表留量,這里的參數(shù)列表正是從測試類中傳遞到代理類中的invoke方法三個(gè)參數(shù)中最后一個(gè)參數(shù)(調(diào)用方法的參數(shù)列表)中窄赋,在傳遞到method的invoke方法中的第二個(gè)參數(shù)中的(此處有點(diǎn)啰嗦)。

    第四點(diǎn):測試類中的異同

  靜態(tài)代理中我們測試類中直接創(chuàng)建代理類的對(duì)象楼熄,使用代理類的對(duì)象來調(diào)用其方法即可忆绰,若是別的接口(這里指的是別的調(diào)用方)要調(diào)用Iuser的方法,也可以使用此法

動(dòng)態(tài)代理中要復(fù)雜的多可岂,首先我們要將之前提到的實(shí)現(xiàn)類的實(shí)例創(chuàng)建(補(bǔ)充完整)错敢,然后利用這個(gè)實(shí)例作為參數(shù),調(diào)用代理來的帶參構(gòu)造器來創(chuàng)建“代理類實(shí)例對(duì)象”,這里加引號(hào)的原因是因?yàn)樗⒉皇钦嬲拇眍惖膶?shí)例對(duì)象稚茅,而是創(chuàng)建真正代理類實(shí)例的一個(gè)參數(shù)纸淮,這個(gè)實(shí)現(xiàn)了InvocationHandler接口的類嚴(yán)格意義上來說并不是代理類,我們可以將其看作是創(chuàng)建代理類的必備中間環(huán)節(jié)亚享,這是一個(gè)調(diào)用處理器咽块,也就是處理方法調(diào)用的一個(gè)類,不是真正意義上的代理類欺税,可以這么說:創(chuàng)建一個(gè)方法調(diào)用處理器實(shí)例侈沪。

  下面才是真正的代理類實(shí)例的創(chuàng)建,之前創(chuàng)建的”代理類實(shí)例對(duì)象“僅僅是一個(gè)參數(shù)

    Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);

  這里使用了動(dòng)態(tài)代理所依賴的第二個(gè)重要類Proxy晚凿,此處使用了其靜態(tài)方法來創(chuàng)建一個(gè)代理實(shí)例亭罪,其參數(shù)分別是:類加載器(可為父類的類加載器)、接口數(shù)組歼秽、方法調(diào)用處理器實(shí)例

  這里同樣使用了多態(tài)应役,使用接口指向代理類的實(shí)例,最后會(huì)用該實(shí)例來進(jìn)行具體方法的調(diào)用即可燥筷。

(3)InvocationHandler

  InvocationHandler是JDK中提供的專門用于實(shí)現(xiàn)基于接口的動(dòng)態(tài)代理的接口箩祥,主要用于進(jìn)行方法調(diào)用模塊,而代理類和實(shí)例的生成需要借助Proxy類完成荆责。

  每個(gè)代理類的實(shí)例的調(diào)用處理器都是實(shí)現(xiàn)該接口實(shí)現(xiàn)的滥比,而且是必備的,即每個(gè)動(dòng)態(tài)代理實(shí)例的實(shí)現(xiàn)都必須擁有實(shí)現(xiàn)該接口的調(diào)用處理器做院,也可以這么說盲泛,每個(gè)動(dòng)態(tài)代理實(shí)例都對(duì)應(yīng)一個(gè)調(diào)用處理器。

  這里要區(qū)分兩個(gè)概念键耕,代理類和代理實(shí)例寺滚,調(diào)用處理器是在創(chuàng)建代理實(shí)例的時(shí)候才與其關(guān)聯(lián)起來的,所以它與代理實(shí)例是一一對(duì)應(yīng)的屈雄,而不是代理類村视。

(4)Proxy

  Proxy類是JDK提供的用于生成動(dòng)態(tài)代理類和其實(shí)例的類。

  我們可以通過Proxy中的靜態(tài)方法getProxyClass來生成代理類酒奶,需要的參數(shù)為類加載器和接口列表(數(shù)組)蚁孔,然后再通過反射調(diào)用代理類的構(gòu)造器來生成代理實(shí)例,需要以一個(gè)InvocationHandler作為參數(shù)(體現(xiàn)出方法調(diào)用是與實(shí)例相關(guān)的惋嚎,而非類)杠氢。

```

? ? InvocationHandler handler = new MyInvocationHandler(...);

? ? Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

? ? Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);

```

  我們也可以直接通過Proxy中的靜態(tài)方法newProxyInstance方法來直接生產(chǎn)代理實(shí)例,需要提供參數(shù)為上面的三個(gè)參數(shù)另伍,即類加載器鼻百,接口數(shù)組,InvocationHandler。

```? Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);```

(5)温艇、總結(jié)

  我們總結(jié)下JDK動(dòng)態(tài)代理的實(shí)現(xiàn)步驟:

    第一步:創(chuàng)建接口因悲,JDK動(dòng)態(tài)代理基于接口實(shí)現(xiàn),所以接口必不可少(準(zhǔn)備工作)

    第二步:實(shí)現(xiàn)InvocationHandler接口勺爱,重寫invoke方法(準(zhǔn)備工作)

    第三步:調(diào)用Proxy的靜態(tài)方法newProxyInstance方法生成代理實(shí)例(生成實(shí)例時(shí)需要提供類加載器晃琳,我們可以使用接口類的加載器即可)

    第四步:使用新生成的代理實(shí)例調(diào)用某個(gè)方法實(shí)現(xiàn)功能。

  我們的動(dòng)態(tài)代理實(shí)現(xiàn)過程中根本沒有涉及到真實(shí)類實(shí)例琐鲁。

#參考文獻(xiàn)

https://www.cnblogs.com/V1haoge/p/5860749.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝎土,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绣否,更是在濱河造成了極大的恐慌,老刑警劉巖挡毅,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒜撮,死亡現(xiàn)場離奇詭異,居然都是意外死亡跪呈,警方通過查閱死者的電腦和手機(jī)段磨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耗绿,“玉大人苹支,你說我怎么就攤上這事∥笞瑁” “怎么了债蜜?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長究反。 經(jīng)常有香客問我寻定,道長,這世上最難降的妖魔是什么精耐? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任狼速,我火速辦了婚禮,結(jié)果婚禮上卦停,老公的妹妹穿的比我還像新娘向胡。我一直安慰自己,他們只是感情好惊完,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布僵芹。 她就那樣靜靜地躺著,像睡著了一般专执。 火紅的嫁衣襯著肌膚如雪淮捆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音攀痊,去河邊找鬼桐腌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛苟径,可吹牛的內(nèi)容都是我干的案站。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棘街,長吁一口氣:“原來是場噩夢啊……” “哼蟆盐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起遭殉,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤石挂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后险污,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痹愚,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蛔糯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拯腮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚁飒,死狀恐怖动壤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淮逻,我是刑警寧澤琼懊,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站弦蹂,受9級(jí)特大地震影響肩碟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凸椿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一削祈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脑漫,春花似錦髓抑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至网杆,卻和暖如春羹饰,著一層夾襖步出監(jiān)牢的瞬間伊滋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工队秩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笑旺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓馍资,卻偏偏與公主長得像筒主,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸟蟹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java代理相關(guān)主要知識(shí)如下: (1)利用代理可以在運(yùn)行時(shí)創(chuàng)建一個(gè)實(shí)現(xiàn)了一組給定接口的新類乌妙。 這種功能只有在編譯時(shí)...
    尚學(xué)先生閱讀 349評(píng)論 0 1
  • https://blog.csdn.net/luanlouis/article/details/24589193 ...
    小陳阿飛閱讀 864評(píng)論 1 1
  • 一直以來我對(duì)于節(jié)日的重要性看的并不是很重。但是喜歡吃湯圓建钥,在預(yù)料到正月十五是周六的時(shí)候藤韵,我準(zhǔn)備自己買一些糯米面自己...
    奈奈奈米醬閱讀 284評(píng)論 0 0
  • 芒種,麥子成熟的季節(jié)熊经。 2017年6月5日荠察,迎來芒種。 它是夏季的第三個(gè)節(jié)氣奈搜,代表仲夏時(shí)節(jié)的正式開始。 關(guān)于芒種 ...
    金勇Maya閱讀 472評(píng)論 0 2
  • 及至幽然閣盯荤,只見采英在后門口走來走去馋吗,甚是焦急,見了山鬼忙跑上來說道:“我的小祖宗秋秤,怎么才回來呢宏粤?再遲一刻,恐就要...
    山鬼琉璃閱讀 613評(píng)論 0 0