靜態代理、動態代理和CGLIB代理

一、介紹

代理模式是一種設計模式,提供了對目標對象額外的訪問方式,即通過代理對象訪問目標對象,這樣可以在不修改原目標對象的前提下,提供額外的功能操作,擴展目標對象的功能。

image

根據代理類的生成時間不同可以將代理分為靜態代理和動態代理兩種。

靜態代理即為JDK原生靜態代理。

而動態代理方式又可以分為兩種:

1. JDK原生動態代理

2. CGLIB動態代理。

靜態代理的對象在編譯時生成,而動態代理的對象只在程序運行時生成,因此靜態代理的類會產生對應的字節碼文件,動態代理則不然。

二、靜態代理

這種代理方式需要代理對象和目標對象實現一樣的接口。

優點:

可以在不修改目標對象的前提下擴展目標對象的功能。

缺點:

1. 冗余。會產生過多的代理類。

2. 不易維護。隨著接口增加方法,目標對象與代理對象都要進行相應修改。

// 接口
interface Hello{
    String sayHello(String str);
}
// 接口類實現
class HelloImp implements Hello{
    @Override
    public String sayHello(String str) {
        return "HelloImp: " + str;
    }
}
// 靜態代理方式
class HelloProxy implements Hello{
    ...
    private Hello hello = new HelloImp();
    @Override
    public String sayHello(String str) {
        return hello.sayHello(str);
    }
}

以上代碼中,對應代理模式中的角色,接口Hello為抽象角色,實現類HelloImpl為真實角色,而類HelloProxy為代理角色。
注:
靜態代理類HelloProxy作為HelloImp的代理,同樣實現了相同的Hello接口。

三、JDK原生動態代理

動態代理對象不需要實現接口,但是要求目標對象必須實現接口,否則不能使用動態代理。

/**JDK原生動態代理
*實現接口InvocationHandler,
*方法調用會被轉發到該類的invoke()方法
*/
class HelloProxy implements InvocationHandler{
    ...
    private Hello hello;
    public HelloProxy (Hello hello) {
        this.hello = hello;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("sayHello".equals(method.getName())) {
            logger.info("You said: " + Arrays.toString(args));
        }
        return method.invoke(hello, args);
    }
}

使用時,通過JDK動態代理獲取Hello的代理對象。

public class TestProxy {
    @Test
    public void testDynamicProxy (){
       Hello hello = (Hello)Proxy.newProxyInstance(
    getClass().getClassLoader(), // 類加載器
    new Class<?>[] {Hello.class}, // 代理需要實現的接口,可以有多個
    new HelloProxy (new HelloImp()));// 方法調用的實際處理者
       System.out.println(hello.sayHello("Hello, world!’"));
    }
}

參數動態創建代理對象。三個參數的意義如下:

  1. loader,指定代理對象的類加載器;
  2. interfaces,代理對象需要實現的接口,可以同時指定多個接口;
  3. handler,方法調用的實際處理者,代理對象的方法調用都會轉發到這里。

注:
newProxyInstance()會返回一個實現了指定接口的代理對象,對該對象的所有方法調用都會轉發給InvocationHandler.invoke()方法。

特點:

  1. 代理對象是在程序運行時產生的,而不是編譯期;
  2. 對代理對象的所有接口方法調用都會轉發到InvocationHandler.invoke()方法,在invoke()方法里可以加入任何邏輯。

四、CGLIB動態代理

CGLIB (Code Generation Library )是一個第三方代碼生成類庫,運行時在內存中動態生成一個子類對象從而實現對目標對象功能的擴展。

特點

  1. JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口。
    如果想代理沒有實現接口的類,就可以使用CGLIB實現。
  2. 它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
  3. CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM,來轉換字節碼并生成新的類。
public class HelloClass {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

此時因為沒有實現接口該類無法使用JDK代理,通過CGLIB代理實現如下:

  1. 實現接口MethodInterceptor,方法調用會被轉發到該類的intercept()方法;
  2. 在需要使用類HelloClass的時候,通過CGLIB動態代理獲取代理對象。
/**CGLIB動態代理
*實現接口MethodInterceptor,方法調用會被轉發到該類的intercept()方法
*/
class CGLIBProxy implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        return proxy.invokeSuper(obj, args);
    }
}
//在需要使用HelloClass的時候,通過CGLIB動態代理獲取代理對象。
public class TestProxy {
    @Test
    public void testDynamicProxy (){
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(HelloClass.class);
      enhancer.setCallback(new CGLIBProxy());

      HelloClass hello = (HelloClass)enhancer.create();
      System.out.println(hello.sayHello("Hello, world!"));
}

五、綜上

  1. 靜態代理只為一個目標對象服務,如果目標對象過多,則會產生很多代理類。
  2. JDK動態代理需要目標對象實現業務接口
  3. 靜態代理在編譯時產生class字節碼文件,可以直接使用,效率高。
  4. JDK動態代理必須實現InvocationHandler接口,通過反射代理方法,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。
  5. CGLIB代理無需實現接口,通過生成類字節碼實現代理,比反射稍快,不存在性能問題
  6. CGLIB會繼承目標對象,需要重寫方法,所以目標對象不能為final類。

引用

Java Proxy和CGLIB動態代理原理
Java三種代理模式:靜態代理、動態代理和cglib代理
Java靜態代理&動態代理筆記
10分鐘看懂動態代理設計模式
Java Proxy 和 CGLIB 動態代理原理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容