聊聊cglib動態代理遇到的坑

EumJi發表於2018-02-23

簡介

cglib是另外一種動態代理的方法,他和jdk動態代理的實現是有區別的,我們在之前見過jdk動態代理類是必須實現了介面的,而cglib不需要實現介面,但是必須保證類不含有final關鍵字,否則是無法代理的。 本文是從個人不小心遇到的cglib的死迴圈問題從而展開的分析。

cglib案例

下面我們來展示一個cglib的死迴圈案例。首先是要被代理的類,還是和常規的一樣,宣告自己的方法就行,但是要確保類和方法沒有被final關鍵字修飾。用final關鍵字修飾類會直接報異常,但是修飾方法不會拋異常,但是此方法不會被代理,但是不影響其他方法被代理。

public class InfoDemo {
    public void welcome (String person){
        System.out.println("welcome :" + person);
    }
}
複製程式碼

下面是具體的代理類實現

public class CglibInfoProxy implements MethodInterceptor{
    private Object target;
    public Object newInstance(Object source){
        target = source;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before method!!!");
        Object value = methodProxy.invoke(o, objects);
        //Object value = methodProxy.invoke(this.target, objects);
        //Object value = methodProxy.invokeSuper(o, objects);
        return value;
    }
   public static void main(String[] args) {
        //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\\\classes");
      InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
      instance.welcome("zhangsan");

    }
}
複製程式碼

和我們的jdk動態代理看起來十分相似,只是兩個類實現的介面不同,並且生成物件的方法也不同。這裡非常坑的是invoke方法和invokeSuper的區別,如果是用invoke方法一定要使用被代理的物件也就是上文中的target,而如果呼叫invokeSuper方法,則一定要使用被代理後的o物件。

上述這個例子就會引發死迴圈,導致StackOverflowFlow,嘿嘿,學沒有,棧溢位的場景

具體為什麼會這樣,可以先思考一下,後面我們在原始碼實現中再去講解。現在我們先看一下執行結果

...
before method!!!
before method!!!
before method!!!
Exception in thread "main" java.lang.StackOverflowError
	at java.nio.CharBuffer.<init>(CharBuffer.java:281)
	at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:70)
	at java.nio.CharBuffer.wrap(CharBuffer.java:373)
	at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
	at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
	at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
	at java.io.PrintStream.write(PrintStream.java:526)
	at java.io.PrintStream.print(PrintStream.java:669)
	at java.io.PrintStream.println(PrintStream.java:806)
	at com.eumji.proxy.cglib.CglibInfoProxy.intercept(CglibInfoProxy.java:30)
	at com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$870a84d7.welcome(<generated>)
	at com.eumji.proxy.cglib.InfoDemo$$FastClassByCGLIB$$2e560a7d.invoke(<generated>)
	at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
...
複製程式碼

此處只展示部分的效果,具體可以自己試一下。

假如我們換成其餘他兩條語句將會是正確的輸出,具體結果如下

before method!!!
welcome :zhangsan
複製程式碼

原理解析

要想弄清楚的這到底是怎麼回事,首先我們要看一下cglib代理後的類是怎樣的,要想生成代理類的檔案,我們只需要在我們的main方法中取消掉這句方法的註釋

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");

就會在D盤的classes檔案下生成對應的代理class檔案,需要注意的是生成的代理class是有三個的,我們首先介紹一下我們最關心的InfoDemo代理類,其他的稍後合適的時機在描述其他兩個。

InfoDemo反編譯程式碼

其實就是將class檔案直接拖到IDEA中

public class InfoDemo$$EnhancerByCGLIB$$870a84d7 extends InfoDemo 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$welcome$0$Method;
    private static final MethodProxy CGLIB$welcome$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 var0 = Class.forName("com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$870a84d7");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.eumji.proxy.cglib.InfoDemo")).getDeclaredMethods())[0];
        CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
    }

    final void CGLIB$welcome$0(String var1) {
        super.welcome(var1);
    }

    public final void welcome(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
        } else {
            super.welcome(var1);
        }
    }
  ...

}

