Java基礎-瞭解一下cglib的動態代理的本質

吾乃上將軍邢道榮發表於2019-03-10

在看這篇文章前,推薦先看一下我的jdk的動態代理。重複的東西在這兒我就不重複說了。

首先來簡單回顧一下cglib動態代理用法

1.建立一個類,這次不需要繼承介面

public class MyService {
    public void print(){
        System.out.println("this is print!");
    }
}
複製程式碼

2.建立攔截器

public class CglibIntercepter implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("this is before!");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("this is after!");
        return result;
    }
}
複製程式碼

3.測試

public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyService.class);
        enhancer.setCallback(new CglibIntercepter());
        MyService service = (MyService) enhancer.create();
        service.print();
    }
}
複製程式碼

不出意外的話,控制檯會列印

this is before!
this is print!
this is after!
複製程式碼

這說明我們的方法得到了增強。

看一下cglib動態代理的新類

如果已經看過jdk的動態代理這篇文章的話,那你對cglib動態代理應該也是有一點想法的。他們實現的手段相似,都是通過位元組碼技術來生成新類。

所以新類生成的地方我就不帶著找了。根據方法引數和返回值很容易分析到。直接來看一下cglib生成的新類吧。

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"d:/cglib");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyService.class);
        enhancer.setCallback(new CglibIntercepter());
        MyService service = (MyService) enhancer.create();
        service.print();
複製程式碼

在測試程式碼最上面增加一行,設定cglib將新類的class檔案列印的地方。設定好了執行一遍,不出意外可以在d盤的cglib資料夾中找到D:\cglib*\*\cglib_proxy這個資料夾,裡面有三個類。一個繼承自MyService,兩個繼承自fastclass, 首先來看一下繼承自MyService的這個類。

不用猜也知道,這個就是cglib增強後的類了。所以找關鍵程式碼。

    public final void print() {
        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$print$0$Method, CGLIB$emptyArgs, CGLIB$print$0$Proxy);
        } else {
            super.print();
        }
    }
複製程式碼

當我們呼叫了print方法後,其實是呼叫的var10000.intercept()方法,var10000你可能有點陌生,MethodInterceptor但是這個應該認識吧。這個就是我們用來建立攔截器的介面。而這邊的var10000,其實就是我們建立的那個攔截器。

所以攔截器如何增強方法到這邊應該清楚了,下面就跟蹤一下MethodProxy的invokeSuper方法。

首先通過靜態程式碼塊得知MethodProxy的來源

CGLIB$print$0$Proxy = MethodProxy.create(var1, var0, "()V", "print", "CGLIB$print$0");
複製程式碼

var1代表著原來的類,var0代表新類,跟蹤進入

 public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }
複製程式碼

只是做了些標記,搞清楚標記的順序後,開始分析invokeSuper方法。

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
複製程式碼

先進入init方法分析

                    CreateInfo ci = createInfo;

                    FastClassInfo fci = new FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(sig1);
                    fci.i2 = fci.f2.getIndex(sig2);
                    fastClassInfo = fci;
                    createInfo = null;
複製程式碼

init方法關鍵點,f1和f2分別是啥?i1 和i2又分別是啥?進入helper方法

        FastClass.Generator g = new FastClass.Generator();
        g.setType(type);
        g.setClassLoader(ci.c2.getClassLoader());
        g.setNamingPolicy(ci.namingPolicy);
        g.setStrategy(ci.strategy);
        g.setAttemptLoad(ci.attemptLoad);
複製程式碼

FastClass.Generator和我們用來增強類的Enhancer類一樣,都繼承自AbstractClassGenerator類,而這邊呼叫了create方法,顯然也是生成了一個新類,那這個新類在哪裡呢?

還記得一開始在資料夾中的另外兩個類麼,其實他們就是這邊生成的fastclass類。開啟那兩個類,進入getIndex方法

public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -1166389848:
            if (var10000.equals("print()V")) {
                return 0;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return 1;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return 2;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return 3;
            }
        }

        return -1;
    }
複製程式碼

邏輯很簡單,就算根據方法的hash嗎,返回int標誌。所以到這兒f1,f2,fci.i1,fci.i2就都應該知道是啥含義了。

在進入invoke方法

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

        try {
            switch(var10001) {
            case 0:
                var10000.print();
                return null;
            case 1:
                return new Boolean(var10000.equals(var3[0]));
            case 2:
                return var10000.toString();
            case 3:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
複製程式碼

其實我覺得到這兒已經沒啥好說的了,先在init方法中,獲得每個方法的標誌,在拿著標誌去執行invoke方法。不同的標誌執行不同的方法,就這樣。

需要注意的是,jdk動態代理,執行方法是通過反射執行的,而cglib動態代理,是通過標誌區分,直接執行原類的方法,所以效率更高。

相關文章