動(dòng)態(tài)代理方案,優(yōu)缺點(diǎn),比較(摘抄+自己的理解)

為什么需要?jiǎng)討B(tài)代理?

  • 如spring等這樣的框架,要增強(qiáng)具體業(yè)務(wù)的邏輯方法,不可能在框架里面去寫一個(gè)靜態(tài)代理類,只能按照用戶的注解或者xml配置來(lái)動(dòng)態(tài)生成代理類。
  • 業(yè)務(wù)代碼內(nèi),當(dāng)需要增強(qiáng)的業(yè)務(wù)邏輯非常通用(如:添加log,重試,統(tǒng)一權(quán)限判斷等)時(shí),使用動(dòng)態(tài)代理將會(huì)非常簡(jiǎn)單,如果每個(gè)方法增強(qiáng)邏輯不同,那么靜態(tài)代理更加適合。
  • 使用靜態(tài)代理時(shí),如果代理類和被代理類同時(shí)實(shí)現(xiàn)了一個(gè)接口,當(dāng)接口方法有變動(dòng)時(shí),代理類也必須同時(shí)修改。

java字節(jié)碼生成方案(還有好些不怎么流行的框架)

  • asm,底層字節(jié)碼框架,操縱的級(jí)別是底層JVM的匯編指令級(jí)別,這要求ASM使用者要對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。

需要對(duì)字節(jié)碼的結(jié)構(gòu),語(yǔ)法含義等十分了解,所以只貼上一點(diǎn)示例,如果希望通過(guò)asm生成下面這樣一個(gè)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 接口,通過(guò)訪問(wèn)者模式進(jìn)行動(dòng)態(tài)創(chuàng)建class字節(jié)碼,看下面的例子:

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);  
        // 通過(guò)visit方法確定類的頭部信息  
        classWriter.visit(Opcodes.V1_7,// java版本  
                Opcodes.ACC_PUBLIC,// 類修飾符  
                "Programmer", // 類的全限定名  
                null, "java/lang/Object", null);  
          
        //創(chuàng)建構(gòu)造函數(shù)  
        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類已經(jīng)完成  
        // 將classWriter轉(zhuǎn)換成字節(jié)數(shù)組寫到文件里面去  
        byte[] data = classWriter.toByteArray();  
        File file = new File("D://Programmer.class");  
        FileOutputStream fout = new FileOutputStream(file);  
        fout.write(data);  
        fout.close();  
    }  
}  
  • javassist,它是一個(gè)開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù)。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計(jì)算機(jī)科學(xué)系的 Shigeru Chiba (千葉 滋)所創(chuàng)建的。它已加入了開放源代碼JBoss 應(yīng)用服務(wù)器項(xiàng)目,通過(guò)使用Javassist對(duì)字節(jié)碼操作為JBoss實(shí)現(xiàn)動(dòng)態(tài)AOP框架。javassist是jboss的一個(gè)子項(xiàng)目,其主要的優(yōu)點(diǎn),在于簡(jiǎn)單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類的結(jié)構(gòu),或者動(dòng)態(tài)生成類。
    下面通過(guò)Javassist創(chuàng)建上述的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();  
        //創(chuàng)建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);  
        //保存生成的字節(jié)碼  
        cc.writeFile("d://temp");  
    }  
}  
  • cglib,它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。最流行的OR Mapping工具h(yuǎn)ibernate也使用CGLIB來(lái)代理單端single-ended(多對(duì)一和一對(duì)一)關(guān)聯(lián)(對(duì)集合的延遲抓取,是采用其他機(jī)制實(shí)現(xiàn)的)。EasyMock和jMock是通過(guò)使用模仿(moke)對(duì)象來(lái)測(cè)試java代碼的包。它們都通過(guò)使用CGLIB來(lái)為那些沒有接口的類創(chuàng)建模仿(moke)對(duì)象。[現(xiàn)在cglib好像不怎么維護(hù)了,javassist比較火爆]

  • jdk (jdk動(dòng)態(tài)代理根據(jù)interface,生成的代理類會(huì)被緩存,每個(gè)接口只會(huì)生成一個(gè)代理類)

動(dòng)態(tài)代理實(shí)現(xiàn)方案

首先定義一個(gè)接口和一個(gè)類:

public interface CountService {  
  
    int count();  
  
}  

public class CountServiceImpl implements CountService {  
  
    private int count = 0;  
  
    public int count() {  
        return count ++;  
    }  
}  
  • 直接使用asm在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類
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在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類
 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動(dòng)態(tài)代理接口(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動(dòng)態(tài)代理(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);  
        }  
    }  

各種動(dòng)態(tài)代理方案比較

  • 速度比較
//測(cè)試結(jié)果
//創(chuàng)建代理的速度
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生成的字節(jié)碼考慮了太多的條件,所以字節(jié)碼非常多,大多都是用不到的
Run CGLIB Proxy: 1123 ms, 1,255,623 t/s  //較慢,也是因?yàn)樽止?jié)碼稍微多了點(diǎn)
Run JAVAASSIST Proxy: 3212 ms, 438,999 t/s   //非常慢,完全不建議使用javassist的代理類來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理
Run JAVAASSIST Bytecode Proxy: 206 ms, 6,844,977 t/s  //和asm差不多的執(zhí)行速度,因?yàn)樗麄兌际侵簧珊?jiǎn)單純粹的執(zhí)行字節(jié)碼,非常少(所以直接用javassist生成代理類的方式最值得推薦[從速度上講])
Run ASM Bytecode Proxy: 209 ms, 6,746,724 t/s   //asm什么都是最快的,畢竟是只和最底層打交道
  • 便捷性比較

排序(只是生成字節(jié)碼,語(yǔ)義性):javassist>asm
排序(動(dòng)態(tài)代理):cglib=jdk>javassist bytecode>javassist proxy>asm

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容