淺談代理模式及其在Java中的實現(xiàn)

代理模式是常用的結(jié)構(gòu)型設(shè)計模式之一,當無法直接訪問某個對象或訪問某個對象存在困難時可以通過一個代理對象來間接訪問叛赚,為了保證客戶端使用的透明性俺附,所訪問的真實對象與代理對象需要實現(xiàn)相同的接口。在Java中代理實現(xiàn)分為靜態(tài)代理和動態(tài)代理召调,本文將簡要描述代理模式及其在Java中的實現(xiàn)蛮浑。

代理模式

定義

給某一個對象提供一個代理或占位符沮稚,并由代理對象來控制對原對象的訪問。

結(jié)構(gòu)

代理模式UML圖

注意:上圖的注釋部分是針對Proxy類的request方法障般。

由圖可知挽荡,代理模式存在以下3種角色:

  • Subject:被代理類和代理類要實現(xiàn)的公共接口(request())
  • ConcreteSubject:要被代理的類定拟,又叫委托類逗嫡,它定義了代理角色所代表的真實對象
  • Proxy:代理類,代理對象扮演著中介的角色延窜,增加被代理類的某些功能或去掉了某些服務(wù)

簡單實現(xiàn)

Subject接口:

<pre>
public interface Subject {
void request();
}
</pre>

ConcreteSubject類:
<pre>
public class ConcreteSubject implements Subject {
@Override
public void request() {
System.out.println("=======具體子類的request=======");
}
}
</pre>

Proxy類:
<pre>
public class Proxy implements Subject {
private ConcreteSubject subject;//被代理對象

public Proxy(ConcreteSubject subject){
    this.subject=subject;
}


public void preRequest(){
    System.out.println("=======代理的preRequest=======");
}

@Override
public void request() {
    preRequest();
    subject.request();//調(diào)用被代理對象的方法
    postRequest();
}

public void postRequest(){
    System.out.println("=======代理的postRequest=======");
}

}
</pre>

客戶端測試類:
<pre>
public class Test {

public static void main(String[] args) {
    //1.創(chuàng)建被代理類對象
    ConcreteSubject subject=new ConcreteSubject();
    //2.創(chuàng)建代理對象逆瑞,使用構(gòu)造注入把被代理類對象注入到代理對象中
    Proxy proxy=new Proxy(subject);
    //3.調(diào)用代理對象的相應(yīng)方法
    proxy.request();
}

}
</pre>

運行測試類呆万,應(yīng)該看到如下結(jié)果:

以上谋减,就實現(xiàn)了一個最簡單的代理模式出爹。接下來看一下常見的幾種代理模式及其應(yīng)用場景严就。

常見代理模式及應(yīng)用場景

  1. 遠程代理(Remote Proxy):為一個位于不同的地址空間的對象提供一個本地的代理對象器罐,這
    個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中铸董,遠程代理又稱為大使
    (Ambassador)粟害。
  2. 虛擬代理(Virtual Proxy):如果需要創(chuàng)建一個資源消耗較大的對象,先創(chuàng)建一個消耗相對較
    小的對象來表示套鹅,真實對象只在需要時才會被真正創(chuàng)建卓鹿。
  3. 保護代理(Protect Proxy):控制對一個對象的訪問留荔,可以給不同的用戶提供不同級別的使用權(quán)限。
  4. 緩沖代理(Cache Proxy):為某一個目標操作的結(jié)果提供臨時的存儲空間拔疚,以便多個客戶端
    可以共享這些結(jié)果稚失。
  5. 智能引用代理(Smart Reference Proxy):當一個對象被引用時恰聘,提供一些額外的操作晴叨,例如將對象被調(diào)用的次數(shù)記錄下來等。

其中兼蕊,智能引用代理是使用得最廣泛的一種代理孙技,適合的應(yīng)用如如:日志處理、權(quán)限管理亚情、事務(wù)處理等哈雏。

代理的實現(xiàn)

代理模式的實現(xiàn)分為靜態(tài)代理和動態(tài)代理衫生。

靜態(tài)代理

概念

由程序員創(chuàng)建或工具生成代理類的源碼罪针,再編譯代理類栅迄。所謂靜態(tài)也就是在程序運行前就已經(jīng)存在代理類>的字節(jié)碼文件毅舆,代理類和委托類的關(guān)系在運行前就確定了憋活。Java編譯完成后代理類是一個實際的 class 文件虱黄。

