動態代理方案,優缺點,比較(摘抄+自己的理解)

為什么需要動態代理?

  • 如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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容