複製程式碼

看程式碼就可以看出來cglib還是很複雜的,現在我們暫且可以看一下我們關心的welcome方法,從上面的程式碼中可以看到cglib是會為被代理類的方法同時生成兩個代理方法的,一個是同名的welcome方法CGLIB$welcome$0方法

1.CGLIB$welcome$0方法直接呼叫被代理的方法,也就是啥都沒幹。

2.welcome方法首先判斷有沒有設定callback,很明顯我們在程式碼中有設定即為CglibInfoProxy,所以就會呼叫CglibInfoProxy.intercept方法。

本來想分析一波生成代理類的過程,看了一下有點複雜,暫時就不分析了。。。。

invokeSuper方法

前面也提及了invoke和invokeSuper方法稍不注意就會出問題的問題,在這裡我們從程式碼的層面去追蹤一下,產生問題的原因。

我們看一下代理類方法invokeSuper的執行流程

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init(); //初始化fastInfo
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}
複製程式碼

invokeSuper在這裡主要的作用就是初始化fastClassInfo。

init方法

private void init() {
    if (this.fastClassInfo == null) {
        Object var1 = this.initLock;
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}
複製程式碼

上面的方法主要是載入methodProxy.FastClassInfo。ci是之前就初始化好的,其中c1指的就是被代理的類InfoDemo,c2則是com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$efe38465這個代理類。

然後生成對應的f1和f2以及方法的下標i1和i2,i1和i2對應的就是在最前面所說的welcome方法CGLIB$welcome$0方法,後面程式碼可以看出。

而f1則是對應InfoDemo$$FastClassByCGLIB$$2e560a7d代理類,f2則對應InfoDemo$$EnhancerByCGLIB$$efe38465$$FastClassByCGLIB$$38345933代理類。這些都可以在生成的代理class中去檢視。

當然這裡並沒有說到底什麼生成的,有興趣的可以自己看一下位元組碼是怎麼生成的。個人沒太看懂。

invoke和invokeSuper區別

為什麼要生成兩個代理類f1和f2,我相信你看過之前的方法應該注意到了,在上面我們提及到了invoke方法和invokeSuper方法,我們來對比一下invoke方法和invokeSuper方法的區別

public Object invoke(Object obj, Object[] args) throws Throwable {
  this.init();
  MethodProxy.FastClassInfo fci = this.fastClassInfo;
  return fci.f1.invoke(fci.i1, obj, args);
}
複製程式碼

我們可以看到invoke使用的是f1.invoke方法,而invokeSuper則是使用f2.invoke方法。

首先看一下f1對應的invoke方法邏輯

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        InfoDemo var10000 = (InfoDemo)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.welcome((String)var3[0]);
                return null;
                ....
}
複製程式碼

直接呼叫InfoDemo物件的welcome方法。

所以這也就能解釋為什麼我們之前會發生迴圈呼叫invoke的方法了,因為我們傳入的var2是InfoDemo的代理物件,看最前面的代理類程式碼就可以看出,又會回到invoke方法,造成死迴圈。

再看一下f2中對應invoke的實現

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        efe38465 var10000 = (efe38465)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
             ....
                var10000.CGLIB$finalize$1();
                return null;
            case 16:
                var10000.CGLIB$welcome$0((String)var3[0]);
                return null;
           ....
    }
複製程式碼

因為我們此時我們傳入的var2是InfoDemo代理物件,所以最終會呼叫代理類中的CGLIB$welcome$0方法。

小結

只是一次失敗的原始碼分析嘗試,不過弄清楚了造成呼叫死迴圈的原因,只能說cglib比jdk的動態代理複雜很多,主要體現在生成程式碼的邏輯和生成的程式碼上,還有待深入的學習。

而且是有兩種invoke方法即invoke和invokeSuper方法,所以使用的時候必須要謹慎。

結語

本文出自個人筆記,如有表述不當或者紕漏的地方歡迎指正。

與君共勉!!!

簡介

