Java中的CGLIB動態(tài)代理的使用及原理詳解
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)代理步驟:
- 引入 CGLIB 依賴
- 定義一個被代理類
- 定義一個攔截器并實(shí)現(xiàn)接口 MethodInterceptor
- 代理工廠類
- 通過代理對象調(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(); } }
說明:
- 生成的動態(tài)代理類繼承了父類 Student,并且實(shí)現(xiàn)了接口 Factory
- 動態(tài)代理類持有 MethodInterceptor
- 動態(tài)代理類會重寫父類 Student 的非 final、private 方法;也會構(gòu)建自己的方法(cglib 方法),構(gòu)建方式:CGLIB”+“$父類方法名$
- cglib 方法的方法體:super.方法名,直接調(diào)用父類;重寫方法:它會調(diào)用攔截器中的 intercept() 方法
- 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)文章
IDEA:Error running,Command line is too&n
這篇文章主要介紹了IDEA:Error running,Command line is too long.解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07Java中的隱式參數(shù)和顯示參數(shù)實(shí)例詳解
這篇文章主要介紹了Java中的隱式參數(shù)和顯示參數(shù)是什么,另外還有兩個小例子幫助大家理解,需要的朋友可以參考下。2017-08-08Spring MVC訪問靜態(tài)文件_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Spring MVC訪問靜態(tài)文件的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08使用Java獲取html中Select,radio多選的值方法
以下是對使用Java獲取html中Select,radio多選值的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-08-08Springboot jar包 idea 遠(yuǎn)程調(diào)試的操作過程
文章介紹了如何在IntelliJ IDEA中遠(yuǎn)程調(diào)試Spring Boot項(xiàng)目的Jar包,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-11-11關(guān)于@Component注解下的類無法@Autowired問題
這篇文章主要介紹了關(guān)于@Component注解下的類無法@Autowired問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03詳解手把手Maven搭建SpringMVC+Spring+MyBatis框架(超級詳細(xì)版)
本篇文章主要介紹了手把手Maven搭建SpringMVC+Spring+MyBatis框架(超級詳細(xì)版),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12