動態代理是指在運行時,動態生成代理類。正如標題所示,能夠提供動態代理的方式有很多。既然選擇這么有很多,那么如何選擇會更好呢?
帶着這個疑問,我找到了Dubbo的作者——梁飛大神的一篇文章。文章中對四種方式都做了性能測試,從當時測試的結果來看,Javassist成了最好的選擇。
不過時間過了那么久,現在在JDK 1.8上,根據上面的測試用例測試,會發現JDK動態代理和CGLIB動態代理的性能提升了很多。
測試的版本信息如下:
1.8.0_201,cglib-3.2.5,javassist-3.12.1.GA,asm使用的是JDK自帶的。
測試結果如下:
Create JDK Proxy: 4 ms
Create CGLIB Proxy: 108 ms
Create JAVAASSIST Proxy: 39 ms
Create JAVAASSIST Bytecode Proxy: 82 ms
Create ASM Proxy: 4 ms
================
Run JDK Proxy: 136 ms, 367,647,000 t/s
Run CGLIB Proxy: 215 ms, 232,558,000 t/s
Run JAVAASSIST Proxy: 1690 ms, 29,585,000 t/s
Run JAVAASSIST Bytecode Proxy: 183 ms, 273,224,000 t/s
Run ASM Bytecode Proxy: 172 ms, 290,697,000 t/s
----------------
Run JDK Proxy: 203 ms, 246,305,000 t/s
Run CGLIB Proxy: 288 ms, 173,611,000 t/s
Run JAVAASSIST Proxy: 1457 ms, 34,317,000 t/s
Run JAVAASSIST Bytecode Proxy: 183 ms, 273,224,000 t/s
Run ASM Bytecode Proxy: 188 ms, 265,957,000 t/s
----------------
Run JDK Proxy: 288 ms, 173,611,000 t/s
Run CGLIB Proxy: 272 ms, 183,823,000 t/s
Run JAVAASSIST Proxy: 1469 ms, 34,036,000 t/s
Run JAVAASSIST Bytecode Proxy: 188 ms, 265,957,000 t/s
Run ASM Bytecode Proxy: 179 ms, 279,329,000 t/s
----------------
從測試結果來看,JDK代理和CGLIB代理性能已經有很大的提升,ASM與Javassist在性能上已經拉不開差距了。
在易用性方面:
- JDK代理是最簡單方便的,只需要使用Proxy和InvocationHandler兩個類,不過只能代理接口。
- 其次是CGLIB,也很方便,不過需要引入CGLIB的JAR包。
- Javassist需要用用字符串拼接Java源代碼,稍微會比較繁瑣。
- 最麻煩的是ASM,需要手工寫字節碼,一般人可能還寫不出來。
在代理對象的創建速度上,JDK代理與ASM都很快,比最慢的CGLIB快20倍以上。
結論
從上面的分析結果來看,性能上各種方式的差距不算太大。
考慮到易用性,在對接口進行動態代理時,使用JDK代理應該是最合適的。
在不能使用JDK代理的情況下,可以考慮使用CGLIB或者Javassist。
CGLIB的缺點是創建代理對象的速度慢,Javassist的缺點是需要手動編寫Java源碼。
如果非要在這個兩個中選擇一個,那么只有在對性能要求非常高的情況下選擇Javassist,其他一般情況下,個人認為CGLIB是比較合適的。
測試代碼
由於現在的機器運行速度較快,為了對比明顯,我將原先1千萬的數據計算,改成了5千萬。
package fun.example.netty.proxy;
import javassist.*;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.FieldVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DecimalFormat;
public class DynamicProxyPerformanceTest {
public static void main(String[] args) throws Exception {
CountService delegate = new CountServiceImpl();
long time = System.currentTimeMillis();
CountService jdkProxy = createJdkDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JDK Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService cglibProxy = createCglibDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create CGLIB Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService javassistProxy = createJavassistDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JAVAASSIST Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService javassistBytecodeProxy = createJavassistBytecodeDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JAVAASSIST Bytecode Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService asmBytecodeProxy = createAsmBytecodeDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create ASM Proxy: " + time + " ms");
System.out.println("================");
for (int i = 0; i < 3; i++) {
test(jdkProxy, "Run JDK Proxy: ");
test(cglibProxy, "Run CGLIB Proxy: ");
test(javassistProxy, "Run JAVAASSIST Proxy: ");
test(javassistBytecodeProxy, "Run JAVAASSIST Bytecode Proxy: ");
test(asmBytecodeProxy, "Run ASM Bytecode Proxy: ");
System.out.println("----------------");
}
}
private static void test(CountService service, String label)
throws Exception {
service.count(); // warm up
int count = 50000000;
long time = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
service.count();
}
time = System.currentTimeMillis() - time;
System.out.println(label + time + " ms, " + new DecimalFormat().format(count / time * 1000) + " t/s");
}
private static CountService createJdkDynamicProxy(final CountService delegate) {
CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{CountService.class}, new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
return method.invoke(delegate, objects);
}
}
private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[]{CountService.class});
CountService cglibProxy = (CountService) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(delegate, objects);
}
}
private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(new Class[]{CountService.class});
Class<?> proxyClass = proxyFactory.createClass();
CountService javassistProxy = (CountService) proxyClass.newInstance();
((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));
return javassistProxy;
}
private static class JavaAssitInterceptor implements MethodHandler {
final Object delegate;
JavaAssitInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object self, Method m, Method proceed,
Object[] args) throws Throwable {
return m.invoke(delegate, args);
}
}
private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");
mCtc.addInterface(mPool.get(CountService.class.getName()));
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));
mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));
Class<?> pc = mCtc.toClass();
CountService bytecodeProxy = (CountService) pc.newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
String className = CountService.class.getName() + "AsmProxy";
String classPath = className.replace('.', '/');
String interfacePath = CountService.class.getName().replace('.', '/');
classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});
MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
initVisitor.visitCode();
initVisitor.visitVarInsn(Opcodes.ALOAD, 0);
initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
initVisitor.visitInsn(Opcodes.RETURN);
initVisitor.visitMaxs(0, 0);
initVisitor.visitEnd();
FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);
fieldVisitor.visitEnd();
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");
methodVisitor.visitInsn(Opcodes.IRETURN);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] code = classWriter.toByteArray();
CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static class ByteArrayClassLoader extends ClassLoader {
public ByteArrayClassLoader() {
super(ByteArrayClassLoader.class.getClassLoader());
}
public synchronized Class<?> getClass(String name, byte[] code) {
if (name == null) {
throw new IllegalArgumentException("");
}
return defineClass(name, code, 0, code.length);
}
}
}