源碼詳解系列(一)------cglib動態代理的使用和分析


簡介

為什么會有動態代理?

舉個例子,當前有一個用戶操作類,要求每個方法執行前打印訪問日志。

這里可以采用兩種方式:

第一種,靜態代理。即編譯時對方法進行擴展。

第二種,動態代理。即運行時對方法進行擴展。

動態代理被廣泛應用於日志記錄、性能統計、安全控制、事務處理、異常處理等等,是spring實現AOP的重要支持。

常見的動態代理有哪些?

常用的動態代理有:JDK動態代理、cglib。

感興趣的可以研究下aspectJ

什么是cglib

cglib基於asm字節碼生成框架,用於動態生成代理類。與JDK動態代理不同,有以下幾點不同:

  1. JDK動態代理要求被代理類實現某個接口,而cglib無該要求。

  2. JDK動態代理生成的代理類是該接口實現類,也就是說,不能代理接口中沒有的方法,而cglib生成的代理類繼承被代理類。

  3. 在字節碼的生成和類的創建上,JDK的動態代理效率更高。

  4. 在代理方法的執行效率上,由於采用了FastClass,cglib的效率更高(以空間換時間)。

注:因為JDK動態代理中代理類中的方法是通過反射調用的,而cglib因為引入了FastClass,可以直接調用代理類對象的方法。

使用例子

需求

模擬對用戶數據進行增刪改前打印訪問日志

工程環境

JDK:1.8

maven:3.6.1

IDE:STS4

主要步驟

  1. 創建Enhancer對象:Enhancer是cglib代理的對外接口,以下操作都是調用這個類的方法
  2. setSuperclass(Class superclass):代理誰?
  3. setCallback(final Callback callback):怎么代理?(我們需要實現Callback的子接口MethodInterceptor,重寫其中的intercept方法,該方法定義了代理規則)
  4. create():獲得代理類
  5. 使用代理類

創建項目

項目類型Maven Project,打包方式jar

引入依賴

    <!-- cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

編寫被代理類

包路徑:cn.zzs.cglib
這里只是簡單地測試,不再引入復雜的業務邏輯。

public class UserController {
    public void save() {
        System.out.println("增加用戶");
    }
    public void delete() {
        System.out.println("刪除用戶");
    }
    public void update() {
        System.out.println("修改用戶");
    }
    public void find() {
        System.out.println("查找用戶");
    }
}

編寫MethodInterceptor接口實現類

包路徑:cn.zzs.cglib

public class LogInterceptor implements MethodInterceptor {

    @Override
    public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy ) throws Throwable {
        // 設置需要代理攔截的方法
        HashSet<String> set = new HashSet<String>( 6 );
        set.add( "save" );
        set.add( "delete" );
        set.add( "update" );
        // 進行日志記錄
        if( method != null && set.contains( method.getName() ) ) {
            System.out.println( "進行" + method.getName() + "的日志記錄" );
        }
        // 執行被代理類的方法
        Object obj2 = proxy.invokeSuper( obj, args );
        return obj2;
    }
}

編寫測試類

這里的輸出代理類的class文件,方便后面分析。

包路徑:test下的cn.zzs.cglib

public class CglibTest {
    @Test
    public void test01() {
        // 設置輸出代理類到指定路徑
        System.setProperty( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/growUp/test" );
        // 創建Enhancer對象,用於生成代理類
        Enhancer enhancer = new Enhancer();
        // 設置哪個類需要代理
        enhancer.setSuperclass( UserController.class );
        // 設置怎么代理,這里傳入的是Callback對象-MethodInterceptor父類
        LogInterceptor logInterceptor = new LogInterceptor();
        enhancer.setCallback( logInterceptor );
        // 獲取代理類實例
        UserController userController = ( UserController )enhancer.create();
        // 測試代理類
        System.out.println( "-------------" );
        userController.save();
        System.out.println( "-------------" );
        userController.delete();
        System.out.println( "-------------" );
        userController.update();
        System.out.println( "-------------" );
        userController.find();
    }
}

運行結果

CGLIB debugging enabled, writing to 'D:/growUp/test'
-------------
進行save的日志記錄
增加用戶
-------------
進行delete的日志記錄
刪除用戶
-------------
進行update的日志記錄
修改用戶
-------------
查找用戶

源碼分析-獲得代理類的過程

主要步驟

這里先簡單說下過程:

  1. 根據當前Enhancer實例生成一個唯一標識key

  2. 用key去緩存中找代理類的Class實例

  3. 找到了就返回代理類實例

  4. 找不到就生成后放入map,再返回代理類實例

獲得key

接下來具體介紹下。

首先,一進來就先調用了createHelper()

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

createHelper()中,創建了key,這個用於唯一標識當前類及相關配置,用於在緩存中存取代理類的Class實例。接着調用父類AbstractClassGeneratorcreate(Object 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;
    }

create(Object key)中,會調用內部類ClassLoaderDataget(AbstractClassGenerator gen, boolean useCache)方法獲取代理類的Class實例。

    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;
            //獲取代理類的Class類實例
            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);
        }
    }

利用key從緩存中獲取Class

ClassLoaderData有一個重要字段generatedClasses,是一個LoadingCache緩存對象,存放着當前類加載器加載的代理類的Class類實例。在以下方法中,就是從它里面尋找,通過key匹配查找。

        //這個對象存放着當前類加載器加載的代理類的Class類實例
        private final LoadingCache<AbstractClassGenerator, Object, Object> generatedClasses;
        public Object get(AbstractClassGenerator gen, boolean useCache) {
            if (!useCache) {
              return gen.generate(ClassLoaderData.this);
            } else {
              //獲取代理類的Class類實例
              Object cachedValue = generatedClasses.get(gen);
              return gen.unwrapCachedValue(cachedValue);
            }
        }

下面重點看下LoadingCache這個類,需要重點理解三個字段的意思:

//K:AbstractClassGenerator 這里指Enhancer類
//KK:Object 這里指前面生成key的類
//V:Object 這里指代理類的Class類
public class LoadingCache<K, KK, V> {
    //通過key可以拿到代理類的Class實例
    protected final ConcurrentMap<KK, Object> map;
    //通過loader.apply(Enhancer實例)可以獲得代理類的Class實例
    protected final Function<K, V> loader;
    //通過keyMapper.apply(Enhancer實例)可以獲得key
    protected final Function<K, KK> keyMapper;
    ·······
}

這里通過key去map里找代理類的Class實例,如果找不到,會重新生成后放入map中。

    public V get(K key) {
        final KK cacheKey = keyMapper.apply(key);
        Object v = map.get(cacheKey);
        if (v != null && !(v instanceof FutureTask)) {
            return (V) v;
        }

        return createEntry(key, cacheKey, v);
    }

生成代理類Class

以上基本說完如何從緩存中拿到代理類實例的方法,接下來簡單看下生成代理類的過程,即loader.apply(Enhancer實例),里面的generate會生成所需的Class對象,比較復雜,后面有時間再研究吧。

    public Object apply(AbstractClassGenerator gen) {
        Class klass = gen.generate(ClassLoaderData.this);
        return gen.wrapCachedClass(klass);
    }

代理類代碼分析

cglib生成文件

在一開始指定的路徑下,可以看到生成了三個文件,前面簡介里說到在代理類的生成上,cglib的效率低於JDK動態代理,主要原因在於多生成了兩個FastClass文件,至於這兩個文件有什么用呢?接下來會重點分析:

cglib生成class文件

代理類源碼

本文采用Luyten作為反編譯工具,一開始用jd-gui解析,但錯誤太多。

下面看看代理類的源碼。

在初始化時,代理類的字段都會被初始化,這里涉及到MethodProxycreate方法。

在實際調用update方法是會調用MethodInterceptor對象的intercept方法,執行我們自定義的代碼后,最終會調用的是MethodProxyinvokeSuper方法。下面重點看看這些方法。

注:考慮篇幅問題,這里僅展示update方法。

