前言
JDK,CGLIB,JAVASSIST是常用的動態代理方式。
JDK動態代理僅能對具有介面的類進行代理。
CGLIB動態代理方式的目標類可以沒有介面。
Javassist是一個開源的分析、編輯和建立Java位元組碼的類庫,JAVASSIST可以動態修改類,比如新增方法和屬性。JAVASSIST的目標類也沒有介面限制。
動態代理常用在RPC介面呼叫中,因此選擇一個好的動態代理方式,會對系統效能有一定的提升。
對於程式碼的效能測試,常規的方法如下,如此是無法獲取到準確的效能資料的
long start = System.currentTimeMillis(); xxx.xx(); long end = System.currentTimeMillis(); System.out.println("執行時間:"+(end-start));
JMH用來做基準測試,由於JIT編譯器會根據程式碼執行情況進行優化,程式碼在第一次執行的時候,速度相對較慢,隨著執行的次數增加,JIT編譯器會對程式碼進行優化,以達到最佳的效能狀態。
JMH可以對程式碼進行預熱,讓程式碼達到最佳的效能狀態,再進行效能測試。
更詳細的說明參考: 使用JMH做Benchmark基準測試
本部落格主要講解使用JMH對這三種動態代理的物件建立過程和方法呼叫進行測試。JDK版本是8.0.
代理實現
Jdk方式
public class JdkInvocationHandler implements InvocationHandler { private Object target = null; public JdkInvocationHandler(Object object){ this.target = object; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(this.target,args); return result; } public Object getProxy(){ Object object = Proxy.newProxyInstance( this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); return object; } }
Cglib方式
引入pom
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
程式碼
public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object result = methodProxy.invokeSuper(o,objects); return result; } }
Javassist方式
pom
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.20</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.20</version> </dependency>
程式碼
public class JavassistProxy { public <T> T getProxy(Class<T> interfaceClass){ ProxyFactory proxyFactory = new ProxyFactory(); if(interfaceClass.isInterface()){ Class[] clz = new Class[1]; clz[0] = interfaceClass; proxyFactory.setInterfaces(clz); } else { proxyFactory.setSuperclass(interfaceClass); } proxyFactory.setHandler(new MethodHandler() { public Object invoke(Object proxy, Method method, Method method1, Object[] args) throws Throwable { Object result = method1.invoke(proxy,args); return result; } }); try{ T bean = (T)proxyFactory.createClass().newInstance(); return bean; } catch(Exception ex){ log.error("Javassit 建立代理失敗:{}",ex.getMessage()); return null; } } }
效能測試
建立效能測試
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class ProxyCreateTest { public static void main(String args[]) throws Exception{ Options ops = new OptionsBuilder().include(ProxyCreateTest.class.getSimpleName()) .forks(1).build(); new Runner(ops).run(); } @Benchmark public void CglibProxyCreate(){ ProxyService proxyService = (ProxyService)new CglibProxy().getProxy(ProxyServiceImpl.class); } @Benchmark public void JdkProxyCreate(){ ProxyService proxyService = (ProxyService) new JdkInvocationHandler(new ProxyServiceImpl()).getProxy(); } @Benchmark public void JavassistProxyCreate(){ ProxyService proxyService = (ProxyService) new JavassistProxy().getProxy(ProxyServiceImpl.class); } }
測試結果
第一次測試
* Benchmark Mode Cnt Score Error Units * ProxyCreateTest.CglibProxyCreate avgt 20 192.691 ± 5.962 ns/op * ProxyCreateTest.JavassistProxyCreate avgt 20 2741254.026 ± 334384.484 ns/op * ProxyCreateTest.JdkProxyCreate avgt 20 130.982 ± 14.467 ns/op * * 第二次測試 * Benchmark Mode Cnt Score Error Units * ProxyCreateTest.CglibProxyCreate avgt 20 212.150 ± 15.399 ns/op * ProxyCreateTest.JavassistProxyCreate avgt 20 2995729.108 ± 265629.897 ns/op * ProxyCreateTest.JdkProxyCreate avgt 20 124.842 ± 8.404 ns/op *
第三次測試 * Benchmark Mode Cnt Score Error Units * ProxyCreateTest.CglibProxyCreate avgt 20 206.603 ± 6.834 ns/op * ProxyCreateTest.JavassistProxyCreate avgt 20 2979335.282 ± 290935.626 ns/op * ProxyCreateTest.JdkProxyCreate avgt 20 129.260 ± 9.020 ns/op
從測試結果來看,javassist的代理物件建立效能最差。最好的是JDK方式。
呼叫效能測試
//所有測試執行緒共享一個例項
@State(Scope.Benchmark)
//呼叫的平均時間,例如“每次呼叫平均耗時xxx毫秒”,單位是時間/運算元 @BenchmarkMode(Mode.AverageTime)
//單位為納秒 @OutputTimeUnit(TimeUnit.NANOSECONDS) public class ProxyRunTest { private ProxyService proxyServiceCglib = (ProxyService)new CglibProxy().getProxy(ProxyServiceImpl.class); private ProxyService proxyServiceJdk = (ProxyService) new JdkInvocationHandler(new ProxyServiceImpl()).getProxy(); private ProxyService proxyServiceJavassist = (ProxyService) new JavassistProxy().getProxy(ProxyServiceImpl.class); public static void main(String args[]) throws Exception{ Options ops = new OptionsBuilder().include(ProxyRunTest.class.getSimpleName()) .forks(1).build(); new Runner(ops).run(); } //方法註解,表示該方法是需要進行 benchmark 的物件。 @Benchmark public void CglibProxyRun(){ proxyServiceCglib.run(); } @Benchmark public void JdkProxyRun(){ proxyServiceJdk.run(); } @Benchmark public void JavassistProxyRun(){ proxyServiceJavassist.run(); } }
測試結果
第一次測試
Benchmark Mode Cnt Score Error Units ProxyRunTest.CglibProxyRun avgt 20 9.918 ± 1.268 ns/op ProxyRunTest.JavassistProxyRun avgt 20 34.226 ± 2.655 ns/op ProxyRunTest.JdkProxyRun avgt 20 5.225 ± 0.449 ns/op 第二次測試 Benchmark Mode Cnt Score Error Units ProxyRunTest.CglibProxyRun avgt 20 6.975 ± 0.629 ns/op ProxyRunTest.JavassistProxyRun avgt 20 31.707 ± 0.885 ns/op ProxyRunTest.JdkProxyRun avgt 20 5.442 ± 0.514 ns/op 第三次測試 Benchmark Mode Cnt Score Error Units ProxyRunTest.CglibProxyRun avgt 20 8.079 ± 1.381 ns/op ProxyRunTest.JavassistProxyRun avgt 20 33.916 ± 2.904 ns/op ProxyRunTest.JdkProxyRun avgt 20 5.947 ± 0.498 ns/op
從測試結果來看,javassist的代理物件呼叫執行效能最差。最好的是JDK方式。
總結
1.不管是代理建立還是方法呼叫執行,Javassist方式的效能最好,JDK的方式最差。
2.對於實際使用過程中。代理物件一般只會建立一次,建立完成後快取起來供後續使用,因此對整體效能影響不大。JDK方式和CGLIB方式的方法呼叫執行效能差不多,實際可根據代理物件有無介面進行選擇。