前面舉的例子就是一個簡單的靜態(tài)代理。

靜態(tài)代理的實現(xiàn)可分為繼承和聚合辜梳,其中繼承容易導(dǎo)致類膨脹作瞄,因此推薦的實現(xiàn)方式是聚合危纫。

靜態(tài)代理優(yōu)缺點

優(yōu)點:業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身,保證了業(yè)務(wù)類的重用性契耿。這是代理的共有優(yōu)點螃征。

缺點:

  1. 代理對象的一個接口只服務(wù)于一種類型的對象盯滚,如果要代理的方法很多,勢必要為每一種方法都進行代理裸燎,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了泼疑。
  2. 如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外蕴纳,所有代理類也需要實現(xiàn)此方法个粱。增加了代碼維護的復(fù)雜度。

另外稻薇,如果要按照上述的方法使用代理模式塞椎,那么真實角色(委托類)必須是事先已經(jīng)存在的睛低,并將其作為代理對象的內(nèi)部屬性钱雷。但是實際使用時,一個真實角色必須對應(yīng)一個代理角色罩抗,如果大量使用會導(dǎo)致類的急劇膨脹澄暮;此外,如果事先并不知道真實角色(委托類)伸辟,該如何使用代理呢馍刮?這個問題可以通過Java的動態(tài)代理類來解決卡啰。

動態(tài)代理

概念

動態(tài)代理類的源碼是在程序運行期間由JVM根據(jù)反射等機制動態(tài)的生成,所以不存在代理類的字節(jié)碼文件匈辱。代理類和委托類的關(guān)系是在程序運行時確定亡脸。

實現(xiàn)方式

Java中實現(xiàn)動態(tài)代理主要有兩種典型方法:

  1. 使用JDK實現(xiàn)動態(tài)代理
  2. 使用CGLIB實現(xiàn)動態(tài)代理

我們主要來看一下第一種方法树酪。

JDK實現(xiàn)動態(tài)代理

JDK實現(xiàn)動態(tài)代理
步驟
  1. 創(chuàng)建被代理的類及接口
  2. 創(chuàng)建一個接口實現(xiàn)InvocationHandler的類(調(diào)用處理器)续语,它必須實現(xiàn)invoke方法
  3. 調(diào)用Proxy的靜態(tài)方法疮茄,動態(tài)創(chuàng)建一個代理類的對象
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  1. 通過代理調(diào)用方法
代碼舉例

看一個最簡單的例子力试,大部分步驟已注釋說明购裙。

Subject接口:
<pre>
/**

  • 被代理類和代理類要實現(xiàn)的公共接口
    */
    public interface Subject {
    void request();
    }
    </pre>

要被代理的類:
<pre>
/**

  • Subject接口實現(xiàn)
    */
    public class ConcreteSubject implements Subject{

    @Override
    public void request() {
    System.out.println("==========被代理對象的request方法==========");
    }

}
</pre>

InvocationHandler實現(xiàn)類:
<pre>
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**

  • 代理類的調(diào)用處理器
    */
    public class ProxyHandler implements InvocationHandler {
    //被代理對象
    private Subject target;

    public ProxyHandler(Subject target) {
    this.target=target;
    }

    /*

    • 參數(shù):
    • proxy:表示最終生成的代理類對象躏率,注意不是被代理的對象
    • method:被代理對象的方法
    • args:方法的參數(shù)
    • 返回值:
    • Object方法的返回值
      */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("=========before===========");
      Object object=method.invoke(target);
      System.out.println("=========after===========");
      return object;

    }

}
</pre>
測試類:
<pre>
import java.lang.reflect.Proxy;

public class ProxyTest {

public static void main(String[] args) {
    //1.創(chuàng)建被代理對象
    ConcreteSubject subject=new ConcreteSubject();
    //2.創(chuàng)建調(diào)用處理器對象
    ProxyHandler proxyHandler=new ProxyHandler(subject);
    //3.動態(tài)生成代理對象
    Class<?> cls=ConcreteSubject.class;//獲得被代理類的Class對象
    Subject proxySubject=(Subject)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), proxyHandler);
    //4.通過代理對象調(diào)用方法
    proxySubject.request();
    
}

}
</pre>

運行測試類薇芝,可以得到以下結(jié)果:


至此夯到,用JDK實現(xiàn)了最簡單的動態(tài)代理耍贾。
其中需要注意的地方是:

  1. 需要創(chuàng)建一個java.lang.reflect.InvocationHandler的實現(xiàn)荐开,作為代理類調(diào)用處理器简肴。該接口只有一個方法:
Object invoke(Object proxy, Method method, Object[] args)
*注:參數(shù)說明見上文代碼*
  1. 調(diào)用
    java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    獲得代理實例

參數(shù)說明:

loader:定義代理類的類加載器

interfaces:代理類需要實現(xiàn)的接口列表

h:調(diào)用處理器砰识,代理對象將會將方法調(diào)用轉(zhuǎn)發(fā)給它

返回值:

Object:一個由上述參數(shù)所指定的代理實例

動態(tài)代理實現(xiàn)思路

實現(xiàn)功能:通過Proxy的newProxyInstance返回代理對象

  1. 聲明一段源碼(動態(tài)代理)
  2. 編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
  3. 將這個類load到內(nèi)存當中,產(chǎn)生一個新的對象(代理對象)
  4. return代理對象

注:可通過慕課網(wǎng):模擬JDK動態(tài)代理實現(xiàn)思路分析及簡單實現(xiàn)了解

CGLIB

JDK提供的實現(xiàn)動態(tài)代理的方法十分強大辫狼,但是也有一定的限制膨处,主要的限制是:

只能代理實現(xiàn)接口的類频蛔,沒有實現(xiàn)接口的類不能實現(xiàn)JDK動態(tài)代理

可以使用CGLIB來解決該問題晦溪。CGLIB的特點是:

  • 針對類來實現(xiàn)代理
  • 對指定目標類生成一個子類三圆,通過方法攔截技術(shù)攔截所有父類方法的調(diào)用
  • 由于使用繼承實現(xiàn),因此不能為final修飾的類或方法實現(xiàn)代理

動態(tài)代理的應(yīng)用

AOP(面向切面編程),即在不改變原有類方法的基礎(chǔ)上舟肉,增加一些額外的業(yè)務(wù)邏輯路媚。

Spring的AOP就是通過JDK動態(tài)代理跟CGLIB動態(tài)代理來實現(xiàn)的整慎。

默認的策略是如果目標類是接口围苫,則使用JDK動態(tài)代理技術(shù),如果目標對象沒有實現(xiàn)接口拧揽,則默認會采用CGLIB代理淤袜。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铡羡,一起剝皮案震驚了整個濱河市蓖墅,隨后出現(xiàn)的幾起案子临扮,更是在濱河造成了極大的恐慌,老刑警劉巖贪壳,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闰靴,死亡現(xiàn)場離奇詭異,居然都是意外死亡配猫,警方通過查閱死者的電腦和手機泵肄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門腐巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玄括,你說我怎么就攤上這事冯丙。” “怎么了遭京?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵胃惜,是天一觀的道長。 經(jīng)常有香客問我洁墙,道長蛹疯,這世上最難降的妖魔是什么戒财? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任热监,我火速辦了婚禮,結(jié)果婚禮上饮寞,老公的妹妹穿的比我還像新娘。我一直安慰自己苦始,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著役电,像睡著了一般法瑟。 火紅的嫁衣襯著肌膚如雪窝剖。 梳的紋絲不亂的頭發(fā)上枯芬,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天蒜埋,我揣著相機與錄音待错,去河邊找鬼。 笑死瓜客,一個胖子當著我的面吹牛谱仪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敬尺,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呜舒,長吁一口氣:“原來是場噩夢啊……” “哼袭蝗!你這毒婦竟也來了朵逝?” 一聲冷哼從身側(cè)響起配名,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后为朋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體习寸,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡威鹿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年幼东,在試婚紗的時候發(fā)現(xiàn)自己被綠了脓杉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凌净,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斩芭,到底是詐尸還是另有隱情,我是刑警寧澤迁筛,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布贪庙,位于F島的核電站,受9級特大地震影響导披,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜止毕,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一闯传、第九天 我趴在偏房一處隱蔽的房頂上張望荚孵。 院中可真熱鬧收叶,春花似錦、人聲如沸澄峰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抛寝,卻和暖如春夷狰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背土至。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工楷扬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓辖所,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揩环。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

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