詳解Java動(dòng)態(tài)代理機(jī)制(二)----cglib實(shí)現(xiàn)動(dòng)態(tài)代理

?????上篇文章的結(jié)尾我們介紹了普通的jdk實(shí)現(xiàn)動(dòng)態(tài)代理的主要不足在于:它只能代理實(shí)現(xiàn)了接口的類(lèi)辰妙,如果一個(gè)類(lèi)沒(méi)有繼承于任何的接口压状,那么就不能代理該類(lèi)仆抵,原因是我們動(dòng)態(tài)生成的所有代理類(lèi)都必須繼承Proxy這個(gè)類(lèi),正是因?yàn)镴ava的單繼承种冬,所以注定會(huì)拋棄原類(lèi)型的父類(lèi)镣丑。而我們的cglib通過(guò)掃描該類(lèi)以及其父類(lèi)中所有的public非final修飾的方法,通過(guò)asm定義該類(lèi)的子類(lèi)字節(jié)碼娱两,其中該子類(lèi)重寫(xiě)了父類(lèi)所有的方法莺匠,然后返回該子類(lèi)的實(shí)例作為代理類(lèi)。也就是說(shuō)我們的cglib是用該類(lèi)的子類(lèi)作為代理類(lèi)來(lái)實(shí)現(xiàn)代理操作的十兢。當(dāng)然cglib的缺點(diǎn)也是呼之欲出部蛇,對(duì)于被代理類(lèi)中的非public或者final修飾的方法堪伍,不能實(shí)現(xiàn)代理纱控。

?????在詳細(xì)介紹cglib之前梳星,我們先簡(jiǎn)單介紹下ASM框架,這是一個(gè)小而快的字節(jié)碼處理框架异袄,它負(fù)責(zé)生成從被代理類(lèi)中掃描出的方法的字節(jié)碼并將這些方法生成字節(jié)碼暫存在內(nèi)存中通砍。然后我們通過(guò)方法將這些字節(jié)碼轉(zhuǎn)換成class類(lèi)型,最后利用反射創(chuàng)建代理類(lèi)的實(shí)例返回烤蜕。下面看一個(gè)完整的實(shí)例封孙,稍后從源代碼的角度分析這個(gè)實(shí)例:

//定義一個(gè)接口
public interface MyInterface {
    public void sayHello();
}
//定義一個(gè)ClassB類(lèi)
public class ClassB {
    public void welcome(){
        System.out.println("welcom walker");
    }
}

//模擬被代理的類(lèi),繼承了ClassB和接口MyInterface
public class ClassA extends ClassB implements MyInterface {
    public void sayHello(){
        System.out.println("hello walker");
    }
}
//定義一個(gè)回調(diào)實(shí)例讽营,稍后解釋
public class MyMethod implements MethodInterceptor {

    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                            MethodProxy proxy) throws Throwable{
        proxy.invokeSuper(obj, args);
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {

        ClassA ca = new ClassA();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ClassA.class);
        enhancer.setCallback(new MyMethod());
        ClassA my = (ClassA)enhancer.create();
        my.welcome();
    }
}
輸出結(jié)果:welcom walker

我們看到虎忌,此處我們獲取了ClassA的代理對(duì)象,然后調(diào)用了ClassA父類(lèi)中的welcome方法橱鹏。這也間接證明了我們通過(guò)cglib代理了ClassA的父類(lèi)中的方法膜蠢,這一點(diǎn)使用jdk實(shí)現(xiàn)的動(dòng)態(tài)處理是做不到的堪藐。下面我們解釋原理。

在這之前挑围,由于cglib是第三方庫(kù)礁竞,所以我們需要下載相應(yīng)的jar文件,主要包含兩個(gè)文件杉辙,一個(gè)是cglib的jar模捂,還有一個(gè)是cglib依賴(lài)的ASM框架的jar文件,注意這兩個(gè)jar的版本不能沖突蜘矢。

我們從main方法的主體代碼中可以看出狂男,Enhancer 類(lèi)是創(chuàng)建代理實(shí)例的核心類(lèi)。沒(méi)錯(cuò)品腹,該類(lèi)負(fù)責(zé)整個(gè)代理對(duì)象的生命周期岖食,它就像是一個(gè)工具一樣,提供了很多方法幫助我們創(chuàng)建代理實(shí)例珍昨。首先我們調(diào)用了setSuperclass方法設(shè)置父類(lèi)型,其實(shí)也就是將被代理對(duì)象傳入句喷,因?yàn)槲覀冎罢f(shuō)過(guò)cglib創(chuàng)建的代理類(lèi)是原對(duì)象的子類(lèi)型镣典,所以這里稱(chēng)原類(lèi)型實(shí)例為父類(lèi)也是合理的。

