動態代理系列Cglib的FastClass機制(四)


書接上文,https://www.cnblogs.com/lyhero11/p/15553458.html

Cglib代理類分析

上回書遺留了一個疑問:cglib是如何動態的對委托類的方法進行調用的,我們說由於Java反射的一些性能問題,cglib使用了一種叫做FastClass的技巧來優化這個調用。
接下來分析下Cglib生成的代理類,來研究一下所謂的FastClass機制。

先在設置一下代理類的輸出路徑:

//設置cglib生成的代理類輸出到temp文件夾
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\temp");

然后就可以去D:\temp用反編譯工具看生成的代理類的源碼了,推薦使用Luyten反編譯,jd-gui有些代碼反編譯不出來。

如圖可以看到對委托類LancerEvolutionVI動態了3個類:

先來看一下LancerEvolutionVI$$EnhancerByCGLIB$$9332002c,這個就是我們的對象對應的代理類:

public class LancerEvolutionVI$$EnhancerByCGLIB$$9332002c extends LancerEvolutionVI implements Factory{
    final void CGLIB$speed$0() {
        super.speed();
    }
    
    public final void speed() {
        MethodInterceptor loc_1;
        MethodInterceptor loc_0;
        //方法攔截器,對應我們的CarMethodInterceptor
        if ((loc_0 = (loc_1 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            loc_1 = (loc_0 = this.CGLIB$CALLBACK_0);
        }
        if (loc_0 != null) {
            //執行攔截方法,實現代理,對應我們實現MethodInterceptor接口的intercept方法
            loc_1.intercept((Object)this, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$speed$0$Method, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$emptyArgs, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$speed$0$Proxy);
            return;
        }
        super.speed();
    }
}

然后,Object ret = methodProxy.invokeSuper(proxyObj, args)里我們用的invokeSuper這個方法,其實理論上這里我們有了proxyObj也就是LancerEvolutionVI$$EnhancerByCGLIB代理對象,那么其實可以直接調用CGLIB$speed$0()來調用父類也就是委托類的方法了,為什么要用invokeSuper呢?接着看MethodProxy.invokeSuper方法的源代碼:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args); //fci.i2是fastClass里邊存的Method的索引
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}

傳說中的FastClass出現了,簡單理解一下FastClass:為一個對象A創建它的FastClass對象,這個FastClass對象相當於A的方法索引,根據A的方法名生成並關聯一個index、每個index對應A的一個方法。后續只要根據這個index以及A的實例,就可以調用fastClass的invoke(instanceOfA, index, args)方法來快速的調用A的方法了。實現了Java反射的“運行時動態調用指定類的方法”的功能,但是使用了不同的機制。我們知道,java反射調用方法是比較“重”的操作,要經過一系列的權限驗證、通過native方法請求jvm去方法區查找方法定義、以及最后的invoke仍然可能要通過JNI調用native方法。而相比之下,FastClass方式則跟一般的一個普通的對象方法調用沒啥區別、只是多了一步根據index判斷調用委托類的哪個方法這一步驟、性能損耗基本沒有。
仿寫一個例子,加深一下FastClass技巧的印象:

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 為每個委托類動態生成FastClass類
 * */
@Slf4j
public class DummyServiceFactClass {

    //sign方法簽名
    public int getIndex(String sign){
        switch (sign){
            case "service1":
                return 1;
            case "service2":
                return 2;
        }
        return 0;
    }

    //index方法索引, obj調用對象, args方法參數
    public Object invoke(int index, Object obj, Object[] args){
        DummyService dummyService = (DummyService) obj;
        switch (index){
            case 1:
                return dummyService.service1((String)args[0]);
            case 2:
                dummyService.service2((String)args[0]);
                return null;
        }
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        DummyService dummyService = new DummyService();
        //使用FastClass
        DummyServiceFactClass dummyServiceFactClass = new DummyServiceFactClass();
        int index1 = dummyServiceFactClass.getIndex("service1");
        int index2 = dummyServiceFactClass.getIndex("service2");

        Object ret1 = dummyServiceFactClass.invoke(1, dummyService, new Object[]{"123"});
        Object ret2 = dummyServiceFactClass.invoke(2, dummyService, new Object[]{"456"});

        log.info("ret1 = {}, ret2 = {}", ret1, ret2);

        //使用反射
        Class clazz = dummyService.getClass();
        Method method1 = clazz.getDeclaredMethod("service1", String.class);
        Method method2 = clazz.getDeclaredMethod("service2", String.class);

        ret1 = method1.invoke(dummyService,"123f");
        ret2 = method2.invoke(dummyService, "345f");

        log.info("ret1 = {}, ret2 = {}", ret1, ret2);
    }
}

輸出

10:40:32.174 [main] INFO com.wangan.springbootone.aop.DummyService - service1 , param = 123
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service2, param = 456
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyServiceFactClass - ret1 = this is service1, param = 123, ret2 = null
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service1 , param = 123f
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service2, param = 345f
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyServiceFactClass - ret1 = this is service1, param = 123f, ret2 = null

總結

FastClass機制算是一種技巧層面的東西,在java內存里邊維護一個index值和對象的方法之間的邏輯映射,然后運行期可以根據index和實例來動態調用方法、且不用使用比較“重”的Java反射功能。

參考:

漫畫:AOP 面試造火箭事件始末 (qq.com)

cglib源碼分析(四):cglib 動態代理原理分析 - cruze_lee - 博客園 (cnblogs.com)

Spring AOP 是怎么運行的?徹底搞定這道面試必考題 - 雲+社區 - 騰訊雲 (tencent.com)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM