深入理解CGLIB動(dòng)態(tài)代理機(jī)制

本文是基于CGLIB 3.1進(jìn)行探究的

cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.

在Spring AOP中奖慌,通常會(huì)用它來生成AopProxy對(duì)象。不僅如此费尽,在Hibernate中PO(Persistant Object 持久化對(duì)象)字節(jié)碼的生成工作也要靠它來完成究孕。

本文將深入探究CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)機(jī)制枫虏,配合下面這篇文章一起食用口味更佳:
深入理解JDK動(dòng)態(tài)代理機(jī)制

一柒巫、CGLIB動(dòng)態(tài)代理示例

下面由一個(gè)簡單的示例開始我們對(duì)CGLIB動(dòng)態(tài)代理的介紹:

為了后續(xù)編碼的順利進(jìn)行氛雪,我們需要使用Maven引入CGLIB的包
圖1.1 被代理類
圖1.2 實(shí)現(xiàn)MethodInterceptor接口生成方法攔截器
圖1.3 生成代理類對(duì)象并打印在代理類對(duì)象調(diào)用方法之后的執(zhí)行結(jié)果

JDK代理要求被代理的類必須實(shí)現(xiàn)接口,有很強(qiáng)的局限性补憾。而CGLIB動(dòng)態(tài)代理則沒有此類強(qiáng)制性要求。簡單的說卷员,CGLIB會(huì)讓生成的代理類繼承被代理類盈匾,并在代理類中對(duì)代理方法進(jìn)行強(qiáng)化處理(前置處理、后置處理等)毕骡。在CGLIB底層削饵,其實(shí)是借助了ASM這個(gè)非常強(qiáng)大的Java字節(jié)碼生成框架。

二挺峡、生成代理類對(duì)象

從圖1.3中我們看到葵孤,代理類對(duì)象是由Enhancer類創(chuàng)建的。Enhancer是CGLIB的字節(jié)碼增強(qiáng)器橱赠,可以很方便的對(duì)類進(jìn)行拓展尤仍,如圖1.3中的為類設(shè)置Superclass。

創(chuàng)建代理對(duì)象的幾個(gè)步驟:

  • 生成代理類的二進(jìn)制字節(jié)碼文件狭姨;
  • 加載二進(jìn)制字節(jié)碼宰啦,生成Class對(duì)象( 例如使用Class.forName()方法 );
  • 通過反射機(jī)制獲得實(shí)例構(gòu)造饼拍,并創(chuàng)建代理類對(duì)象

我們來看看將代理類Class文件反編譯之后的Java代碼

