代理設(shè)計(jì)模式

事例

小張是一個普普通通的碼農(nóng)帮孔,每天勤勤懇懇地碼代碼。某天中午小張剛要去吃飯不撑,一個電話打到了他的手機(jī)上文兢。“是XX公司的小張嗎焕檬?我是YY公司的王AA”姆坚。“哦实愚,是王總啊兼呵,有什么事情嗎烹俗?”。溝通過后萍程,小張弄明白了,原來客戶有個需求,剛好負(fù)責(zé)這方面開發(fā)的是小張兔仰,客戶就直接找到了他茫负。不過小張卻沒有答應(yīng)客戶的請求,而是讓客戶找產(chǎn)品經(jīng)理小李溝通乎赴。

是小張著急去吃面而甩鍋嗎忍法?并不是,只是為了使故事可以套到代理模式上榕吼。我們先看一下代理模式的定義: * 為其他對象提供一種代理饿序,以控制對這個對象的訪問。(Provide a surrogate or placeholder for another object to control access to it)

對照定義羹蚣,碼農(nóng)小張可以映射為其他對象原探,產(chǎn)品經(jīng)理小李為小張的代理。我們通過JAVA代碼顽素,表述上面事例咽弦。

靜態(tài)代理

1.抽象角色

基于面向?qū)ο蟮乃枷耄紫榷x一個碼農(nóng)接口,它有一個實(shí)現(xiàn)用戶需求的方法胁出。

publicinterfaceICoder {


????publicvoidimplDemands(String demandName);


}

2.真實(shí)角色

我們假設(shè)小張是JAVA程序員型型,定義一個JAVA碼農(nóng)類,他通過JAA語言實(shí)現(xiàn)需求全蝶。

publicclassJavaCoder implementsICoder{


????privateString name;


????publicJavaCoder(String name){

????????this.name = name;

????}


????@Override

????publicvoidimplDemands(String demandName) {

????????System.out.println(name + " implemented demand:"+ demandName + " in JAVA!");

????}

}

3.代理角色

委屈一下產(chǎn)品經(jīng)理闹蒜,將其命名為碼農(nóng)代理類,同時讓他實(shí)現(xiàn)ICoder接口抑淫。

publicclassCoderProxy implementsICoder{


????privateICoder coder;


????publicCoderProxy(ICoder coder){

????????this.coder = coder;

????}


????@Override

????publicvoidimplDemands(String demandName) {

????????coder.implDemands(demandName);

????}

}

上面一個接口绷落,兩個類,就實(shí)現(xiàn)了代理模式始苇。Are you kidding me嘱函?這么簡單?是的埂蕊,就是這么簡單往弓。 我們通過一個場景類,模擬用戶找產(chǎn)品經(jīng)理增加需求蓄氧。

publicclassCustomer {


????publicstaticvoidmain(String args[]){

????????//定義一個java碼農(nóng)

????????ICoder coder = newJavaCoder("Zhang");

????????//定義一個產(chǎn)品經(jīng)理

????????ICoder proxy = newCoderProxy(coder);

????????//讓產(chǎn)品經(jīng)理實(shí)現(xiàn)一個需求

????????proxy.implDemands();

????}

}

運(yùn)行程序函似,結(jié)果如下:

1Zhang implemented demand:Add user manageMent in JAVA!

產(chǎn)品經(jīng)理充當(dāng)了程序員的代理,客戶把需求告訴產(chǎn)品經(jīng)理喉童,并不需要和程序員接觸撇寞。看到這里,有些機(jī)智的程序員發(fā)現(xiàn)了問題蔑担。你看牌废,產(chǎn)品經(jīng)理就把客戶的需求轉(zhuǎn)達(dá)了一下,怪不得我看產(chǎn)品經(jīng)理這么不爽啤握。

產(chǎn)品經(jīng)理當(dāng)然不只是轉(zhuǎn)達(dá)用戶需求鸟缕,他還有很多事情可以做。比如排抬,該項(xiàng)目決定不接受新增功能的需求了懂从,對修CoderProxy類做一些修改:

publicclassCoderProxy implementsICoder{


????privateICoder coder;


????publicCoderProxy(ICoder coder){

????????this.coder = coder;

????}


????@Override

????publicvoidimplDemands(String demandName) {

????????if(demandName.startsWith("Add")){

????????????System.out.println("No longer receive 'Add' demand");

????????????return;

????????}

????????coder.implDemands(demandName);

????}

}

