動態代理-cglib分析

BaldHead發表於2023-02-08

生成代理類檔案的方式

jvm新增此啟動引數,後面就是代理類class生成的地址
-Dcglib.debugLocation=~/baldhead/java/dynamic-proxy-cglib/src/main/java/com/baldhead/dynamic/proxy/cglib/class

新增這個引數之後,CGLIB就會把生成的代理Class檔案存在指定的路徑

生成動態代理物件流程

  1. CGLIB首先生成代理類
  2. 程式碼中的 static 靜態程式碼塊 會呼叫 CGLIB$STATICHOOK1(); 方法,方法作用
    3. 新建一個名字為 CGLIB$THREAD_CALLBACKSThreadLocal,用來存放所設定的 callback
    4. 使用反射找到代理類中的所有方法,包括(toStringhashCodeequalsclone),名字為模板 CGLIB$METHODNAME$數字編號$Method
    並且給對應的方法建立代理方法 名字模板CGLIB$METHODNAME$數字編號$Proxy
  3. 呼叫構造方法建立代理物件
  4. 然後CGLIB會呼叫代理物件的 CGLIB$SET_THREAD_CALLBACKS 方法,將傳入的 callBack存到 ThreadLocal(CGLIB$THREAD_CALLBACKS) 中去
  5. 後續在物件執行需要代理的方法的時候,就會從CGLIB$THREAD_CALLBACKS中拿到所設定的 CallBack並呼叫它的intercept()方法

代理物件的建立

static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.baldhead.dynamic.proxy.cglib.Impl.UserService$$EnhancerByCGLIB$$e34eec9a");
        Class var1;
        CGLIB$test$0$Method = ReflectUtils.findMethods(new String[]{"test", "()V"}, (var1 = Class.forName("com.baldhead.dynamic.proxy.cglib.Impl.UserService")).getDeclaredMethods())[0];
        CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
    }

以上程式碼經過簡化的,主要看下面給出的一行
CGLIB$test$0$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$0");
對應的方法如下

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        /**
         * 這幾個引數都可以找到入參物件
         * c1: 被代理類物件的class,也就是原始物件的class
         * c2: 代理類物件的 class
         * desc: 方法的返回值型別
         * name1: 原始代理方法的名稱
         * name2: 代理方法在代理類中的名稱(CGLIB$test$0)
         */
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }

在MethodProxy中有三個很重要的屬性

  • sig1: 表示test方法
  • sig2: 表示 CGLIB$test$0 方法
  • createInfo: 表示原始類和代理類

invoke和invokeSuper方法

 public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            FastClassInfo fci = this.fastClassInfo;
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        } catch (IllegalArgumentException var5) {
            if (this.fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: " + this.sig1);
            } else {
                throw var5;
            }
        }
    }

    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

兩個方法大差不差的,但是都用到了一個物件 fastClassInfo 這個物件是在 init()方法中構造的

private void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    CreateInfo ci = this.createInfo;
                    FastClassInfo fci = new 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;
                }
            }
        }

    }

fastClassInfo物件中主要是有四個屬性

  • f1: 原始類對應的一個FastClass 代理物件
  • f2: 代理類對應的一個FastClass 代理物件
  • i1: test方法在原始類對應的一個FastClass代理物件中的下標
  • i2: CGLIB$test$0方法在代理類對應的一個 FastClass 代理物件中的下標
    這裡產生了兩個代理物件,你說好巧不巧,正好產生的代理,class有3個,其中有兩個繼承 FastClass, 另外一個繼承原始類並且實現 Factory介面

image.png
其實這兩個類類似,都是針對某一個類的FastClass代理類,所以我們好好看一下UserService所對應的FastClass該類主要有:

  1. 一個構造方法
  2. public int getlndex(Signature var1)
  3. public int getlndex(String var1, Classll var2)
  4. public int getlndex(ClassI var1)
  5. public Object invoke(int var1, Object ar2, Objectll var3)
  6. public Object newlnstance(int var1, Objectll var2)
  7. public int getMaxlndex0

顧名思義,FastClass的作用是提高方法的執行速度,按照正常的實現,當我們呼叫MethodProxy物件的invokel或invokeSuper0方法時,首先應該要做到的就是找到對應的Method物件,比如:

  1. 執行invoke0,要找到test方法對應的Method物件

  2. 執行invokeSuper0,要找到CGLIBstest$00方法對應的Method物件然後利用反射來執行Method。