cglib是另外一種動態代理的方法,他和jdk動態代理的實現是有區別的,我們在之前見過jdk動態代理類是必須實現了介面的,而cglib不需要實現介面,但是必須保證類不含有final關鍵字,否則是無法代理的。

cglib案例

下面我們來展示一個cglib的死迴圈案例。首先是要被代理的類,還是和常規的一樣,宣告自己的方法就行,但是要確保類和方法沒有被final關鍵字修飾。用final關鍵字修飾類會直接報異常,但是修飾方法不會拋異常,但是此方法不會被代理,但是不影響其他方法被代理。

public class InfoDemo {
    public void welcome (String person){
        System.out.println("welcome :" + person);
    }
}
複製程式碼

下面是具體的代理類實現

public class CglibInfoProxy implements MethodInterceptor{
    private Object target;
    public Object newInstance(Object source){
        target = source;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before method!!!");
        Object value = methodProxy.invoke(o, objects);
        //Object value = methodProxy.invoke(this.target, objects);
        //Object value = methodProxy.invokeSuper(o, objects);
        return value;
    }
   public static void main(String[] args) {
        //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\\\classes");
      InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
      instance.welcome("zhangsan");

    }
}
複製程式碼

和我們的jdk動態代理看起來十分相似,只是兩個類實現的介面不同,並且生成物件的方法也不同。這裡非常坑的是invoke方法和invokeSuper的區別,如果是用invoke方法一定要使用被代理的物件也就是上文中的target,而如果呼叫invokeSuper方法,則一定要使用被代理後的o物件。

上述這個例子就會引發死迴圈,導致StackOverflowFlow,嘿嘿,學沒有,棧溢位的場景

具體為什麼會這樣,可以先思考一下,後面我們在原始碼實現中再去講解。現在我們先看一下執行結果

...
before method!!!
before method!!!
before method!!!
Exception in thread "main" java.lang.StackOverflowError
	at java.nio.CharBuffer.<init>(CharBuffer.java:281)
	at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:70)
	at java.nio.CharBuffer.wrap(CharBuffer.java:373)
	at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
	at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
	at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
	at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
	at java.io.PrintStream.write(PrintStream.java:526)
	at java.io.PrintStream.print(PrintStream.java:669)
	at java.io.PrintStream.println(PrintStream.java:806)
	at com.eumji.proxy.cglib.CglibInfoProxy.intercept(CglibInfoProxy.java:30)
	at com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$870a84d7.welcome(<generated>)
	at com.eumji.proxy.cglib.InfoDemo$$FastClassByCGLIB$$2e560a7d.invoke(<generated>)
	at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
...
複製程式碼

此處只展示部分的效果,具體可以自己試一下。

假如我們換成其餘他兩條語句將會是正確的輸出,具體結果如下

before method!!!
welcome :zhangsan
複製程式碼

原理解析

要想弄清楚的這到底是怎麼回事,首先我們要看一下cglib代理後的類是怎樣的,要想生成代理類的檔案,我們只需要在我們的main方法中取消掉這句方法的註釋

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classes");

就會在D盤的classes檔案下生成對應的代理class檔案,需要注意的是生成的代理class是有三個的,我們首先介紹一下我們最關心的InfoDemo代理類,其他的稍後合適的時機在描述其他兩個。

InfoDemo反編譯程式碼

其實就是將class檔案直接拖到IDEA中

public class InfoDemo$$EnhancerByCGLIB$$870a84d7 extends InfoDemo 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$welcome$0$Method;
    private static final MethodProxy CGLIB$welcome$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 var0 = Class.forName("com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$870a84d7");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.eumji.proxy.cglib.InfoDemo")).getDeclaredMethods())[0];
        CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
    }

    final void CGLIB$welcome$0(String var1) {
        super.welcome(var1);
    }

    public final void welcome(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
        } else {
            super.welcome(var1);
        }
    }
  ...

}

複製程式碼

看程式碼就可以看出來cglib還是很複雜的,現在我們暫且可以看一下我們關心的welcome方法,從上面的程式碼中可以看到cglib是會為被代理類的方法同時生成兩個代理方法的,一個是同名的welcome方法CGLIB$welcome$0方法