這樣,當(dāng)客戶再有增加功能的需求時蹲蒲,產(chǎn)品經(jīng)理就直接回絕了番甩,程序員無需再對這部分需求做過濾。

總結(jié)

我們對上面的事例做一個簡單的抽象:

代理模式包含如下角色:

Subject:抽象主題角色届搁≡笛Γ可以是接口,也可以是抽象類卡睦。

RealSubject:真實(shí)主題角色掩宜。業(yè)務(wù)邏輯的具體執(zhí)行者。

ProxySubject:代理主題角色么翰。內(nèi)部含有RealSubject的引用,負(fù)責(zé)對真實(shí)角色的調(diào)用牺汤,并在真實(shí)主題角色處理前后做預(yù)處理和善后工作。

代理模式優(yōu)點(diǎn):

職責(zé)清晰 真實(shí)角色只需關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn)浩嫌,非業(yè)務(wù)邏輯部分檐迟,后期通過代理類完成即可。

高擴(kuò)展性 不管真實(shí)角色如何變化码耐,由于接口是固定的追迟,代理類無需做任何改動。

動態(tài)代理

前面講的主要是靜態(tài)代理骚腥。那么什么是動態(tài)代理呢敦间?

假設(shè)有這么一個需求,在方法執(zhí)行前和執(zhí)行完成后束铭,打印系統(tǒng)時間廓块。這很簡單嘛,非業(yè)務(wù)邏輯契沫,只要在代理類調(diào)用真實(shí)角色的方法前带猴、后輸出時間就可以了。像上例懈万,只有一個implDemands方法拴清,這樣實(shí)現(xiàn)沒有問題靶病。但如果真實(shí)角色有10個方法,那么我們要寫10遍完全相同的代碼口予。有點(diǎn)追求的碼農(nóng)娄周,肯定會對這種方法感到非常不爽。有些機(jī)智的小伙伴可能想到了用AOP解決這個問題沪停。非常正確煤辨。莫非AOP和動態(tài)代理有什么關(guān)系?沒錯牙甫!AOP用的恰恰是動態(tài)代理。

代理類在程序運(yùn)行時創(chuàng)建的代理方式被稱為動態(tài)代理调违。也就是說窟哺,代理類并不需要在Java代碼中定義,而是在運(yùn)行時動態(tài)生成的技肩。相比于靜態(tài)代理且轨, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個代理類的函數(shù)虚婿。對于上例打印時間的需求旋奢,通過使用動態(tài)代理,我們可以做一個“統(tǒng)一指示”然痊,對所有代理類的方法進(jìn)行統(tǒng)一處理至朗,而不用逐一修改每個方法。下面我們來具體介紹下如何使用動態(tài)代理方式實(shí)現(xiàn)我們的需求剧浸。

與靜態(tài)代理相比锹引,抽象角色、真實(shí)角色都沒有變化唆香。變化的只有代理類嫌变。因此,抽象角色躬它、真實(shí)角色腾啥,參考ICoder和JavaCodr。

在使用動態(tài)代理時冯吓,我們需要定義一個位于代理類與委托類之間的中介類倘待,也叫動態(tài)代理類,這個類被要求實(shí)現(xiàn)InvocationHandler接口:

public class CoderDynamicProxy implements InvocationHandler{

?????//被代理的實(shí)例

????private ICoder coder;


????public CoderDynamicProxy(ICoder _coder){

????????this.coder = _coder;

????}


????//調(diào)用被代理的方法

????@Override

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

????????System.out.println(System.currentTimeMillis());

????????Object result = method.invoke(coder, args);

????????System.out.println(System.currentTimeMillis());

????????return result;

????}

}

當(dāng)我們調(diào)用代理類對象的方法時组贺,這個“調(diào)用”會轉(zhuǎn)送到中介類的invoke方法中延柠,參數(shù)method標(biāo)識了我們具體調(diào)用的是代理類的哪個方法,args為這個方法的參數(shù)锣披。

我們通過一個場景類贞间,模擬用戶找產(chǎn)品經(jīng)理更改需求贿条。

public class DynamicClient {


?????public static void main(String args[]){

????????????//要代理的真實(shí)對象

????????????ICoder coder = new JavaCoder("Zhang");

????????????//創(chuàng)建中介類實(shí)例

????????????InvocationHandler? handler = new CoderDynamicProxy(coder);

????????????//獲取類加載器

????????????ClassLoader cl = coder.getClass().getClassLoader();

????????????//動態(tài)產(chǎn)生一個代理類

????????????ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);

????????????//通過代理類,執(zhí)行doSomething方法增热;

????????????proxy.implDemands("Modify user management");

????????}

}