跟進(jìn)去唾琼,我們看到:

public void setSuperclass(Class superclass)
  {
    if ((superclass != null) && (superclass.isInterface())) {
      setInterfaces(new Class[] { superclass });
    } else if ((superclass != null) && (superclass.equals(Object.class))) {
      this.superclass = null;
    } else {
      this.superclass = superclass;
    }
  }

這段代碼的主要意思是:如果傳入的類(lèi)型是接口的話(huà)兄春,保存在專(zhuān)門(mén)用于保存接口類(lèi)型的變量中。

private Class[] interfaces;

如果傳入的類(lèi)型是Object類(lèi)型的話(huà)锡溯,將用于保存普通類(lèi)類(lèi)型的變量賦值為null赶舆,否則保存該傳入的參數(shù)的值在該變量中。這些操作過(guò)程中保存的一些數(shù)值是為了在最后create的時(shí)候提供幫助祭饭。

接下來(lái)是setCallback方法芜茵,該方法設(shè)置了回調(diào)。也就是將來(lái)對(duì)我們代理中方法的訪(fǎng)問(wèn)會(huì)轉(zhuǎn)發(fā)到該回調(diào)中倡蝙,所有自定義的回調(diào)類(lèi)必須繼承MethodInterceptor接口并實(shí)現(xiàn)其intercept方法九串,這一點(diǎn)和jdk的InvocationHandler類(lèi)似。這里的intercept有幾個(gè)參數(shù):

  • Object obj:被代理的原對(duì)象
  • Method method:被調(diào)用的當(dāng)前方法
  • Object[] args:該方法的參數(shù)集合
  • MethodProxy proxy:被調(diào)用方法的代理寺鸥,它可以和method完成同樣的事情猪钮,但是它使用FastClass機(jī)制非反射執(zhí)行方法,效率高

我們對(duì)于所有調(diào)用代理方法的請(qǐng)求胆建,轉(zhuǎn)發(fā)到invokeSuper方法中烤低,該方法源碼如下:

//fastclassinfo類(lèi)
    private static class FastClassInfo
  {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
    
    private FastClassInfo() {}
    
    FastClassInfo(MethodProxy.1 x0)
    {
      this();
    }
  }
  
 
  public Object invokeSuper(Object obj, Object[] args)
    throws Throwable
  {
    try
    {
      init();
      FastClassInfo fci = this.fastClassInfo;
      return fci.f2.invoke(fci.i2, obj, args);
    }
    catch (InvocationTargetException e)
    {
      throw e.getTargetException();
    }
  }

其中fastclassinfo類(lèi)中,幾個(gè)參數(shù)的意思解釋下笆载,f1指向被代理對(duì)象扑馁,f2指向代理類(lèi)對(duì)象涯呻,i1和i2分別是代理類(lèi)中的該方法的兩個(gè)索引。也就是這種fastclass機(jī)制并不是通過(guò)反射找到指定的方法的檐蚜,而是在創(chuàng)建代理類(lèi)的時(shí)候?yàn)槠渲械姆椒ńash索引魄懂,這樣調(diào)用的時(shí)候通過(guò)索引調(diào)用提高了效率。最后調(diào)用了代理類(lèi)的方法闯第,也就是重寫(xiě)了父類(lèi)的方法市栗。

最后也是最核心的一步是create方法的調(diào)用,這個(gè)方法才是實(shí)際創(chuàng)建代理實(shí)例的方法咳短,我們看源碼:

  public Object create()
  {
    this.classOnly = false;
    this.argumentTypes = null;
    return createHelper();
  }

該方法主要設(shè)置了兩個(gè)參數(shù)配置填帽,指定將要?jiǎng)?chuàng)建的對(duì)象不僅僅是一個(gè)類(lèi),指定參數(shù)為空咙好。至于這兩個(gè)參數(shù)有何作用篡腌,還需要往下追,我們看createHelper類(lèi):

  private Object createHelper()
  {
    validate();
    if (this.superclass != null) {
      setNamePrefix(this.superclass.getName());
    } else if (this.interfaces != null) {
      setNamePrefix(this.interfaces[ReflectUtils.findPackageProtected(this.interfaces)].getName());
    }
    return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
  }

validate()方法主要對(duì)于一些參數(shù)進(jìn)行校驗(yàn)勾效,如果不符合創(chuàng)建實(shí)例的標(biāo)準(zhǔn)將拋出異常嘹悼,我們可以簡(jiǎn)單的看一眼:

private void validate()
  {
    if ((this.classOnly ^ this.callbacks == null))
    {
      if (this.classOnly) {
        throw new IllegalStateException("createClass does not accept callbacks");
      }
      throw new IllegalStateException("Callbacks are required");
    }
    if ((this.classOnly) && (this.callbackTypes == null)) {
      throw new IllegalStateException("Callback types are required");
    }
    if ((this.callbacks != null) && (this.callbackTypes != null))
    {
      if (this.callbacks.length != this.callbackTypes.length) {
        throw new IllegalStateException("Lengths of callback and callback types array must be the same");
..........
..........
.........      
 }

主要還是判斷回調(diào)是否指定,類(lèi)型是否正確等层宫,如果不符合創(chuàng)建條件就拋出異常杨伙。我們回去,接著就做了兩個(gè)判斷萌腿,用于指定被創(chuàng)建的代理類(lèi)的名稱(chēng)限匣,我們暫時(shí)不管他。又到了一個(gè)核心的方法毁菱,該方法將創(chuàng)建代理類(lèi)并返回該類(lèi)實(shí)例米死。首先我們看參數(shù)都是是什么意思,就一個(gè)參數(shù)贮庞,該參數(shù)是由KEY_FACTORY.newInstance方法返回的一個(gè)Object類(lèi)型峦筒,我們看到在該方法的傳入?yún)?shù)中,包括了父類(lèi)類(lèi)名或者接口名窗慎,回調(diào)類(lèi)型勘天,版本號(hào)等。該方法實(shí)際上返回了一個(gè)該代理類(lèi)的一個(gè)唯一標(biāo)識(shí)捉邢,這還不是關(guān)鍵脯丝,最關(guān)鍵的方法是這個(gè)create方法:

  protected Object create(Object key)
  {
    try
    {
      Class gen = null;
      synchronized (this.source)
      {
        ClassLoader loader = getClassLoader();
        Map cache2 = null;
        cache2 = (Map)this.source.cache.get(loader);
        if (cache2 == null)
        {
          cache2 = new HashMap();
          cache2.put(NAME_KEY, new HashSet());
          this.source.cache.put(loader, cache2);
        }
        else if (this.useCache)
        {
          Reference ref = (Reference)cache2.get(key);
          gen = (Class)(ref == null ? null : ref.get());
        }
        if (gen == null)
        {
          Object save = CURRENT.get();
          CURRENT.set(this);
          try
          {
            this.key = key;
            if (this.attemptLoad) {
              try
              {
                gen = loader.loadClass(getClassName());
              }
              catch (ClassNotFoundException e) {}
            }
            if (gen == null)
            {
              b = this.strategy.generate(this);
              String className = ClassNameReader.getClassName(new ClassReader(b));
              getClassNameCache(loader).add(className);
              gen = ReflectUtils.defineClass(className, b, loader);
            }
            if (this.useCache) {
              cache2.put(key, new WeakReference(gen));
            }
            byte[] b = firstInstance(gen);
            
            CURRENT.set(save);return b;
          }
          finally
          {
            CURRENT.set(save);
          }
        }
      }
      return firstInstance(gen);
//省去了異常捕獲的代碼塊

如果usecache為為true表明該代理類(lèi)已經(jīng)在cache中了,直接返回引用即可伏伐。否則通過(guò) this.strategy.generate(this);方法生成該代理類(lèi)的字節(jié)碼宠进,然后通過(guò)通過(guò)類(lèi)加載器加載該字節(jié)碼生成class類(lèi)型,最后通過(guò)firstInstance方法生成代理類(lèi)的實(shí)例返回藐翎。

最后我們看一眼剛才生成的代理的源碼:

//代碼很多材蹬,此處貼出部分
public class ClassA$$EnhancerByCGLIB$$64984e8e extends ClassA
    implements Factory
{
    final void CGLIB$sayHello$0()
    {
            super.sayHello();
    }
    public final void sayHello()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$sayHello$0$Method;
        CGLIB$emptyArgs;
        CGLIB$sayHello$0$Proxy;
        intercept();
        return;
        super.sayHello();
        return;
    }

    final void CGLIB$welcome$1()
    {
        super.welcome();
    }

    public final void welcome()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$welcome$1$Method;
        CGLIB$emptyArgs;
        CGLIB$welcome$1$Proxy;
        intercept();
        return;
        super.welcome();
        return;
    }
....
....
}

