設計模式【3.3】-- CGLIB動態代理原始碼解讀

秦懷雜貨店發表於2021-11-14

<img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210925233820.png" width = "500" height = "400" alt="圖片名稱" align=center />

cglib 動態代理

cglib介紹

CGLIB 是一個開源專案,一個強大高效能高質量的程式碼生成庫,可以在執行期擴充 Java 類,實現 Java 介面等等。底層是使用一個小而快的位元組碼處理框架 ASM,從而轉換位元組碼和生成新的類。

理論上我們也可以直接用 ASM 來直接生成程式碼,但是要求我們對 JVM 內部,class 檔案格式,以及位元組碼的指令集都很熟悉。

這玩意不在 JDK 的包裡面,需要自己下載匯入或者 Maven 座標匯入。

我選擇 Maven 匯入, 加到 pom.xml 檔案:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

Student.java:

public class Student {
    public void learn() {
        System.out.println("我是學生,我想學習");
    }
}

MyProxy.java(代理類)


import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class StudentProxy implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("代理前 -------");
        proxy.invokeSuper(obj, args);
        System.out.println("代理後 -------");
        return null;
    }

}

測試類(Test.java


import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class Test {
    public static void main(String[] args) {
        // 代理類class檔案存入本地磁碟方便我們反編譯檢視原始碼
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/aphysia/Desktop");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);
        enhancer.setCallback(new StudentProxy());
        Student student = (Student) enhancer.create();
        student.learn();

    }
}

執行之後的結果是:

CGLIB debugging enabled, writing to '/Users/xuwenhao/Desktop'
代理前 -------
我是學生,我想學習
代理後 -------

在我們選擇的資料夾裡面,生成了代理類的程式碼:

原始碼分析

我們先要代理的類,需要實現MethodInterceptor(方法攔截器)介面,這個介面只有一個方法 intercept,引數分別是:

  • obj:需要增強的物件
  • method:需要攔截的方法
  • args:要被攔截的方法引數
  • proxy:表示要觸發父類的方法物件
package net.sf.cglib.proxy;
import java.lang.reflect.Method;
public interface MethodInterceptor extends Callback {
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

再看回我們要建立代理類的方法 enhancer.create(),這個方法的意思:如果需要,生成一個新類,並使用指定的回撥(如果有的話)來建立一個新的物件例項。使用超類的無引數建構函式。

    public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

主要的方法邏輯我們得看 createHelper(),除了校驗,就是呼叫 KEY_FACTORY.newInstance() 方法生成 EnhancerKey物件,KEY_FACTORY 是靜態 EnhancerKey 介面,newInstance()是介面裡面的一個方法,重點在super.create(key)裡面,呼叫的是父類的方法:

    private Object createHelper() {
        // 校驗
        preValidate();
        Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
                ReflectUtils.getNames(interfaces),
                filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
                callbackTypes,
                useFactory,
                interceptDuringConstruction,
                serialVersionUID);
        this.currentKey = key;
        Object result = super.create(key);
        return result;
    }

AbstractClassGeneratorEnhancer 的父類,create(key) 方法的主要邏輯是獲取類載入器,快取獲取類載入資料,然後再反射構造物件,裡面有兩個創造例項物件的方法:

  • fistInstance(): 不應該在常規流中呼叫此方法。從技術上講,{@link #wrapCachedClass(Class)}使用{@link EnhancerFactoryData}作為快取值,後者支援比普通的舊反射查詢和呼叫更快的例項化。出於向後相容性的原因,這個方法保持不變:只是以防它曾經被使用過。(我的理解是目前的邏輯不會走到這個分支,因為它比較忙,但是為了相容,這個case還儲存著),內部邏輯其實用的是ReflectUtils.newInstance(type)
  • nextInstance(): 真正的建立代理物件的類
    protected Object create(Object key) {
        try {
            ClassLoader loader = getClassLoader();
            Map<ClassLoader, ClassLoaderData> cache = CACHE;
            ClassLoaderData data = cache.get(loader);
            if (data == null) {
                synchronized (AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                        data = new ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }
            this.key = key;
            Object obj = data.get(this, getUseCache());
            if (obj instanceof Class) {
                return firstInstance((Class) obj);
            }
            // 真正建立物件的方法
            return nextInstance(obj);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        }
    }

這個方法定義在AbstractClassGenerator,但是實際上是呼叫子類 Enhancer的實現,主要是通過獲取引數型別,引數,以及回撥物件,用這些引數反射生成代理物件。

    protected Object nextInstance(Object instance) {
        EnhancerFactoryData data = (EnhancerFactoryData) instance;

        if (classOnly) {
            return data.generatedClass;
        }

        Class[] argumentTypes = this.argumentTypes;
        Object[] arguments = this.arguments;
        if (argumentTypes == null) {
            argumentTypes = Constants.EMPTY_CLASS_ARRAY;
            arguments = null;
        }
        // 構造
        return data.newInstance(argumentTypes, arguments, callbacks);
    }

內部實現邏輯,呼叫的都是ReflectUtils.newInstance(), 引數種類不一樣:

        public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
            setThreadCallbacks(callbacks);
            try {

                if (primaryConstructorArgTypes == argumentTypes ||
                        Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
                    return ReflectUtils.newInstance(primaryConstructor, arguments);
                }
               return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
            } finally {
            
                setThreadCallbacks(null);
            }

        }

跟進去到底,就是獲取構造器方法,反射方式構造代理物件,最終呼叫到的是 JDK 提供的方法:

    public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
        return newInstance(getConstructor(type, parameterTypes), args);
    }
    public static Object newInstance(final Constructor cstruct, final Object[] args) {
            
        boolean flag = cstruct.isAccessible();
        try {
            if (!flag) {
                cstruct.setAccessible(true);
            }
            Object result = cstruct.newInstance(args);
            return result;
        } catch (InstantiationException e) {
            throw new CodeGenerationException(e);
        } catch (IllegalAccessException e) {
            throw new CodeGenerationException(e);
        } catch (InvocationTargetException e) {
            throw new CodeGenerationException(e.getTargetException());
        } finally {
            if (!flag) {
                cstruct.setAccessible(flag);
            }
        }
                
    }

開啟它自動生成的代理類檔案看看,就會發現其實也是生成那些方法,加上了一些增強方法:

生成的代理類繼承了原來的類:

public class Student$$EnhancerByCGLIB$$929cb5fe extends Student implements Factory {
    ...
}

看看生成的增強方法,其實是呼叫到 intercept()方法,這個方法由我們前面自己實現,因此就完成了代理物件增強的功能:

    public final void learn() {
        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$learn$0$Method, CGLIB$emptyArgs, CGLIB$learn$0$Proxy);
        } else {
            super.learn();
        }
    }

cglib 和 jdk 動態代理有什麼區別

  1. jdk 動態代理是利用攔截器加上反射生成了一個代理介面的匿名類,執行方法的時候交給 InvokeHandler 處理。CGLIB 動態代理是使用了 ASM框架,修改原來的位元組碼,然後生成新的子類來處理。
  2. JDK 代理需要實現介面,但是CGLIB不強制。
  3. 在JDK1.6之前,cglib因為用了位元組碼生成技術,比反射效率高,但是之後jdk也進行了一些優化,效率上已經提升了。

【作者簡介】
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java原始碼解析JDBCMybatisSpringredis分散式劍指OfferLeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。

劍指Offer全部題解PDF

2020年我寫了什麼?

開源程式設計筆記

相關文章