執(zhí)行結(jié)果如下:

1501728574978

Zhang implemented demand:Modify user management in JAVA!

1501728574979

通過上述代碼整以,就實(shí)現(xiàn)了,在執(zhí)行委托類的所有方法前峻仇、后打印時間公黑。還是那個熟悉的小張,但我們并沒有創(chuàng)建代理類摄咆,也沒有時間ICoder接口凡蚜。這就是動態(tài)代理。

總結(jié)

總結(jié)一下吭从,一個典型的動態(tài)代理可分為以下四個步驟:

創(chuàng)建抽象角色

創(chuàng)建真實(shí)角色

通過實(shí)現(xiàn)InvocationHandler接口創(chuàng)建中介類

通過場景類朝蜘,動態(tài)生成代理類

如果只是想用動態(tài)代理,看到這里就夠了涩金。但如果想知道為什么通過proxy對象谱醇,就能夠執(zhí)行中介類的invoke方法,以及生成的proxy對象是什么樣的步做,可以繼續(xù)往下看副渴。

源碼分析(JDK7)

看到這里的小伙伴,都是有追求的程序員全度。上面的場景類中煮剧,通過

//動態(tài)產(chǎn)生一個代理類

ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);

動態(tài)產(chǎn)生了一個代理類。那么這個代理類是如何產(chǎn)生的呢将鸵?我們通過代碼一窺究竟轿秧。

Proxy類的newProxyInstance方法,主要業(yè)務(wù)邏輯如下:

//生成代理類class咨堤,并加載到j(luò)vm中

Class cl = getProxyClass0(loader, interfaces);

//獲取代理類參數(shù)為InvocationHandler的構(gòu)造函數(shù)

finalConstructor cons = cl.getConstructor(constructorParams);

//生成代理類菇篡,并返回

returnnewInstance(cons, ih);

上面代碼做了三件事:

根據(jù)傳入的參數(shù)interfaces動態(tài)生成一個類,它實(shí)現(xiàn)interfaces中的接口一喘,該例中即ICoder接口的implDemands方法驱还。假設(shè)動態(tài)生成的類為$Proxy0。

通過傳入的classloder,將剛生成的$Proxy0類加載到j(luò)vm中凸克。

利用中介類议蟆,調(diào)用$Proxy0的$Proxy0(InvocationHandler)構(gòu)造函數(shù),創(chuàng)建$Proxy0類的實(shí)例萎战,其InvocationHandler屬性咐容,為我們創(chuàng)建的中介類。

上面的核心蚂维,就在于getProxyClass0方法:

privatestaticClass getProxyClass0(ClassLoader loader,

???????????????????????????????????????????Class... interfaces) {

????????if(interfaces.length > 65535) {

????????????thrownewIllegalArgumentException("interface limit exceeded");

????????}


????????// If the proxy class defined by the given loader implementing

????????// the given interfaces exists, this will simply return the cached copy;

????????// otherwise, it will create the proxy class via the ProxyClassFactory

????????returnproxyClassCache.get(loader, interfaces);

????}

在Proxy類中有個屬性proxyClassCache戳粒,這是一個WeakCache類型的靜態(tài)變量路狮。它指示了類加載器和代理類之間的映射。所以proxyClassCache的get方法用于根據(jù)類加載器來獲取Proxy類蔚约,如果已經(jīng)存在則直接從cache中返回奄妨,如果沒有則創(chuàng)建一個映射并更新cache表。

我們跟一下代理類的創(chuàng)建流程:

調(diào)用Factory類的get方法苹祟,而它又調(diào)用了ProxyClassFactory類的apply方法砸抛,最終找到下面一行代碼:

1

2

//Generate the specified proxy class.

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

就是它,生成了代理類树枫。

查看動態(tài)生成的代理類

通過上面的分析直焙,我們已經(jīng)知道Proxy類動態(tài)創(chuàng)建代理類的流程。那創(chuàng)建出來的代理類到底是什么樣子的呢砂轻?我們可以通過下面的代碼奔誓,手動生成:

publicclassCodeUtil {


???????publicstaticvoidmain(String[] args) throwsIOException {

????????????byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", JavaCoder.class.getInterfaces());

????????????File file = newFile("D:/aaa/TestProxyGen.class");

????????????FileOutputStream fos = newFileOutputStream(file);

????????????fos.write(classFile);

????????????fos.flush();

????????????fos.close();

??????????}

?}

通過反編譯工具查看生成的class文件:

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