從中我們看到实幕,該類(lèi)ClassA$$EnhancerByCGLIB$$64984e8e繼承自ClassA,實(shí)現(xiàn)了接口factory堤器。并且在其中我們看到不僅是父類(lèi)ClassA中的方法sayHello在其中被重寫(xiě)了之外昆庇,ClassA的父類(lèi)ClassB中的welcome方法也被重寫(xiě)了。足以見(jiàn)得闸溃,cglib利用繼承的方式動(dòng)態(tài)創(chuàng)建了被代理類(lèi)的子類(lèi)整吆,通過(guò)ASM生成父類(lèi)中所有public非final修飾的方法,實(shí)現(xiàn)了代理辉川。

最后稍微小結(jié)下表蝙,cglib的實(shí)現(xiàn)代理的邏輯。首先我們通過(guò)Enhancer實(shí)例設(shè)置被代理類(lèi)乓旗,然后設(shè)置該代理類(lèi)的回調(diào)府蛇,也就是在訪(fǎng)問(wèn)代理類(lèi)方法的時(shí)候會(huì)首先轉(zhuǎn)向該回調(diào),在回調(diào)中我們調(diào)用invokeSuper方法以fastclass這種非反射機(jī)制快速的調(diào)用到代理類(lèi)中的方法屿愚,其中代理類(lèi)中方法又調(diào)用原類(lèi)型的對(duì)應(yīng)方法汇跨。

由于cglib已經(jīng)停止維護(hù)好多年,導(dǎo)致參考文檔很少妆距,學(xué)習(xí)難度很大穷遂,此篇文章也是作者研讀jdk和網(wǎng)上優(yōu)秀博文總結(jié),不當(dāng)之處毅厚,望大家指出塞颁,學(xué)習(xí) 浦箱!學(xué)習(xí)吸耿!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酷窥,隨后出現(xiàn)的幾起案子咽安,更是在濱河造成了極大的恐慌,老刑警劉巖蓬推,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妆棒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沸伏,警方通過(guò)查閱死者的電腦和手機(jī)糕珊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)毅糟,“玉大人红选,你說(shuō)我怎么就攤上這事∧妨恚” “怎么了喇肋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵坟乾,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蝶防,道長(zhǎng)甚侣,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任间学,我火速辦了婚禮殷费,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘菱鸥。我一直安慰自己宗兼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布氮采。 她就那樣靜靜地躺著殷绍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹊漠。 梳的紋絲不亂的頭發(fā)上主到,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音躯概,去河邊找鬼登钥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娶靡,可吹牛的內(nèi)容都是我干的牧牢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼姿锭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼塔鳍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起呻此,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤轮纫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后焚鲜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掌唾,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年忿磅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糯彬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葱她,死狀恐怖撩扒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情览效,我是刑警寧澤却舀,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布虫几,位于F島的核電站,受9級(jí)特大地震影響挽拔,放射性物質(zhì)發(fā)生泄漏辆脸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一螃诅、第九天 我趴在偏房一處隱蔽的房頂上張望啡氢。 院中可真熱鬧,春花似錦术裸、人聲如沸倘是。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)搀崭。三九已至,卻和暖如春猾编,著一層夾襖步出監(jiān)牢的瞬間瘤睹,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工答倡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轰传,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓瘪撇,卻偏偏與公主長(zhǎng)得像获茬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倔既,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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

  • 之前介紹的反射和注解都是Java中的動(dòng)態(tài)特性恕曲,還有即將介紹的動(dòng)態(tài)代理也是Java中的一個(gè)動(dòng)態(tài)特性。這些動(dòng)態(tài)特性使得...
    Single_YAM閱讀 2,052評(píng)論 0 9
  • 如何使用動(dòng)態(tài)代理叉存? 參照上面的例子码俩,我們可以知道要實(shí)現(xiàn)動(dòng)態(tài)代理需要做兩方面的工作度帮。 首先需要新建一個(gè)類(lèi)歼捏,并且這個(gè)類(lèi)...
    java部落閱讀 2,113評(píng)論 1 16
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法笨篷,內(nèi)部類(lèi)的語(yǔ)法瞳秽,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法率翅,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,639評(píng)論 18 399
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理练俐,服務(wù)發(fā)現(xiàn),斷路器冕臭,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 1.女孩 “明年四月我再來(lái)找你腺晾,你這段時(shí)間就乖乖呆著工作燕锥,好不好?”發(fā)送~女孩將手機(jī)握在胸口悯蝉,手骨慘白归形。叮~“好鼻由,...
    七渺閱讀 308評(píng)論 0 0