1.CGLIB$welcome$0方法直接呼叫被代理的方法,也就是啥都沒幹。

2.welcome方法首先判斷有沒有設定callback,很明顯我們在程式碼中有設定即為CglibInfoProxy,所以就會呼叫CglibInfoProxy.intercept方法。

本來想分析一波生成代理類的過程,看了一下有點複雜,暫時就不分析了。。。。

invokeSuper方法

前面也提及了invoke和invokeSuper方法稍不注意就會出問題的問題,在這裡我們從程式碼的層面去追蹤一下,產生問題的原因。

我們看一下代理類方法invokeSuper的執行流程

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init(); //初始化fastInfo
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}
複製程式碼

invokeSuper在這裡主要的作用就是初始化fastClassInfo,然後通過fastClassInfo去呼叫目標方法。

init方法

此方法也有比較關鍵的地方,因為生成了代理類f1和f2。

private void init() {
    if (this.fastClassInfo == null) {
        Object var1 = this.initLock;
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1); //fastclass代理類生成
                fci.f2 = helper(ci, ci.c2);//fastclass代理類生成
                fci.i1 = fci.f1.getIndex(this.sig1); //獲取下標
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}
複製程式碼

上面的方法主要是載入methodProxy.FastClassInfo。ci是之前就初始化好的,其中c1指的就是被代理的類InfoDemo,c2則是com.eumji.proxy.cglib.InfoDemo$$EnhancerByCGLIB$$efe38465這個代理類。

然後生成對應的f1和f2以及方法的下標i1和i2,i1和i2對應的就是在最前面所說的welcome方法CGLIB$welcome$0方法,後面程式碼可以看出。

而f1則是對應InfoDemo$$FastClassByCGLIB$$2e560a7d代理類,f2則對應InfoDemo$$EnhancerByCGLIB$$efe38465$$FastClassByCGLIB$$38345933代理類。這些都可以在生成的代理class中去檢視。

當然這裡並沒有說到底什麼生成的,有興趣的可以自己看一下位元組碼是怎麼生成的。個人沒太看懂。

invoke和invokeSuper區別

為什麼要生成兩個代理類f1和f2,我相信你看過之前的方法應該注意到了,在上面我們提及到了invoke方法和invokeSuper方法,我們來對比一下invoke方法和invokeSuper方法的區別

public Object invoke(Object obj, Object[] args) throws Throwable {
  this.init();
  MethodProxy.FastClassInfo fci = this.fastClassInfo;
  return fci.f1.invoke(fci.i1, obj, args);
}
複製程式碼

我們可以看到invoke使用的是f1.invoke方法,而invokeSuper則是使用f2.invoke方法。

首先看一下f1對應的invoke方法邏輯

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        InfoDemo var10000 = (InfoDemo)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.welcome((String)var3[0]);
                return null;
                ....
}
複製程式碼

直接呼叫InfoDemo物件的welcome方法。

所以這也就能解釋為什麼我們之前會發生迴圈呼叫invoke的方法了,因為我們傳入的var2是InfoDemo的代理物件,看最前面的代理類程式碼就可以看出,又會回到invoke方法,造成死迴圈。

再看一下f2中對應invoke的實現

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        efe38465 var10000 = (efe38465)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
             ....
                var10000.CGLIB$finalize$1();
                return null;
            case 16:
                var10000.CGLIB$welcome$0((String)var3[0]);
                return null;
           ....
    }
複製程式碼

因為我們此時我們傳入的var2是InfoDemo代理物件,所以最終會呼叫代理類中的CGLIB$welcome$0方法。

小結

只是一次失敗的原始碼分析嘗試,不過弄清楚了造成呼叫死迴圈的原因,只能說cglib比jdk的動態代理複雜很多,主要體現在生成程式碼的邏輯和生成的程式碼上,還有待深入的學習。

而且是有兩種invoke方法即invoke和invokeSuper方法,所以使用的時候必須要謹慎。

結語

本文出自個人筆記,如有表述不當或者紕漏的地方歡迎指正。

與君共勉!!!

相關文章