為什么需要動態代理?
- 如spring等這樣的框架,要增強具體業務的邏輯方法,不可能在框架里面去寫一個靜態代理類,只能按照用戶的注解或者xml配置來動態生成代理類。
- 業務代碼內,當需要增強的業務邏輯非常通用(如:添加log,重試,統一權限判斷等)時,使用動態代理將會非常簡單,如果每個方法增強邏輯不同,那么靜態代理更加適合。
- 使用靜態代理時,如果代理類和被代理類同時實現了一個接口,當接口方法有變動時,代理類也必須同時修改。
java字節碼生成方案(還有好些不怎么流行的框架)
- asm,底層字節碼框架,操縱的級別是底層JVM的匯編指令級別,這要求ASM使用者要對class組織結構和JVM匯編指令有一定的了解。
需要對字節碼的結構,語法含義等十分了解,所以只貼上一點示例,如果希望通過asm生成下面這樣一個class.
package com.samples;
import java.io.PrintStream;
public class Programmer {
public void code()
{
System.out.println("I'm a Programmer,Just Coding.....");
}
}
使用ASM框架提供了ClassWriter 接口,通過訪問者模式進行動態創建class字節碼,看下面的例子:
package samples;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyGenerator {
public static void main(String[] args) throws IOException {
System.out.println();
ClassWriter classWriter = new ClassWriter(0);
// 通過visit方法確定類的頭部信息
classWriter.visit(Opcodes.V1_7,// java版本
Opcodes.ACC_PUBLIC,// 類修飾符
"Programmer", // 類的全限定名
null, "java/lang/Object", null);
//創建構造函數
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定義code方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter類已經完成
// 將classWriter轉換成字節數組寫到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("D://Programmer.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
- javassist,它是一個開源的分析、編輯和創建Java字節碼的類庫。是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所創建的。它已加入了開放源代碼JBoss 應用服務器項目,通過使用Javassist對字節碼操作為JBoss實現動態AOP框架。javassist是jboss的一個子項目,其主要的優點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。
下面通過Javassist創建上述的Programmer類:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//創建Programmer類
CtClass cc= pool.makeClass("com.samples.Programmer");
//定義code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代碼
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字節碼
cc.writeFile("d://temp");
}
}
cglib,它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。最流行的OR Mapping工具hibernate也使用CGLIB來代理單端single-ended(多對一和一對一)關聯(對集合的延遲抓取,是采用其他機制實現的)。EasyMock和jMock是通過使用模仿(moke)對象來測試java代碼的包。它們都通過使用CGLIB來為那些沒有接口的類創建模仿(moke)對象。[現在cglib好像不怎么維護了,javassist比較火爆]
jdk (jdk動態代理根據interface,生成的代理類會被緩存,每個接口只會生成一個代理類)
動態代理實現方案
首先定義一個接口和一個類:
public interface CountService {
int count();
}
public class CountServiceImpl implements CountService {
private int count = 0;
public int count() {
return count ++;
}
}
- 直接使用asm在被代理類基礎上生成新的字節碼形成代理類
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);
}
}
- 直接使用javassist在被代理類基礎上生成新的字節碼形成代理類
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;
}
- JavaAssit動態代理接口(javassist.util.proxy.MethodHandler)
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);
}
}
- cglib生成(net.sf.cglib.proxy.MethodInterceptor)
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);
}
}
- jdk動態代理(java.lang.reflect.InvocationHandler)
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);
}
}
各種動態代理方案比較
- 速度比較
//測試結果
//創建代理的速度
Create JDK Proxy: 13 ms
Create CGLIB Proxy: 217 ms //較慢
Create JAVAASSIST Proxy: 99 ms
Create JAVAASSIST Bytecode Proxy: 168 ms //較慢
Create ASM Proxy: 3 ms //最快
================
Run JDK Proxy: 2224 ms, 634,022 t/s //很慢,jdk生成的字節碼考慮了太多的條件,所以字節碼非常多,大多都是用不到的
Run CGLIB Proxy: 1123 ms, 1,255,623 t/s //較慢,也是因為字節碼稍微多了點
Run JAVAASSIST Proxy: 3212 ms, 438,999 t/s //非常慢,完全不建議使用javassist的代理類來實現動態代理
Run JAVAASSIST Bytecode Proxy: 206 ms, 6,844,977 t/s //和asm差不多的執行速度,因為他們都是只生成簡單純粹的執行字節碼,非常少(所以直接用javassist生成代理類的方式最值得推薦[從速度上講])
Run ASM Bytecode Proxy: 209 ms, 6,746,724 t/s //asm什么都是最快的,畢竟是只和最底層打交道
- 便捷性比較
排序(只是生成字節碼,語義性):javassist>asm
排序(動態代理):cglib=jdk>javassist bytecode>javassist proxy>asm