前言
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方式的方法調用執行性能差不多,實際可根據代理對象有無接口進行選擇。