ASM 實現RPC客戶端

一般RPC客戶端都是經過動態代理來實現的。出於好奇,看看經過ASM生成客戶端調用類,性能有沒有JDK動態代理更好,想經過實踐驗證一下。java

要想實現RPC的調用,須要作下面幾件事情:git

1:經過interface 實現調用類github

2:經過遍歷interface的各個方法,獲取方法中參數及返回值類型等信息。web

咱們在MyClassVisitor.java文件中來完成這些功能:spring

package com.jarvis.netty.rpc.aop.asm.impl;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.springframework.asm.Type;

import com.jarvis.netty.rpc.core.client.RpcClient;

public class MyClassVisitor extends ClassVisitor implements Opcodes {

    private static final String rpcClientType=Type.getDescriptor(RpcClient.class);

    private static final String rpcClient="rpcClient";

    private String interfaceName;

    private String subClassPath;// 實現類路徑

    private String subClassName;

    public MyClassVisitor(ClassVisitor cv) {
        // Responsechain 的下一個 ClassVisitor,這裏咱們將傳入 ClassWriter,
        // 負責改寫後代碼的輸出
        super(ASM5, cv);
    }

    @Override
    public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
        System.out.println(name);
        this.interfaceName=name.replaceAll("[/]", ".");
        this.subClassPath=name + "$Impl";
        this.subClassName=subClassPath.replaceAll("[/]", ".");
        System.out.println(subClassName);
        super.visit(version, ACC_PUBLIC + ACC_SUPER, subClassPath, signature, "java/lang/Object", new String[]{name});
    }

    public String getSubClassName() {
        return subClassName;
    }

    // 重寫 visitMethod,訪問到 "operation" 方法時,
    // 給出自定義 MethodVisitor,實際改寫方法內容
    @Override
    public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
        System.out.println(name + "--->" + desc);
        Type[] paramTypes=Type.getArgumentTypes(desc);
        int paramLength=0;
        if(null != paramTypes) {
            paramLength=paramTypes.length;
            for(Type t: paramTypes) {
                System.out.println("    type-->" + t);
            }
        }
        int ind=desc.lastIndexOf(')');
        Type returnType=Type.getType(desc.substring(ind + 1));
        System.out.println("returnType=" + returnType);
        MethodVisitor mv=super.visitMethod(ACC_PUBLIC, name, desc, null, null);
        int loadI=1;
        for(Type tp: paramTypes) {
            if(tp.equals(Type.LONG_TYPE) || tp.equals(Type.DOUBLE_TYPE)) {
                loadI++;
            }
            loadI++;
        }

        // Load class name and method name
        mv.visitCode();
        Label l0=new Label();
        mv.visitLabel(l0);
        mv.visitLdcInsn(this.subClassName);
        mv.visitVarInsn(ASTORE, loadI + 1);
        Label l1=new Label();
        mv.visitLabel(l1);
        mv.visitLdcInsn(name);
        mv.visitVarInsn(ASTORE, loadI + 2);
        Label l2=new Label();
        mv.visitLabel(l2);
        mv.visitVarInsn(ALOAD, loadI + 1);
        mv.visitVarInsn(ALOAD, loadI + 2);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, this.subClassPath, "rpcClient", "Lcom/jarvis/netty/rpc/core/client/RpcClient;");
        // Create array with length equal to number of parameters
        if(paramLength > 0) {
            mv.visitIntInsn(BIPUSH, paramLength);
        } else {
            mv.visitInsn(ICONST_2);
        }
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

        // Fill the created array with method parameters
        int i=0;
        int aloadI=1;
        for(Type tp: paramTypes) {
            mv.visitInsn(DUP);
            if(i == 0) {
                mv.visitInsn(ICONST_0);
            } else if(i == 1) {
                mv.visitInsn(ICONST_1);
            } else if(i == 2) {
                mv.visitInsn(ICONST_2);
            } else if(i == 3) {
                mv.visitInsn(ICONST_3);
            } else if(i == 4) {
                mv.visitInsn(ICONST_4);
            } else if(i == 5) {
                mv.visitInsn(ICONST_5);
            } else {
                mv.visitIntInsn(BIPUSH, i);
            }

            if(tp.equals(Type.BOOLEAN_TYPE)) {
                mv.visitVarInsn(ILOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
            } else if(tp.equals(Type.BYTE_TYPE)) {
                mv.visitVarInsn(ILOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
            } else if(tp.equals(Type.CHAR_TYPE)) {
                mv.visitVarInsn(ILOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
            } else if(tp.equals(Type.SHORT_TYPE)) {
                mv.visitVarInsn(ILOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
            } else if(tp.equals(Type.INT_TYPE)) {
                mv.visitVarInsn(ILOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            } else if(tp.equals(Type.LONG_TYPE)) {
                mv.visitVarInsn(LLOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
                aloadI++;
            } else if(tp.equals(Type.FLOAT_TYPE)) {
                mv.visitVarInsn(FLOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
            } else if(tp.equals(Type.DOUBLE_TYPE)) {
                mv.visitVarInsn(DLOAD, aloadI);
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
                aloadI++;
            } else {
                mv.visitVarInsn(ALOAD, aloadI);
            }
            mv.visitInsn(AASTORE);
            i++;
            aloadI++;
        }

        // mv.visitMethodInsn(Opcodes.INVOKESTATIC, "callbackpackage/CallBack", "callbackfunc",
        // "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V");
        mv.visitMethodInsn(INVOKESTATIC, "com/jarvis/netty/rpc/core/client/MessageSendProxy", "request",
            "(Ljava/lang/String;Ljava/lang/String;Lcom/jarvis/netty/rpc/core/client/RpcClient;[Ljava/lang/Object;)Ljava/lang/Object;", false);
        // mv.visitInsn(POP);
        if(returnType.equals(Type.BOOLEAN_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
            mv.visitInsn(IRETURN);
        } else if(returnType.equals(Type.BYTE_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
            mv.visitInsn(IRETURN);
        } else if(returnType.equals(Type.CHAR_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
            mv.visitInsn(IRETURN);
        } else if(returnType.equals(Type.SHORT_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
            mv.visitInsn(IRETURN);
        } else if(returnType.equals(Type.INT_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
            mv.visitInsn(IRETURN);
        } else if(returnType.equals(Type.LONG_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
            mv.visitInsn(LRETURN);
        } else if(returnType.equals(Type.FLOAT_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
            mv.visitInsn(FRETURN);
        } else if(returnType.equals(Type.DOUBLE_TYPE)) {
            mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
            mv.visitInsn(DRETURN);
        } else if(returnType.equals(Type.VOID_TYPE)) {
            mv.visitInsn(RETURN);
        } else {
            mv.visitTypeInsn(CHECKCAST, returnType.getInternalName());
            mv.visitInsn(ARETURN);
        }
        mv.visitMaxs(4, 3);
        mv.visitEnd();
        return mv;
    }

    @Override
    public void visitEnd() {
        // 添加
        FieldVisitor fv=super.visitField(ACC_PRIVATE + ACC_FINAL, rpcClient, rpcClientType, null, null);
        fv.visitEnd();
        // 構造方法
        MethodVisitor mv=super.visitMethod(ACC_PUBLIC, "<init>", "(" + rpcClientType + ")V", null, null);
        mv.visitCode();
        Label l0=new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        Label l1=new Label();
        mv.visitLabel(l1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitFieldInsn(PUTFIELD, subClassPath, rpcClient, rpcClientType);
        Label l2=new Label();
        mv.visitLabel(l2);
        mv.visitInsn(RETURN);
        Label l3=new Label();
        mv.visitLabel(l3);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

}

調用RPC的簡單實現:dom

public static Object request(String className, String methodName, RpcClient rpcClient, Object... args) {
        try {
            MessageRequest request=new MessageRequest();
            request.setMessageId(UUID.randomUUID().toString());
            request.setClassName(className);
            request.setMethodName(methodName);
            // request.setTypeParameters(method.getParameterTypes());
            request.setParameters(args);

            MessageSendHandler handler=rpcClient.getMessageSendHandler();
            MessageCallBack callBack=handler.sendRequest(request);
            return callBack.get();
        } catch(Throwable e) {
            throw new RuntimeException(e);
        }
    }

生成調用類:ide

public static <T> T generate(Class<T> clazz) throws Exception {
            if(!clazz.isInterface()) {
                throw new IllegalAccessException("必須傳入interface");
            }
            ClassReader cr=new ClassReader(clazz.getName());
            ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS);
            MyClassVisitor classAdapter=new MyClassVisitor(cw);
            cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
            String className=classAdapter.getSubClassName();
            /*
             * 寫入文件
             */
            byte[] code=cw.toByteArray();
            Class<?> tmp=classLoader.defineClassFromClassFile(className, code);
            return (T)tmp.getConstructor(RpcClient.class).newInstance(new RpcClient("localhost:8801", RpcSerializeType.HESSIAN));
    }

詳細代碼實現,能夠參考:https://github.com/qiujiayu/netty-rpc-demo 中的com.jarvis.netty.rpc.aop.asm.impl.ImplGenerator性能

參考資料:this

深刻字節碼 -- 使用 ASM 實現 AOPspa

深刻字節碼 -- 玩轉 ASM-Bytecode

深刻字節碼 -- ASM 關鍵接口 ClassVisitor

深刻字節碼 -- ASM 關鍵接口 MethodVisitor

AOP 的利器:ASM 3.0 介紹

get function arguments values using java asm for bytecode instrimentation

相關文章
相關標籤/搜索