//生成類的名字規則是:被代理classname + "$$"+classgeneratorname+"ByCGLIB"+"$$"+key的hashcode
public class UserController$$EnhancerByCGLIB$$e6f193aa extends UserController implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    
    //我們一開始傳入的MethodInterceptor對象
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    //被代理類update方法
    private static final Method CGLIB$update$0$Method;
    //代理類update方法
    private static final MethodProxy CGLIB$update$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        //代理類Class對象
        final Class<?> forName = Class.forName("cn.zzs.cglib.UserController$$EnhancerByCGLIB$$e6f193aa");
        //被代理類Class對象
        final Class<?> forName2;
        final Method[] methods = ReflectUtils.findMethods(new String[] { "update", "()V", "find", "()V", "delete", "()V", "save", "()V" }, 
                (forName2 = Class.forName("cn.zzs.cglib.UserController")).getDeclaredMethods());
        //初始化被代理類update方法
        CGLIB$update$0$Method = methods[0];
        //初始化代理類update方法
        CGLIB$update$0$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()V", "update", "CGLIB$update$0");
    }
    
    final void CGLIB$update$0() {
        super.update();
    }
    
    public final void update() {
        MethodInterceptor cglib$CALLBACK_2;
        MethodInterceptor cglib$CALLBACK_0;
        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
        }
        //一般走這里,即調用我們傳入MethodInterceptor對象的intercept方法
        if (cglib$CALLBACK_0 != null) {
            cglib$CALLBACK_2.intercept((Object)this, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Proxy);
            return;
        }
        super.update();
    }

MethodProxy.create

通過以下代碼可以知道,MethodProxy對象CGLIB$update$0$Proxy持有了代理類和被代理類的Class實例,以及代理方法和被代理方法的符號表示,這兩個sig用於后面獲取方法索引。

    //Class c1, 被代理對象 
    //Class c2, 代理對象 
    //String desc, 參數列表描述  
    //String name1, 被代理方法  
    //String name2,代理方法 
    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);
        //創建createInfo
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }

MethodProxy.invokeSuper

以下方法中會去創建兩個FastClass文件,也就是我們看到的另外兩個文件。當然,它們只會創建一次。
另外,通過原來的方法簽名獲得了update的方法索引。

    //傳入參數obj:代理類實例
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            //初始化,創建了兩個FastClass類對象,並根據原來的方法簽名得到方法索引
            init();
            //這個對象持有兩個FastClass類對象和方法的索引
            FastClassInfo fci = fastClassInfo;
            //調用了代理對象FastClass的invoke方法
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
    private void init(){  
        if (fastClassInfo == null){  
            synchronized (initLock){  
                if (fastClassInfo == null){  
                    CreateInfo ci = createInfo;  
                    FastClassInfo fci = new FastClassInfo();  
                    //helper方法用ASM框架去生成了兩個FastClass類  
                    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;  
                }  
            }  
        }  
    }
private static class FastClassInfo{  
    FastClass f1;//被代理對象FastClass  
    FastClass f2;//代理對象FastClass  
    int i1;//被代理update方法的索引  
    int i2; //代理update方法的索引 
}  

FastClass.invoke

根據方法索引進行匹配,可以直接調用代理類實例的方法,而不需要像JDK動態代理一樣采用反射的方式,所以在方法執行上,cglib的效率會更高。

    //傳入參數:
    //n:方法索引
    //o:代理類實例
    //array:方法輸入參數
    public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
        final UserController$$EnhancerByCGLIB$$e6f193aa userController$$EnhancerByCGLIB$$e6f193aa = (UserController$$EnhancerByCGLIB$$e6f193aa)o;
        try {
            switch (n) {
                case 0: {
                    return new Boolean(userController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));
                }
                case 1: {
                    return userController$$EnhancerByCGLIB$$e6f193aa.toString();
                }
                case 2: {
                    return new Integer(userController$$EnhancerByCGLIB$$e6f193aa.hashCode());
                }
                case 3: {
                    return userController$$EnhancerByCGLIB$$e6f193aa.clone();
                }
                // ·······
                case 24: {
                    // 通過匹配方法索引,直接調用該方法,這個方法將直接調用代理類的超類的方法
                    userController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0();
                    return null;
                }
                // ·······

        }
        catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

相關源碼請移步:https://github.com/ZhangZiSheng001/cglib-demo

本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/11917086.html


免責聲明!

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



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