package proxy;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06
  extends HelloServiceImpl
  implements Factory
{
  private boolean CGLIB$BOUND;
  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
  private static final Callback[] CGLIB$STATIC_CALLBACKS;
  private MethodInterceptor CGLIB$CALLBACK_0;
  private static final Method CGLIB$sayHello$0$Method;
  private static final MethodProxy CGLIB$sayHello$0$Proxy;
  private static final Object[] CGLIB$emptyArgs;
  private static final Method CGLIB$finalize$1$Method;
  private static final MethodProxy CGLIB$finalize$1$Proxy;
  private static final Method CGLIB$equals$2$Method;
  private static final MethodProxy CGLIB$equals$2$Proxy;
  private static final Method CGLIB$toString$3$Method;
  private static final MethodProxy CGLIB$toString$3$Proxy;
  private static final Method CGLIB$hashCode$4$Method;
  private static final MethodProxy CGLIB$hashCode$4$Proxy;
  private static final Method CGLIB$clone$5$Method;
  private static final MethodProxy CGLIB$clone$5$Proxy;
  
  static void CGLIB$STATICHOOK1()
  {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    Class localClass1 = Class.forName("proxy.HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06");
    Class localClass2;
    Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
    CGLIB$finalize$1$Method = tmp95_92[0];
    CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
    Method[] tmp115_95 = tmp95_92;
    CGLIB$equals$2$Method = tmp115_95[1];
    CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
    Method[] tmp135_115 = tmp115_95;
    CGLIB$toString$3$Method = tmp135_115[2];
    CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
    Method[] tmp155_135 = tmp135_115;
    CGLIB$hashCode$4$Method = tmp155_135[3];
    CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
    Method[] tmp175_155 = tmp155_135;
    CGLIB$clone$5$Method = tmp175_155[4];
    CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
    tmp175_155;
    Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "sayHello", "()V" }, (localClass2 = Class.forName("proxy.HelloServiceImpl")).getDeclaredMethods());
    CGLIB$sayHello$0$Method = tmp223_220[0];
    CGLIB$sayHello$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "sayHello", "CGLIB$sayHello$0");
    tmp223_220;
    return;
  }
  
  final void CGLIB$sayHello$0()
  {
    super.sayHello();
  }
  
  public final void sayHello()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    if (this.CGLIB$CALLBACK_0 != null) {
      return;
    }
    super.sayHello();
  }
  
  final void CGLIB$finalize$1()
    throws Throwable
  {
    super.finalize();
  }
  
  protected final void finalize()
    throws Throwable
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    if (this.CGLIB$CALLBACK_0 != null) {
      return;
    }
    super.finalize();
  }
  
  final boolean CGLIB$equals$2(Object paramObject)
  {
    return super.equals(paramObject);
  }
  
  public final boolean equals(Object paramObject)
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    {
      Object tmp41_36 = tmp17_14.intercept(this, CGLIB$equals$2$Method, new Object[] { paramObject }, CGLIB$equals$2$Proxy);
      tmp41_36;
      return tmp41_36 == null ? false : ((Boolean)tmp41_36).booleanValue();
    }
    return super.equals(paramObject);
  }
  
  final String CGLIB$toString$3()
  {
    return super.toString();
  }
  
  public final String toString()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      return (String)tmp17_14.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy);
    }
    return super.toString();
  }
  
  final int CGLIB$hashCode$4()
  {
    return super.hashCode();
  }
  
  public final int hashCode()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    {
      Object tmp36_31 = tmp17_14.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
      tmp36_31;
      return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();
    }
    return super.hashCode();
  }
  
  final Object CGLIB$clone$5()
    throws CloneNotSupportedException
  {
    return super.clone();
  }
  
  protected final Object clone()
    throws CloneNotSupportedException
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      return tmp17_14.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy);
    }
    return super.clone();
  }
  
  public static MethodProxy CGLIB$findMethodProxy(Signature paramSignature)
  {
    String tmp4_1 = paramSignature.toString();
    switch (tmp4_1.hashCode())
    {
    case -1574182249: 
      if (tmp4_1.equals("finalize()V")) {
        return CGLIB$finalize$1$Proxy;
      }
      break;
    }
  }
  
  public HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06()
  {
    CGLIB$BIND_CALLBACKS(this);
  }
  
  public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] paramArrayOfCallback)
  {
    CGLIB$THREAD_CALLBACKS.set(paramArrayOfCallback);
  }
  
  public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] paramArrayOfCallback)
  {
    CGLIB$STATIC_CALLBACKS = paramArrayOfCallback;
  }
  
  private static final void CGLIB$BIND_CALLBACKS(Object paramObject)
  {
    82ef2d06 local82ef2d06 = (82ef2d06)paramObject;
    if (!local82ef2d06.CGLIB$BOUND)
    {
      local82ef2d06.CGLIB$BOUND = true;
      Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();
      if (tmp23_20 == null)
      {
        tmp23_20;
        CGLIB$STATIC_CALLBACKS;
      }
      local82ef2d06.CGLIB$CALLBACK_0 = (// INTERNAL ERROR //

三赡模、對(duì)委托類進(jìn)行代理

我們上面貼出了生成的代理類源碼。以我們上面的例子為參考师抄,下面我們總結(jié)一下CGLIB在進(jìn)行代理的時(shí)候都進(jìn)行了哪些工作呢

  • 生成的代理類HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06繼承被代理類HelloServiceImpl漓柑。在這里我們需要注意一點(diǎn):如果委托類被final修飾,那么它不可被繼承叨吮,即不可被代理辆布;同樣,如果委托類中存在final修飾的方法茶鉴,那么該方法也不可被代理锋玲;
  • 代理類會(huì)為委托方法生成兩個(gè)方法,一個(gè)是重寫的sayHello方法涵叮,另一個(gè)是CGLIB$sayHello$0方法惭蹂,我們可以看到它是直接調(diào)用父類的sayHello方法;
  • 當(dāng)執(zhí)行代理對(duì)象的sayHello方法時(shí)割粮,會(huì)首先判斷一下是否存在實(shí)現(xiàn)了MethodInterceptor接口的CGLIB$CALLBACK_0;盾碗,如果存在,則將調(diào)用MethodInterceptor中的intercept方法舀瓢,如圖2.1廷雅。

圖2.1 intercept方法

圖2.2 代理類為每個(gè)委托方法都會(huì)生成兩個(gè)方法

intercept方法中,我們除了會(huì)調(diào)用委托方法,還會(huì)進(jìn)行一些增強(qiáng)操作榜轿。在Spring AOP中幽歼,典型的應(yīng)用場景就是在某些敏感方法執(zhí)行前后進(jìn)行操作日志記錄。

我們從圖2.1中看到谬盐,調(diào)用委托方法是通過代理方法的MethodProxy對(duì)象調(diào)用invokeSuper方法來執(zhí)行的甸私,下面我們看看invokeSuper方法中的玄機(jī):

圖2.3 invokeSuper方法

在這里好像不能直接看出代理方法的調(diào)用。沒關(guān)系飞傀,我會(huì)慢慢介紹皇型。
我們知道,在JDK動(dòng)態(tài)代理中方法的調(diào)用是通過反射來完成的砸烦。如果有對(duì)此不太了解的同學(xué)弃鸦,可以看下我之前的博客----深入理解JDK動(dòng)態(tài)代理機(jī)制。但是在CGLIB中幢痘,方法的調(diào)用并不是通過反射來完成的唬格,而是直接對(duì)方法進(jìn)行調(diào)用:FastClass對(duì)Class對(duì)象進(jìn)行特別的處理,比如將會(huì)用數(shù)組保存method的引用颜说,每次調(diào)用方法的時(shí)候都是通過一個(gè)index下標(biāo)來保持對(duì)方法的引用购岗。比如下面的getIndex方法就是通過方法簽名來獲得方法在存儲(chǔ)了Class信息的數(shù)組中的下標(biāo)。

圖2.4 getIndex方法
圖2.5 FastClassInfo類中持有兩個(gè)FastClass對(duì)象的引用.png

以我們上面的sayHello方法為例门粪,f1指向委托類對(duì)象喊积,f2指向代理類對(duì)象,i1和i2分別代表著sayHello方法以及CGLIB$sayHello$0方法在對(duì)象信息數(shù)組中的下標(biāo)玄妈。

到此為止CGLIB動(dòng)態(tài)代理機(jī)制就介紹完了乾吻,下面給出三種代理方式之間對(duì)比。

代理方式 實(shí)現(xiàn) 優(yōu)點(diǎn) 缺點(diǎn) 特點(diǎn)
JDK靜態(tài)代理 代理類與委托類實(shí)現(xiàn)同一接口拟蜻,并且在代理類中需要硬編碼接口 實(shí)現(xiàn)簡單绎签,容易理解 代理類需要硬編碼接口,在實(shí)際應(yīng)用中可能會(huì)導(dǎo)致重復(fù)編碼瞭郑,浪費(fèi)存儲(chǔ)空間并且效率很低 好像沒啥特點(diǎn)
JDK動(dòng)態(tài)代理 代理類與委托類實(shí)現(xiàn)同一接口辜御,主要是通過代理類實(shí)現(xiàn)InvocationHandler并重寫invoke方法來進(jìn)行動(dòng)態(tài)代理的鸭你,在invoke方法中將對(duì)方法進(jìn)行增強(qiáng)處理 不需要硬編碼接口屈张,代碼復(fù)用率高 只能夠代理實(shí)現(xiàn)了接口的委托類 底層使用反射機(jī)制進(jìn)行方法的調(diào)用
CGLIB動(dòng)態(tài)代理 代理類將委托類作為自己的父類并為其中的非final委托方法創(chuàng)建兩個(gè)方法,一個(gè)是與委托方法簽名相同的方法袱巨,它在方法中會(huì)通過super調(diào)用委托方法阁谆;另一個(gè)是代理類獨(dú)有的方法。在代理方法中愉老,它會(huì)判斷是否存在實(shí)現(xiàn)了MethodInterceptor接口的對(duì)象场绿,若存在則將調(diào)用intercept方法對(duì)委托方法進(jìn)行代理 可以在運(yùn)行時(shí)對(duì)類或者是接口進(jìn)行增強(qiáng)操作,且委托類無需實(shí)現(xiàn)接口 不能對(duì)final類以及final方法進(jìn)行代理 底層將方法全部存入一個(gè)數(shù)組中嫉入,通過數(shù)組索引直接進(jìn)行方法調(diào)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焰盗,一起剝皮案震驚了整個(gè)濱河市璧尸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熬拒,老刑警劉巖爷光,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澎粟,居然都是意外死亡蛀序,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門活烙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來徐裸,“玉大人,你說我怎么就攤上這事啸盏≈睾兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵回懦,是天一觀的道長檬姥。 經(jīng)常有香客問我,道長粉怕,這世上最難降的妖魔是什么健民? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮贫贝,結(jié)果婚禮上秉犹,老公的妹妹穿的比我還像新娘。我一直安慰自己稚晚,他們只是感情好崇堵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著客燕,像睡著了一般鸳劳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上也搓,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天赏廓,我揣著相機(jī)與錄音,去河邊找鬼傍妒。 笑死幔摸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颤练。 我是一名探鬼主播既忆,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了患雇?” 一聲冷哼從身側(cè)響起跃脊,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苛吱,沒想到半個(gè)月后匾乓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡又谋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年拼缝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彰亥。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咧七,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出任斋,到底是詐尸還是另有隱情继阻,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布废酷,位于F島的核電站瘟檩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏澈蟆。R本人自食惡果不足惜墨辛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望趴俘。 院中可真熱鬧睹簇,春花似錦、人聲如沸寥闪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疲憋。三九已至凿渊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缚柳,已是汗流浹背埃脏。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喂击,地道東北人剂癌。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓淤翔,卻偏偏與公主長得像翰绊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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