那麼FastClass的機制就是預先把UserService類或UserService代理類中的所有方法做一個索引,比如:

  public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch (var10000.hashCode()) {
            case -2055565910:
                if (var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
                    return 19;
                }
                break;
            case -1659690448:
                if (var10000.equals("CGLIB$test$4()V")) {
                    return 20;
                }
                break;
            case -1457535688:
                if (var10000.equals("CGLIB$STATICHOOK1()V")) {
                    return 12;
                }
                break;
            case -1422510685:
                if (var10000.equals("test()V")) {
                    return 7;
                }
                break;
            case -1411872516:
                if (var10000.equals("CGLIB$hashCode$2()I")) {
                    return 15;
                }
                break;
        // 省略部分程式碼
        }

        return -1;
    }

一旦呼叫 getIndex(Signature var1) 方法,就對得到對應方法返回的索引,例如這裡就是test方法返回的對應的索引就是7
再回到init 方法

private void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    CreateInfo ci = this.createInfo;
                    FastClassInfo fci = new 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;
                }
            }
        }

    }

init方法中的兩個 helper方法就是去生成原始類和代理類的 FactClass代理類,後面個兩個getIndex方法
1. 第一個fci.f1.getIndex(this.sig1)就是去獲取原始類對應的FastClass代理類中 test方法的下標i1
2. 第二個 fci.f2.getIndex(this.sig2)就是去獲取代理類對應的FastClass代理類中$test$0方法的下標i2

然後會把兩個下標都記錄在 fastClassInfo 物件中

後面就是我們看到的invokeinvokeSuper中呼叫的兩個方法

  • invoke

    • fci.f1.invoke(fci.i1, obj, args);

      執行原始類對應的FastClass 代理類的invoke方法

  • invokeSuper

    • fci.f2.invoke(fci.i2, obj, args);

      執行代理類對應的FastClass代理類的invoke方法

例如: 原始類對應的FastClass 程式碼

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

        try {
            switch (var10001) {
                case 0:
                    var10000.test();
                    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");
    }

這個程式碼比較簡單,第一個引數就是執行方法的index,第二個引數就是原始類,第三個就是原始類的引數

如果傳入的index 是0 ,那麼就會去執行test方法

代理類對應的FastClass代理類的invoke方法也是類似

 public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        UserService..EnhancerByCGLIB..e34eec9a var10000 = (UserService..EnhancerByCGLIB..e34eec9a)var2;
        int var10001 = var1;

        try {
            switch (var10001) {
                case 0:
                    return new Boolean(var10000.equals(var3[0]));
                case 1:
                    return var10000.toString();
                case 2:
                    return new Integer(var10000.hashCode());
                case 3:
                    return var10000.clone();
                case 4:
                    return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
                case 5:
                    return var10000.newInstance((Callback[])var3[0]);
                case 6:
                    return var10000.newInstance((Callback)var3[0]);
                case 7:
                    var10000.test();
                    return null;
                case 8:
                    e34eec9a.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
                    return null;
                case 9:
                    e34eec9a.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
                    return null;
                case 10:
                    var10000.setCallbacks((Callback[])var3[0]);
                    return null;
                case 11:
                    return var10000.getCallback(((Number)var3[0]).intValue());
                case 12:
                    return var10000.getCallbacks();
                case 13:
                    var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
                    return null;
                case 14:
                    return e34eec9a.CGLIB$findMethodProxy((Signature)var3[0]);
                case 15:
                    e34eec9a.CGLIB$STATICHOOK1();
                    return null;
                case 16:
                    var10000.CGLIB$test$0();
                    return null;
                case 17:
                    return new Integer(var10000.CGLIB$hashCode$3());
                case 18:
                    return new Boolean(var10000.CGLIB$equals$1(var3[0]));
                case 19:
                    return var10000.CGLIB$toString$2();
                case 20:
                    return var10000.CGLIB$clone$4();
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

例如傳入的index 是16 那麼執行的就是 var10000.CGLIB$test$0();

如果傳入的index是 7 那麼執行的就是var10000.test();

var10000 是傳入物件強轉為UserService..EnhancerByCGLIB..e34eec9a類的物件,UserService..EnhancerByCGLIB..e34eec9a類其實就是UserService的代理類

invokeSuper結論

所以當我們執行invokeSuper方法的時候,不能傳入原始類(UserService)只能傳入代理類物件,不然就無法轉換成為代理類型別

所以FastClass 快的地方就是預先把所有的方法資訊都生成了對應的index,在真正的去執行的時候不用再去找Method物件,直接傳入對應方法的index就可以直接執行對應的方法了

相關文章