import model.proxy.ICoder;


public final classTestProxyGen extends Proxy

??implementsICoder

{

??privatestaticMethod m1;

??privatestaticMethod m0;

??privatestaticMethod m3;

??privatestaticMethod m2;


??publicTestProxyGen(InvocationHandler paramInvocationHandler)

????throws

??{

????super(paramInvocationHandler);

??}


??publicfinalbooleanequals(Object paramObject)

????throws

??{

????try

????{

??????return((Boolean)this.h.invoke(this, m1, newObject[] { paramObject })).booleanValue();

????}

????catch(RuntimeException localRuntimeException)

????{

??????throwlocalRuntimeException;

????}

????catch(Throwable localThrowable)

????{

????}

????thrownewUndeclaredThrowableException(localThrowable);

??}


??publicfinalinthashCode()

????throws

??{

????try

????{

??????return((Integer)this.h.invoke(this, m0, null)).intValue();

????}

????catch(RuntimeException localRuntimeException)

????{

??????throwlocalRuntimeException;

????}

????catch(Throwable localThrowable)

????{

????}

????thrownewUndeclaredThrowableException(localThrowable);

??}


??publicfinalvoidimplDemands(String paramString)

????throws

??{

????try

????{

??????this.h.invoke(this, m3, newObject[] { paramString });

??????return;

????}

????catch(RuntimeException localRuntimeException)

????{

??????throwlocalRuntimeException;

????}

????catch(Throwable localThrowable)

????{

????}

????thrownewUndeclaredThrowableException(localThrowable);

??}


??publicfinalString toString()

????throws

??{

????try

????{

??????return(String)this.h.invoke(this, m2, null);

????}

????catch(RuntimeException localRuntimeException)

????{

??????throwlocalRuntimeException;

????}

????catch(Throwable localThrowable)

????{

????}

????thrownewUndeclaredThrowableException(localThrowable);

??}


??static

??{

????try

????{

??????m1 = Class.forName("java.lang.Object").getMethod("equals", newClass[] { Class.forName("java.lang.Object") });

??????m0 = Class.forName("java.lang.Object").getMethod("hashCode", newClass[0]);

??????m3 = Class.forName("model.proxy.ICoder").getMethod("implDemands", newClass[] { Class.forName("java.lang.String") });

??????m2 = Class.forName("java.lang.Object").getMethod("toString", newClass[0]);

??????return;

????}

????catch(NoSuchMethodException localNoSuchMethodException)

????{

??????thrownewNoSuchMethodError(localNoSuchMethodException.getMessage());

????}

????catch(ClassNotFoundException localClassNotFoundException)

????{

????}

????thrownewNoClassDefFoundError(localClassNotFoundException.getMessage());

??}

}

這樣,我們就理解舔清,為什么調(diào)用代理類的implDemands方法丝里,回去執(zhí)行中介類的invoke方法了曲初。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末体谒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子臼婆,更是在濱河造成了極大的恐慌抒痒,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颁褂,死亡現(xiàn)場離奇詭異故响,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颁独,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門彩届,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人誓酒,你說我怎么就攤上這事樟蠕。” “怎么了靠柑?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵寨辩,是天一觀的道長。 經(jīng)常有香客問我歼冰,道長靡狞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任隔嫡,我火速辦了婚禮甸怕,結(jié)果婚禮上甘穿,老公的妹妹穿的比我還像新娘。我一直安慰自己蕾各,他們只是感情好扒磁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著式曲,像睡著了一般妨托。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吝羞,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天兰伤,我揣著相機(jī)與錄音,去河邊找鬼钧排。 笑死敦腔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恨溜。 我是一名探鬼主播符衔,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼糟袁!你這毒婦竟也來了判族?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤项戴,失蹤者是張志新(化名)和其女友劉穎形帮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體周叮,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辩撑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿耽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片合冀。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖项贺,靈堂內(nèi)的尸體忽然破棺而出君躺,到底是詐尸還是另有隱情,我是刑警寧澤敬扛,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布晰洒,位于F島的核電站,受9級特大地震影響啥箭,放射性物質(zhì)發(fā)生泄漏谍珊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一急侥、第九天 我趴在偏房一處隱蔽的房頂上張望砌滞。 院中可真熱鬧侮邀,春花似錦、人聲如沸贝润。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽打掘。三九已至华畏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尊蚁,已是汗流浹背亡笑。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留横朋,地道東北人仑乌。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像琴锭,于是被迫代替她去往敵國和親晰甚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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