asm雜耍第一彈,使用asm實現動態代理

asm雜耍第一彈,使用asm實現動態代理

asm:用來操作字節碼的框架,可以對class文件進行CRUD,聽這名字就知道是什麼東西,在寫c代碼的時候來一句ASM裝逼,這個就像java的彙編一樣。

User接口

/**
 * @author authorZhao
 * @date 2019/12/30
 */
public interface User {

    /**
     * 獲取名字
     * @return
     */
    String getName();

    /**
     * 設置名字
     * @param name
     */
    void setName(String name);

    /**
     * 獲取年齡
     * @return
     */
    Integer getAge();

    /**
     * 設施自年齡
     * @param age
     */
    void setAge(Integer age) ;

}

測試代碼

/**
 * @author authorZhao
 * @date 2019/12/30
 */
public class TestUser {
    public static void main(String[] args) {
        try {
            Class uClass = UserImplUtils.getClass("com.qdz.proxy.asm.test1.UserImpl", User.class);
            User user = (User)uClass.getConstructors()[0].newInstance();
            user.setName("渣渣輝");
            user.setAge(385);
            System.out.println("帶噶好,我係"+user.getName()+",我今天"+user.getAge()+"歲");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

User實現類

上文在不採用jdk和從cglib動態代理的情況下生成了user實現類,採用的是asm框架

上代碼

/**
 * @author authorZhao
 * @date 2019/12/30
 */
public class UserImplUtils {

    /**
     * 根據類全名和接口名生成class文件
     * @param className 類全名,注意是用點分隔的,採用idea的
     * @param clazz 接口class對象 採用點分隔
     * @return
     * @throws Exception
     */
    private static byte[] dump(String className,Class clazz) throws Exception {
        String className2 = className.replace(".","/");
        String sourceName = className.substring(className.lastIndexOf(".")+1)+".java";
        //模仿常量池的語法
        String constClassName = "L"+className2+";";//Lcom/qdz/proxy/asm/test1/UserImpl;

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;
        //聲明需要實現的類
        //jdk版本 11,權限,這個不懂,類名,簽名,父類名,接口名
        cw.visit(55, ACC_PUBLIC + ACC_SUPER, className2, null, "java/lang/Object", new String[]{clazz.getName().replace(".","/")});
        //
        cw.visitSource(sourceName, null);

        //設置屬性 name
        // age
        {
            fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
            fv.visitEnd();
        }
        {
            fv = cw.visitField(ACC_PRIVATE, "age", "Ljava/lang/Integer;", null, null);
            fv.visitEnd();
        }
        //方法
        {
            //構造方法,語法固定
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(7, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            //這個this是固定寫法
            mv.visitLocalVariable("this", constClassName, null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        //getName方法
        {
            mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(14, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, className2, "name", "Ljava/lang/String;");
            mv.visitInsn(ARETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", constClassName, null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        //setName方法
        {
            mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(18, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitFieldInsn(PUTFIELD, className2, "name", "Ljava/lang/String;");
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(19, l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this", constClassName, null, l0, l2, 0);
            mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        //getAge方法
        {
            mv = cw.visitMethod(ACC_PUBLIC, "getAge", "()Ljava/lang/Integer;", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(22, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, className2, "age", "Ljava/lang/Integer;");
            mv.visitInsn(ARETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", constClassName, null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        //setAge方法
        {
            mv = cw.visitMethod(ACC_PUBLIC, "setAge", "(Ljava/lang/Integer;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(26, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitFieldInsn(PUTFIELD, className2, "age", "Ljava/lang/Integer;");
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(27, l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this", constClassName, null, l0, l2, 0);
            mv.visitLocalVariable("age", "Ljava/lang/Integer;", null, l0, l2, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    /**
     * 根據類名和接口返回對應的class對象
     * @param className 類全名,注意是用點分隔的,採用idea的
     * @param clazz 接口class對象 採用點分隔
     * @return
     * @throws Exception
     */
    public static Class<?> getClass(String className,Class clazz ) throws Exception {
        byte[] classData = dump(className, clazz);
        return  new MyClassLoader().defineClassForName(className, classData);
    }
}

類加載器

/**
 * 自定義類加載器
 */
public class MyClassLoader extends ClassLoader {
    public MyClassLoader() {
        super(Thread.currentThread().getContextClassLoader());
    }

    /**
     * 吧class數組轉化爲Class對象
     * @param name 類全名
     * @param data class數組
     * @return
     */
    public Class<?> defineClassForName(String name, byte[] data) {
        return this.defineClass(name, data, 0, data.length);
    }

}

說明:

  • 爲什麼要定義接口

    • 爲了編輯器方便,免得反射方法名太麻煩
  • 爲什麼這麼生成

    • 哈哈,我當然不會,先自己實現以下接口然使用asm工具生成代碼
  • 原創文章,轉載請申明

附錄:class文件結構

參考文章,oracle官網

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;//55表示jdk11
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章