深入理解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閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穿稳,死亡現(xiàn)場離奇詭異晌坤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)它改,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門央拖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人专控,你說我怎么就攤上這事遏餐。” “怎么了柏蘑?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵粹庞,是天一觀的道長庞溜。 經(jīng)常有香客問我,道長又官,這世上最難降的妖魔是什么旅掂? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任商虐,我火速辦了婚禮崖疤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叮趴。我一直安慰自己权烧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著板祝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪孤里。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音琢蛤,去河邊找鬼博其。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慕淡,可吹牛的內(nèi)容都是我干的峰髓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼疾掰,長吁一口氣:“原來是場噩夢啊……” “哼徐紧!你這毒婦竟也來了并级?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤稻励,失蹤者是張志新(化名)和其女友劉穎愈涩,沒想到半個(gè)月后履婉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舰蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年身害,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侍瑟。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丙猬,死狀恐怖茧球,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抢埋,我是刑警寧澤揪垄,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站捡鱼,受9級(jí)特大地震影響酷愧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翘鸭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拱烁。 院中可真熱鬧噩翠,春花似錦、人聲如沸擅笔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弯淘。三九已至庐橙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間态鳖,已是汗流浹背浆竭。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羽戒,地道東北人虎韵。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓包蓝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亡电。 傳聞我的和親對(duì)象是個(gè)殘疾皇子硅瞧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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