CGLIB代理使用示例與底層原理解析

CGLIB經常使用使用示例:html

目標類java

public class Todo {
    public void doString(String desc) {
        System.out.println("doString: " + desc);
    }
}

代理生成類 編程

public class ProxyInstance implements MethodInterceptor {




    public <T> T getInstance(Class<T> klass) {
        Enhancer enhancer = new Enhancer();
        /*
         * 設置父類型,用於生成代理的子類
         */
        enhancer.setSuperclass(klass);
        /*
         * 設置實現Callback接口的實例對象
         * 這裏爲MethodInterceptor對象
         */
        enhancer.setCallback(this);
        /*
         * create()用於建立代理類對象
         * createClass()用於生成java字節碼
         */
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("proxy before");
        Object obj= methodProxy.invokeSuper(o, objects);
        System.out.println("proxy after");
        return obj;
    }
}

測試類app

public class Test {
    public static void main(String[] args) {
        String path = Test.class.getClassLoader().getResource("").getPath();
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path + "cglib");
        Todo todo=new ProxyInstance().getInstance(Todo.class);
        todo.doString("------ nothing-------- ");
    }
}

控制檯框架

CGLIB debugging enabled, writing to '/E:/ideaspace/demo/demo/target/classes/cglib'
proxy before
doString: ------ nothing--------
proxy after

ASM(assembly):彙編的縮寫,是java的一個字節碼級別的編程框架,它能夠動態生成Class的字節碼文件,CGLIB以及Groovy等都是基於ASM實現動態語言特性的,在CGLIB的代理中主要用於生成類文件,實現方式爲動態生成繼承了目標類的代理類。maven

maven依賴ide

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>8.0.1</version>
</dependency>

針對示例的目標類,生成類過多這裏只展現主要代碼:注意全部生成的類都會加載到JVM中測試

// 代理類相關的生成類this

Todo$$EnhancerByCGLIB$$939b412c$$FastClassByCGLIB$$b6c5bc7f.class
Todo$$EnhancerByCGLIB$$939b412c.class  // 代理類
Todo$$FastClassByCGLIB$$9f944a58.class

//其餘類url

core/MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class
proxy/Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class

代理類描述

// 經過繼承方式來實現代理,目標類成爲了父類supper。經過繼承實現也附帶了繼承的缺點,注意final類型和final方法的坑

public class Todo$$EnhancerByCGLIB$$939b412c extends Todo implements Factory {...}

針對方法的靜態處理

static void CGLIB$STATICHOOK1() {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    // 當前代理類
    Class var0 = Class.forName("cn.tinyice.demo.proxy.cglib.Todo$$EnhancerByCGLIB$$939b412c");
    Class var1;
    // 經過反射獲取java.lang.Object聲明的全部方法(不包括繼承的方法,這裏也不會有)中的equals、toString、hashCode、clone方法,準備重寫
    // 注意 這裏 var1賦值  
    Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 =         Class.forName("java.lang.Object")).getDeclaredMethods());
    // 對每一個方法獲取其Method對象$Method以及代理對象$Proxy
    CGLIB$equals$1$Method = var10000[0];
    CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
    CGLIB$toString$2$Method = var10000[1];
    CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
    CGLIB$hashCode$3$Method = var10000[2];
    CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
    CGLIB$clone$4$Method = var10000[3];
    CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
    // 獲取目標類用戶方法doString的Method對象$Method以及代理對象$Proxy,注意這裏var1賦值
    CGLIB$doString$0$Method = ReflectUtils.findMethods(new String[]{"doString", "(Ljava/lang/String;)V"}, (var1 = Class.forName("cn.tinyice.demo.proxy.cglib.Todo")).getDeclaredMethods())[0];
    CGLIB$doString$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "doString", "CGLIB$doString$0");
}

代理方法描述

public final void doString(String var1) {
    // 方法攔截器:從callBack中獲取,這裏爲ProxyInstance
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }
    // 具備方法攔截器則執行其攔截方法
    if (var10000 != null) {
        // 參數: 代理類對象、原方法對象、參數對象、代理方法對象
        var10000.intercept(this, CGLIB$doString$0$Method, new Object[]{var1}, CGLIB$doString$0$Proxy);
    } else {
    // 不攔截方法則直接調用原方法
        super.doString(var1);
    }
}

到這裏已經執行到了ProxyInstance#intercept,而後調用methodProxy.invokeSuper(o, objects);

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        // 初始化MethodProxy實例,注意在調用時才初始化的
        this.init();
        // 獲取FastClass
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        // 調用FastClassInfo的FastClass2的invoke,參數爲方法簽名下標,調用對象、方法參數
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    }
}


private void init() {
    if (this.fastClassInfo == null) {
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                // fastClass1 生成 ,c1爲MethodProxy.create的第一個參數,目標類
                              // 生成類名稱前綴=參數2的類名稱+$$FastClassByCGLIB$$Integer.toHexString(hashCode)
                fci.f1 = helper(ci, ci.c1);
                // fastClass2生成,c2爲MethodProxy.create的第二個參數,代理類Class
                fci.f2 = helper(ci, ci.c2);
                // 調用getIndex獲取下標,參數爲方法簽名,兩者一一映射,這裏使用switch語句來提升效率
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                // 實例構建完畢
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}
法攔截器中的調用 : fci.f2.invoke(fci.i2, obj, args)
 
目標類$$EnhancerByCGLIB$$FastClassByCGLIB$$Integer.toHexString(hashCode).invoke(代理類的方法簽名、傳入代理對象,傳入的方法參數)
調用時一樣使用switch語句來調用對應的方法
 
 
總結:
 
經過Enhancer能夠生成代理類,傳入CallBack使代碼執行時進入CallBack代碼中,從而執行用戶自定義代碼;
用戶自定義代碼能夠決定使用目標類調用仍是代理類調用
  • 目標類使用$$FastClass1進行調用
  • 代理類使用$$FastClass2進行調用
  • $$FastClass都在調用時觸發生成
$$FastClass爲動態生成類,經過index與方法簽名一一映射產生switch case 語句來提升調用效率
代理類是經過繼承方式實現,不能對final類和方法使用CGLIB代理。
 
所以 CGLB代理調用效率高,可是生成效率慢,至少3個文件,1個代理類,2個FastClass類。全部生成的類都會加載到JVM堆中。
 
相關文章
相關標籤/搜索