Spring AOP中的JDK和CGLib動態(tài)代理哪個效率更高?
一、背景
今天有小伙伴面試的時候被問到:Spring AOP中JDK 和 CGLib動態(tài)代理哪個效率更高?
二、基本概念
首先,我們知道Spring AOP的底層實現(xiàn)有兩種方式:一種是JDK動態(tài)代理,另一種是CGLib的方式。
自Java 1.3以后,Java提供了動態(tài)代理技術,允許開發(fā)者在運行期創(chuàng)建接口的代理實例,后來這項技術被用到了Spring的很多地方。
JDK動態(tài)代理主要涉及java.lang.reflect包下邊的兩個類:Proxy和InvocationHandler。其中,InvocationHandler是一個接口,可以通過實現(xiàn)該接口定義橫切邏輯,并通過反射機制調(diào)用目標類的代碼,動態(tài)地將橫切邏輯和業(yè)務邏輯貶值在一起。
JDK動態(tài)代理的話,他有一個限制,就是它只能為接口創(chuàng)建代理實例,而對于沒有通過接口定義業(yè)務方法的類,如何創(chuàng)建動態(tài)代理實例哪?答案就是CGLib。
CGLib采用底層的字節(jié)碼技術,全稱是:Code Generation Library,CGLib可以為一個類創(chuàng)建一個子類,在子類中采用方法攔截的技術攔截所有父類方法的調(diào)用并順勢織入橫切邏輯。
三、JDK 和 CGLib動態(tài)代理區(qū)別
1、JDK動態(tài)代理具體實現(xiàn)原理:
- 通過實現(xiàn)InvocationHandlet接口創(chuàng)建自己的調(diào)用處理器;
- 通過為Proxy類指定ClassLoader對象和一組interface來創(chuàng)建動態(tài)代理;
- 通過反射機制獲取動態(tài)代理類的構造函數(shù),其唯一參數(shù)類型就是調(diào)用處理器接口類型;
- 通過構造函數(shù)創(chuàng)建動態(tài)代理類實例,構造時調(diào)用處理器對象作為參數(shù)參入;
JDK動態(tài)代理是面向接口的代理模式,如果被代理目標沒有接口那么Spring也無能為力,Spring通過Java的反射機制生產(chǎn)被代理接口的新的匿名實現(xiàn)類,重寫了其中AOP的增強方法。
2、CGLib動態(tài)代理:
CGLib是一個強大、高性能的Code生產(chǎn)類庫,可以實現(xiàn)運行期動態(tài)擴展java類,Spring在運行期間通過 CGlib繼承要被動態(tài)代理的類,重寫父類的方法,實現(xiàn)AOP面向切面編程呢。
3、兩者對比:
- JDK動態(tài)代理是面向接口的。
- CGLib動態(tài)代理是通過字節(jié)碼底層繼承要代理類來實現(xiàn)(如果被代理類被final關鍵字所修飾,那么抱歉會失敗)。
4、使用注意:
- 如果要被代理的對象是個實現(xiàn)類,那么Spring會使用JDK動態(tài)代理來完成操作(Spirng默認采用JDK動態(tài)代理實現(xiàn)機制);
- 如果要被代理的對象不是個實現(xiàn)類那么,Spring會強制使用CGLib來實現(xiàn)動態(tài)代理。
四、JDK 和 CGLib動態(tài)代理性能對比-教科書上的描述
我們不管是看書還是看文章亦或是我那個上搜索參考答案,可能很多時候,都可以找到如下的回答:
關于兩者之間的性能的話,JDK動態(tài)代理所創(chuàng)建的代理對象,在以前的JDK版本中,性能并不是很高,雖然在高版本中JDK動態(tài)代理對象的性能得到了很大的提升,但是他也并不是適用于所有的場景。主要體現(xiàn)在如下的兩個指標中:
1、CGLib所創(chuàng)建的動態(tài)代理對象在實際運行時候的性能要比JDK動態(tài)代理高不少,有研究表明,大概要高10倍;
2、但是CGLib在創(chuàng)建對象的時候所花費的時間卻比JDK動態(tài)代理要多很多,有研究表明,大概有8倍的差距;
3、因此,對于singleton的代理對象或者具有實例池的代理,因為無需頻繁的創(chuàng)建代理對象,所以比較適合采用CGLib動態(tài)代理,反正,則比較適用JDK動態(tài)代理。
結果是不是如上邊1、2、3條描述的那樣哪?下邊我們做一些小實驗分析一下!
五、性能測試
1、首先有幾個Java類
2、Target.java
package com.java.proxy.test; public interface Target { int test(int i); }
3、TargetImpl.java
package com.java.proxy.test; public class TargetImpl implements Target { @Override public int test(int i) { return i + 1; } }
4、JdkDynamicProxyTest.java
package com.java.proxy.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkDynamicProxyTest implements InvocationHandler { private Target target; private JdkDynamicProxyTest(Target target) { this.target = target; } public static Target newProxyInstance(Target target) { return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(), new Class<?>[]{Target.class}, new JdkDynamicProxyTest(target)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }
5、CglibProxyTest.java
package com.java.proxy.test; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxyTest implements MethodInterceptor { private CglibProxyTest() { } public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetInstanceClazz); enhancer.setCallback(new CglibProxyTest()); return (Target) enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }
6、ProxyPerformanceTest.java
package com.java.proxy.test; import java.util.LinkedHashMap; import java.util.Map; public class ProxyPerformanceTest { public static void main(String[] args) { //創(chuàng)建測試對象 Target nativeTest = new TargetImpl(); Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(nativeTest); Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class); //預熱一下 int preRunCount = 10000; runWithoutMonitor(nativeTest, preRunCount); runWithoutMonitor(cglibProxy, preRunCount); runWithoutMonitor(dynamicProxy, preRunCount); //執(zhí)行測試 Map<String, Target> tests = new LinkedHashMap<String, Target>(); tests.put("Native ", nativeTest); tests.put("Dynamic ", dynamicProxy); tests.put("Cglib ", cglibProxy); int repeatCount = 3; int runCount = 1000000; runTest(repeatCount, runCount, tests); runCount = 50000000; runTest(repeatCount, runCount, tests); } private static void runTest(int repeatCount, int runCount, Map<String, Target> tests) { System.out.println( String.format("\n===== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] =====", repeatCount, runCount, System.getProperty("java.version"))); for (int i = 0; i < repeatCount; i++) { System.out.println(String.format("\n--------- test : [%s] ---------", (i + 1))); for (String key : tests.keySet()) { runWithMonitor(tests.get(key), runCount, key); } } } private static void runWithoutMonitor(Target target, int runCount) { for (int i = 0; i < runCount; i++) { target.test(i); } } private static void runWithMonitor(Target target, int runCount, String tag) { long start = System.currentTimeMillis(); for (int i = 0; i < runCount; i++) { target.test(i); } long end = System.currentTimeMillis(); System.out.println("[" + tag + "] Total Time:" + (end - start) + "ms"); } }
7、測試結果
(1)JDK 1.6
(2)JDK 1.7
(3)JDK 1.8
經(jīng)過多次試驗,可以看出平均情況下的話,JDK動態(tài)代理的運行速度已經(jīng)逐漸提高了,在低版本的時候,運行的性能可能不如CGLib,但是在1.8版本中運行多次,基本都可以得到一致的測試結果,那就是JDK動態(tài)代理已經(jīng)比CGLib動態(tài)代理快了!
但是JDK動態(tài)代理和CGLib動態(tài)代理的適用場景還是不一樣的哈!
六、總結
最終的測試結果大致是這樣的,在1.6和1.7的時候,JDK動態(tài)代理的速度要比CGLib動態(tài)代理的速度要慢,但是并沒有教科書上的10倍差距,在JDK1.8的時候,JDK動態(tài)代理的速度已經(jīng)比CGLib動態(tài)代理的速度快很多了,希望小伙伴在遇到這個問題的時候能夠有的放矢!
Spring AOP中的JDK和CGLib動態(tài)代理關于這個知識點很重要,關于兩者之間性能的對比經(jīng)過測試實驗已經(jīng)有了一個初步的結果,以后再有人問你Spring AOP,不要簡單的說JDK動態(tài)代理和CGLib這兩個了,是時候的可以拋出來對兩者之間區(qū)別的理解,是有加分的哦!
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。如果你想了解更多相關內(nèi)容請查看下面相關鏈接
相關文章
Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法
本文主要介紹了Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-07-07JavaWeb中JavaMail創(chuàng)建郵件和發(fā)送郵件
這篇文章主要介紹了JavaWeb中JavaMail創(chuàng)建郵件和發(fā)送郵件,較為詳細的分析了JavaMail發(fā)送郵件的用法,是非常實用的技巧,需要的朋友可以參考下2015-12-12SpringBoot中支持Https協(xié)議的實現(xiàn)
本文主要介紹了SpringBoot中支持Https協(xié)議的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-01-01java如何更改數(shù)據(jù)庫中的數(shù)據(jù)
這篇文章主要介紹了java如何更改數(shù)據(jù)庫中的數(shù)據(jù),修改數(shù)據(jù)庫是數(shù)據(jù)庫操作必不可少的一部分,使用Statement接口中的excuteUpdate()方法可以修改數(shù)據(jù)表中的數(shù)據(jù),感興趣的朋友跟隨小編一起看看吧2021-11-11解決 Spring RestTemplate post傳遞參數(shù)時報錯問題
本文詳解說明了RestTemplate post傳遞參數(shù)時報錯的問題及其原由,需要的朋友可以參考下2020-02-02