欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中的CGLIB動態(tài)代理的使用及原理詳解

 更新時間:2023年09月27日 08:51:55   作者:sco5282  
這篇文章主要介紹了Java中的CGLIB動態(tài)代理的使用及原理詳解,CGLIB是一個功能強(qiáng)大,高性能的代碼生成包,它為沒有實(shí)現(xiàn)接口的類提供代理,為JDK的動態(tài)代理提供了很好的補(bǔ)充,需要的朋友可以參考下

1. CGLIB 動態(tài)代理介紹

什么是 CGLIB?

CGLIB是一個功能強(qiáng)大,高性能的代碼生成包。它為沒有實(shí)現(xiàn)接口的類提供代理,為JDK的動態(tài)代理提供了很好的補(bǔ)充。

通常可以使用Java的動態(tài)代理創(chuàng)建代理,但當(dāng)要代理的類沒有實(shí)現(xiàn)接口或者為了更好的性能,CGLIB 是一個好的選擇。

CGLIB 的原理

CGLIB 原理:動態(tài)生成一個要代理類的子類,子類重寫要代理的類的所有不是 final 的方法。在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯。

CGLIB 底層:采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,比使用 Java 反射效率要高。

2. CGLIB 動態(tài)代理使用

CGLIB 動態(tài)代理步驟:

  1. 引入 CGLIB 依賴
  2. 定義一個被代理類
  3. 定義一個攔截器并實(shí)現(xiàn)接口 MethodInterceptor
  4. 代理工廠類
  5. 通過代理對象調(diào)用方法

引入依賴: cglib-nodep-2.2.jar

Student :被代理類

public class Student {
    public void handOut() {
        System.out.println("學(xué)生交作業(yè)。");
    }
}

CglibProxy :攔截器

public class CglibProxy implements MethodInterceptor {
    /**
     * @param o: 代理對象
     * @param method: 被代理方法
     * @param params: 方法入?yún)?
     * @param methodProxy: CGLIB方法
     **/
    @Override
    public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
        System.out.println("【增強(qiáng)方法】代理對象正在執(zhí)行的方法:" + method.getName());
        Object result = methodProxy.invokeSuper(o, params);
        return result;
    }
}

CglibProxyFactory :代理工廠類

public class CglibProxyFactory {
    public static Object creatCglibProxyObj(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        // 為加強(qiáng)器指定要代理的業(yè)務(wù)類(即為下面生成的代理類指定父類)
        enhancer.setSuperclass(clazz);
        // 設(shè)置回調(diào):對于代理類上所有方法的調(diào)用,都會調(diào)用CallBack,而Callback則需要實(shí)現(xiàn)intercept()方法
        enhancer.setCallback(new CglibProxy());
        return enhancer.create();
    }
}

測試:

public class Test {
    public static void main(String[] args) {
        Student studentProxy = (Student)CglibProxyFactory.creatCglibProxyObj(Student.class);
        studentProxy.handOut();
    }
}

運(yùn)行后,依舊可以增強(qiáng)原功能。

3. CGLIB 動態(tài)代理原理

上文中的是通過 enhancer.create 方法調(diào)用獲取的代理對象,以此為入口深入探究一下 CGLIB 動態(tài)代理的實(shí)現(xiàn)原理。

Enhancer#create() :

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

Enhancer#createHelper() :調(diào)用父類的 create() 方法

private Object createHelper() {
    //...
    return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), 
    		this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
}

AbstractClassGenerator#create() :

protected Object create(Object key) {
    try {
        //...
            if (gen == null) {
            	// 1.生成代理類 
                byte[] b = this.strategy.generate(this);
                // 2.獲取代理類名稱
                String className = ClassNameReader.getClassName(new ClassReader(b));
                this.getClassNameCache(loader).add(className);
                gen = ReflectUtils.defineClass(className, b, loader);
            }
            if (this.useCache) {
                ((Map)cache2).put(key, new WeakReference(gen));
            }
            var24 = this.firstInstance(gen);
        } finally {
            CURRENT.set(save);
        }
        return var24;
    }
    //...
}

DefaultGeneratorStrategy#generate() :生成代理類

public byte[] generate(ClassGenerator cg) throws Exception {
    ClassWriter cw = this.getClassWriter();
    this.transform(cg).generateClass(cw);
    return this.transform(cw.toByteArray());
}

DebuggingClassWriter#toByteArray() :

public byte[] toByteArray() {
    return (byte[])((byte[])AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            byte[] b = DebuggingClassWriter.super.toByteArray();
            if (DebuggingClassWriter.debugLocation != null) {
                String dirs = DebuggingClassWriter.this.className.replace('.', File.separatorChar);
                try {
                	// 如果 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY 系統(tǒng)屬性被設(shè)置,則輸出代理類到指定目錄
                    (new File(DebuggingClassWriter.debugLocation + File.separatorChar + dirs)).getParentFile().mkdirs();
                    File file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".class");
                    BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                    try {
                        out.write(b);
                    } finally {
                        out.close();
                    }
                    if (DebuggingClassWriter.traceEnabled) {
                        file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".asm");
                        out = new BufferedOutputStream(new FileOutputStream(file));
                        try {
                            ClassReader cr = new ClassReader(b);
                            PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
                            TraceClassVisitor tcv = new TraceClassVisitor((ClassVisitor)null, pw);
                            cr.accept(tcv, 0);
                            pw.flush();
                        } finally {
                            out.close();
                        }
                    }
                } catch (IOException var17) {
                    throw new CodeGenerationException(var17);
                }
            }
            return b;
        }
    }));
}

生成 CGLIB 字節(jié)碼文件

由上文可知,把 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY (也就是 cglib.debugLocation )系統(tǒng)屬性設(shè)置為當(dāng)前項(xiàng)目的根目錄,即可保存 CGLIB 生成的代理類到當(dāng)前項(xiàng)目根目錄下。

設(shè)置系統(tǒng)屬性配置:

public static void main(String[] args) {
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));
    Student studentProxy = (Student)CglibProxyFactory.creatCglibProxyObj(Student.class);
    studentProxy.handOut();
}

運(yùn)行代碼:

在這里插入圖片描述

生成的動態(tài)代理類為:

public class Student$$EnhancerByCGLIB$$723acbd8 extends Student implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$handOut$0$Method;
    private static final MethodProxy CGLIB$handOut$0$Proxy;
    //...
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.zzc.proxy.cglib.Student$$EnhancerByCGLIB$$723acbd8");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$handOut$0$Method = ReflectUtils.findMethods(new String[]{"handOut", "()V"}, (var1 = Class.forName("com.zzc.proxy.cglib.Student")).getDeclaredMethods())[0];
        CGLIB$handOut$0$Proxy = MethodProxy.create(var1, var0, "()V", "handOut", "CGLIB$handOut$0");
        //...
    }
    final void CGLIB$handOut$0() {
        super.handOut();
    }
    public final void handOut() {
        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$handOut$0$Method, CGLIB$emptyArgs, CGLIB$handOut$0$Proxy);
        } else {
            super.handOut();
        }
    }
	//...
    static {
        CGLIB$STATICHOOK1();
    }
}

說明:

  1. 生成的動態(tài)代理類繼承了父類 Student,并且實(shí)現(xiàn)了接口 Factory
  2. 動態(tài)代理類持有 MethodInterceptor
  3. 動態(tài)代理類會重寫父類 Student 的非 final、private 方法;也會構(gòu)建自己的方法(cglib 方法),構(gòu)建方式:CGLIB”+“$父類方法名$
  4. cglib 方法的方法體:super.方法名,直接調(diào)用父類;重寫方法:它會調(diào)用攔截器中的 intercept() 方法
  5. methodProxy.invokeSuper() 方法會調(diào)用動態(tài)代理類中的 cglib 方法;methodProxy.invoke() 方法會調(diào)用動態(tài)代理類中的重寫方法

CGLIB 動態(tài)代理原理:外界調(diào)用了方法后( studentProxy.handOut(); ),由于父類 Student 被子類(動態(tài)代理類)給繼承了(已經(jīng)重寫了 handOut() ),所以,會調(diào)用動態(tài)代理類中的 handOut() 方法。而在這個重寫的方法中,又會去調(diào)用 MethodInterceptor#intercept() 方法。在這個方法中,功能增強(qiáng)后,再去調(diào)用動態(tài)代理中的 cglib 方法,而此方法又會去調(diào)用父類中的方法。

4. JDK 動態(tài)代理和 CGLIB 動態(tài)代理比較

4.1 區(qū)別

總結(jié)一下兩者的區(qū)別吧:

  • JDK 動態(tài)代理基于接口,CGLIB 動態(tài)代理基于類。因?yàn)?JDK 動態(tài)代理生成的代理類需要繼承 java.lang.reflect.Proxy,所以,只能基于接口;CGLIB 動態(tài)代理是根據(jù)類創(chuàng)建此類的子類,所以,此類不能被 final 修飾
  • JDK 和 CGLIB 動態(tài)代理都是在運(yùn)行期生成字節(jié)碼。而 JDK 是直接寫 Class 字節(jié)碼;而 CGLIB 使用 ASM 框架寫 Class 字節(jié)碼(不鼓勵直接使用ASM,因?yàn)樗竽惚仨殞?JVM 內(nèi)部結(jié)構(gòu)包括 class 文件的格式和指令集都很熟悉)
  • JDK 通過反射調(diào)用方法,CGLIB 通過 FastClass 機(jī)制(下一篇再將)直接調(diào)用方法。所以,CGLIB 執(zhí)行的效率較高
  • JDK 動態(tài)代理是利用反射機(jī)制生成一個實(shí)現(xiàn)代理接口的類(這個類看不見摸不著,在 jvm 內(nèi)存中有這個類),在調(diào)用具體方法前調(diào)用 InvokeHandler來處理。核心是實(shí)現(xiàn) InvocationHandler接口,使用 invoke()方法進(jìn)行面向切面的處理,調(diào)用相應(yīng)的通知;CGLIB 動態(tài)代理是利用 asm 開源包,對代理對象類的 class 文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。核心是實(shí)現(xiàn) MethodInterceptor 接口,使用 intercept() 方法進(jìn)行面向切面的處理,調(diào)用相應(yīng)的通知。

4.2 優(yōu)缺點(diǎn)

劣勢:

  • JDK:JDK的動態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,而不能實(shí)現(xiàn)接口的類就不能實(shí)現(xiàn)JDK的動態(tài)代理
  • CGLIB:CGLIB 的原理是對指定的目標(biāo)類生成一個子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對 final 修飾的類進(jìn)行代理

優(yōu)勢:

  • JDK:最小化依賴關(guān)系,減少依賴意味著簡化開發(fā)和維護(hù),JDK本身的支持,可能比 cglib 更加可靠
  • JDK:平滑進(jìn)行JDK版本升級,而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版Java 上能夠使用。代碼實(shí)現(xiàn)簡單
  • CGLIB:從某種角度看,限定調(diào)用者實(shí)現(xiàn)接口是有些侵入性的實(shí)踐,類似cglib動態(tài)代理就沒有這種限制。只操作我們關(guān)心的類,而不必為其他相關(guān)類增加工作量。另外高性能。

5. 動態(tài)代理在 Spring 中的應(yīng)用

Spring 應(yīng)用:

如果目標(biāo)對象實(shí)現(xiàn)了接口,默認(rèn)情況下 Spring 會采用 JDK 的動態(tài)代理實(shí)現(xiàn) AOP

如果目標(biāo)對象實(shí)現(xiàn)了接口,Spring 也可以強(qiáng)制使用 CGLIB 實(shí)現(xiàn) AOP

如果目標(biāo)對象沒有實(shí)現(xiàn)接口,必須采用 CGLIB 實(shí)現(xiàn)動態(tài)代理,當(dāng)然 Spring 可以在 JDK 動態(tài)代理和 CGLIB 動態(tài)代理之間轉(zhuǎn)換

到此這篇關(guān)于Java中的CGLIB動態(tài)代理的使用及原理詳解的文章就介紹到這了,更多相關(guān)CGLIB動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論