Spring事務Transactional和動態代理(二)-cglib動態代理


系列文章索引:

  1. Spring事務Transactional和動態代理(一)-JDK代理實現
  2. Spring事務Transactional和動態代理(二)-cglib動態代理
  3. Spring事務Transactional和動態代理(三)-事務失效的場景

什么是cglib

Cglib是一個強大的、高性能的代碼生成包,它廣泛被許多AOP框架使用,為他們提供方法的攔截。它為沒有實現接口的類提供代理,為JDK的動態代理提供了很好的補充。JDK必須強制基於interface接口類型:Spring事務Transactional和動態代理(上)-JDK代理實現

cglib的應用

cglib應用很廣泛,根據cglib在Github上的描述(cglib),存在以下應用:

  1. Byte Code Engineering Library
    也就是JavaClass字節碼文件,這個庫可以很方便的分析,創建和操作字節碼文件
  2. XORM
    是一個可擴展的ORM框架,使用cglib來生成持久化對象,為RDBMS提供了映射到接口的持久Entity,讓開發人員專注於業務對象模型
  3. Hibernate
    Hibernate是一個又一個強大的、超高性能的Java對象/關系持久性框架。可以開發持久對象,包括關聯、繼承、多態性、組合和Java集合框架
  4. The Java Class File Editor
    Java類文件編輯器,允許用戶在磁盤上或在運行時加載類時讀取/修改Class文件,也它可以動態地創建新類
  5. Nanning Aspects
    是一個基於java的簡介AOP框架
  6. Spring
  7. iBatis/Mybatis
  8. ASM
  9. Proxool
    基於java的連接池
  10. Guice
  11. ModelMapper

cglib的使用

使用cglib需要先引入jar包,在maven中添加依賴:

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

新建一個目標類,其中一個為final方法,一個為非final方法,用於對比cglib對於兩種方法的織入結果:

public class Student {

    public void study(){
        System.out.println("study");
    }

    public final void eat(){
        System.out.println("eat");
    }

}

Interceptor 代理類如下:

public class CglibInterceptor implements MethodInterceptor {
    //織入前的處理
    private void beforeInvoke(Method method){
        System.out.println("before " + method.getName());
    }

    //織入后的處理
    private void afterInvoke(Method method){
        System.out.println("after "  + method.getName());
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        beforeInvoke(method);
        //調用cglib的invokeSuper而不是invoke方法
        Object object = methodProxy.invokeSuper(o,objects);
        afterInvoke(method);
        return object;
    }
}

測試類的調用順序為

  1. 創建增強建Enhancer實例
  2. 通過setSuperclass方法來設置目標類
  3. 通過setCallback設置Interceptor攔截
  4. 調用Enhancer的create方法生成代理類
    代碼如下:
public class CglibTesst {

    public static void main(String[] args) {
        //把生產的代理類保存到磁盤指定文件夾
        System.getProperties().put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);
        enhancer.setCallback(new CglibInterceptor());

        Student studentProxy = (Student) enhancer.create();
        studentProxy.study();
        studentProxy.eat();
    }
}

其中的輸出如下,可以看到只有非final方法study織入了before和after邏輯,而final方法eat是沒有的:

before study
study
after study

eat

cglib生成的代理class文件分析

通過在測試類中加入了

System.getProperties().put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");

代碼之后,本地就多出來了一些.class文件如下:

首先看一下Student$EnhancerByCGLIB$92f3e3f6,繼承了Student並且實現了Factory接口(接口方法主要是newInstance,setCallback和getCallbacks),該類中的代碼太多,以下代碼是節選:

public class Student$EnhancerByCGLIB$92f3e3f6 extends Student implements Factory {
    
    //靜態初始化類
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.randy.dynamicproxy.cglib.Student$$EnhancerByCGLIB$$92f3e3f6");
        Class 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());
        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");
        CGLIB$study$0$Method = ReflectUtils.findMethods(new String[]{"study", "()V"}, (var1 = Class.forName("com.randy.dynamicproxy.cglib.Student")).getDeclaredMethods())[0];
        CGLIB$study$0$Proxy = MethodProxy.create(var1, var0, "()V", "study", "CGLIB$study$0");
    }

    static {
        CGLIB$STATICHOOK1();
    }

    final void CGLIB$study$0() {
        super.study();
    }

    public final void study() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        //檢查當前Callback攔截對象
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        //根據是否存在判定是通過攔截類來調用還是直接調用父類Student的study方法
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$study$0$Method, CGLIB$emptyArgs, CGLIB$study$0$Proxy);
        } else {
            super.study();
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
       ...
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

    public final String toString() {
       ...
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

    public final int hashCode() {
        ...
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
       ...
    }
}

可以看到該生成類中除了實現Factory接口的方法以外,都復寫了Student類以及超類Object中的非final方法(對於Student中的final方法eat和Object中的final方法wati,notify,notifyAll等方法都沒有復寫),這也就是為什么cglib無法對final方法進行代理,因為java不允許復寫final方法

另外兩個類 Student$EnhancerByCGLIB\(92f3e3f6\)FastClassByCGLIB\(1d02f934 和 Student\)FastClassByCGLIB$ec571eb6 都繼承了cglib的抽象類FastClass,
主要是實現了FastClass的一下幾個方法

    public abstract int getIndex(String var1, Class[] var2);
    public abstract int getIndex(Class[] var1);
    public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;
    public abstract Object newInstance(int var1, Object[] var2) throws InvocationTargetException;
    public abstract int getIndex(Signature var1);
    public abstract int getMaxIndex();

其中的

cglib的原理

cglib動態生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法(cglib無法對final方法進行代理)。在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。

CGLIB底層使用字節碼處理框架ASM,來轉換字節碼並生成新的類。關於java字節碼請查看:The Java class File Format

Enhancer類源碼分析

public class Enhancer extends AbstractClassGenerator {
    //設置目標類作為父類,也就是對應生成的Student$$EnhancerByCGLIB$$92f3e3f6類繼承了Student
    public void setSuperclass(Class superclass) {
        if (superclass != null && superclass.isInterface()) {
            this.setInterfaces(new Class[]{superclass});
        } else if (superclass != null && superclass.equals(Object.class)) {
            this.superclass = null;
        } else {
            this.superclass = superclass;
        }

    }
    //通過Enhancer來創建代理類
    public Object create() {
        this.classOnly = false;
        this.argumentTypes = null;
        return this.createHelper();
    }
    
    private Object createHelper() {
        this.preValidate();
        //根據當前設置的父類等信心構造一個唯一的key
        Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
        this.currentKey = key;
        Object result = super.create(key);
        return result;
    }
}

    protected Object create(Object key) {
        try {
            ClassLoader loader = this.getClassLoader();
            Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
            //首先從緩存中查找key,如果就生成一個
            AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
            if (data == null) {
                Class var5 = AbstractClassGenerator.class;
                synchronized(AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
                        //核心是調用了AbstractClassGenerator的generate來生成字節碼文件,並通過ReflectUtils.defineClass返回
                        data = new AbstractClassGenerator.ClassLoaderData(loader);
                        //加入緩存  
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }

            this.key = key;
            Object obj = data.get(this, this.getUseCache());
            //如果是類就通過firstInstance初始化,而firstInstance在AbstractClassGenerator類中是一個抽象方法,具體實現如下
            //firstInstance和nextInstance都是通過cglib的ReflectUtils.newInstance來創建實例的
            return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
        } catch (RuntimeException var9) {
            throw var9;
        } catch (Error var10) {
            throw var10;
        } catch (Exception var11) {
            throw new CodeGenerationException(var11);
        }
    }

MethodProxy

當所生成的代理類被調用的時候,MethodProxy會在所設置的CallBack中調用intercept方法。而在上面的CglibInterceptor類的intercept方法中就是使用的MethodProxy.invokeSuper方法,源碼如下:

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            //單例初始化
            init();
            FastClassInfo fci = fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

init方法:

init()方法是一個經典的雙重檢查單例設計模式,初始判斷對象是否已經初始化了,如果沒有就加鎖並再次判空。初始化的內容主要是FastClassInfo對象及其屬性

private final Object initLock = new Object();

private void init()
    {
        if (fastClassInfo == null)
        {
            synchronized (initLock)
            {
                if (fastClassInfo == null)
                {
                    CreateInfo ci = createInfo;
                    FastClassInfo fci = new FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    //通過getIndex來查找到指定方法的索引
                    fci.i1 = fci.f1.getIndex(sig1);
                    fci.i2 = fci.f2.getIndex(sig2);
                    fastClassInfo = fci;
                    createInfo = null;
                }
            }
        }
    }

FastClass機制

FastClass機制就是對一個類的方法建立索引,通過索引來直接調用相應的方法,在上述的其中的invokeSuper中init初始化的主要就是FastClassInfo(內部類,持有兩個FastClass類型的變量)。

 private static class FastClassInfo
    {
        //目標類的FastClass
        FastClass f1;
        //代理類的FastClass
        FastClass f2;
        //目標類方法的索引
        int i1;
        //代理類方法的索引
        int i2;
    }

在上一篇JDK代理實現 中提到JDK攔截對象是通過InvocationHandler反射的機制來調用被攔截方法的,反射的效率比較低。
而cglib是對一個類的方法建立索引,通過索引來直接調用相應的方法。
如生成的Student\(FastClassByCGLIB\)ec571eb6就是繼承了FastClass,通過getIndex(Signature)通過方法簽名來定位一個索引,

    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -1310345955:
            if (var10000.equals("eat()V")) {
                return 1;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return 2;
            }
            break;
        case 1876544780:
            if (var10000.equals("study()V")) {
                return 0;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return 3;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return 4;
            }
        }

        return -1;
    }

在根據獲取的的Index位置來調用invoke方法,invoke方法在FastClass類中是一個抽象方法,子類(也就是生成的Student\(FastClassByCGLIB\)ec571eb6繼承FastClass)具體實現如下:

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

        try {
            switch(var10001) {
            case 0:
                var10000.study();
                return null;
            case 1:
                var10000.eat();
                return null;
            case 2:
                return new Boolean(var10000.equals(var3[0]));
            case 3:
                return var10000.toString();
            case 4:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }
}

參考:

  1. https://github.com/cglib/cglib/wiki
  2. https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html
  3. https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
  4. https://www.baeldung.com/cglib
  5. https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/index.html


免責聲明!

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



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