学会与虚拟机对话---ASM

JVM 对于每个Java 开发者都不会太陌生,但是你真的弄懂了JVM吗?人人都知道要做JVM调优,但是如果能在Java层面直接对话JVM,那将能完成很多你以前无法完成的操作.虽然部分功能通过纯Java的方式也能轻松做到.但中间多出的抽象成本也是非常大的.这样来看,与虚拟机对话似乎是必须的.

想要对其深入了解,JVM相关的知识是必不可少的.推荐圣经---<深入理解Java虚拟机>

ASM

ASM作为JDK与Spring中内置的字节码框架,其速度最快,相应的抽象层更少.但毋庸置疑,学会如何熟练使用还是非常必要的.

ASM的介绍

ASM分为三个主要的功能

  • Genrate(无中生有---生成新的)
  • Tranformation(偷天换日---替换修改)
  • Analysis(细致入微---全局分析)

其中又有三个核心类ClassWrite, ClassReader, ClassVisitor.对于Genrate操作,需要ClassWrite与ClassVisitor同时操作.而Transformation操作则需要三者同时协作.对于Analysis只需要ClassReader介入即可.

Generate

既然是生成新的类,那就不可避免要提他的一个关键类Label,它是实现指令流的关键.可以把JVM的指令想象成一条河中的流水,永远从上游向下游动.JVM的指令并不像Java代码一样丰富.关键的循环, try-catch, if, switch...等一系列的操作全都要用到Label类.

而ASM是观察者模式设计而成的,我们在ClassWrite的实例直接调用其中的visitXxx方法,去生成一个类对应的细节.而在JVM中方法内部的逻辑存在于Code属性表中.当通过cw.visitMethod生成一个目标方法后他就会返回一个MethodVisitor的子类对象,用于生成Code属性表中内部的指令(方法体中的逻辑),通过名为visitXxxInsn的有多个参数的方法(但并非绝对,一是根据指令的属性,是否操作数据来决定的因此可能会出现调用单个参数的visitInsn的情况...)进行方法体的构筑.

Label类

而它的使用其实也非常简单,直接new它的对象即可.再通过MethodVisitor在操作Code表中的指令时通过其中的 visitLabel方法设置锚点.通过visitJumpInsn根据判断指令或跳转指令直接跳转到对应的锚点.以下就是生成for循环的例子.

csharp 复制代码
/**
* 生成的目标类
**/
public class HelloWorld8 {
    public HelloWorld8() {
        super();
    }

    public void forTest() {
        for(int i = 0; i < 10; i++) {
            System.out.print(i);
        }

    }
}
ini 复制代码
/**
 * ASM字节码研究
 * for循环
 */
public class lab08_testDemo08 {

    static void main() {
        String relative_path = "fileDemo/HelloWorld8.class";
        File file = new File(relative_path);

        byte[] bytes = dump();

        new File(file.getParent()).mkdirs();
        try {
            Files.write(file.toPath(), bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] dump() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // 生成类
        cw.visit(
                69,
                ACC_PUBLIC + ACC_SUPER,
                "fileDemo/HelloWorld8",
                null,
                "java/lang/Object",
                null
        );

        {
            MethodVisitor mv1 = cw.visitMethod(
                    ACC_PUBLIC,
                    "<init>",
                    "()V",
                    null,
                    null
            );
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(
                    INVOKESPECIAL,
                    "java/lang/Object",
                    "<init>",
                    "()V",
                    false
            );
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            Label conLabel = new Label();
            Label endLabel = new Label();
            MethodVisitor mv = cw.visitMethod(
                    ACC_PUBLIC,
                    "forTest",
                    "()V",
                    null,
                    null
            );
            mv.visitCode();
            // 准备一个0值
            mv.visitInsn(ICONST_0);
            mv.visitVarInsn(ISTORE, 1);

            // 循环开始标记
            mv.visitLabel(conLabel);
            mv.visitVarInsn(ILOAD, 1);
            mv.visitIntInsn(BIPUSH, 10);
            // 如果i >= 10跳转
            mv.visitJumpInsn(IF_ICMPGE, endLabel);
            mv.visitFieldInsn(
                    GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
            );
            mv.visitVarInsn(ILOAD, 1);
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "print",
                    "(I)V",
                    false
            );
            mv.visitIincInsn(1 , 1);
            // 正常完成一次循环
            mv.visitJumpInsn(GOTO, conLabel);

            mv.visitLabel(endLabel);
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }

        cw.visitEnd();
        return cw.toByteArray();
    }
}

我们可以发现几个全新的的问题,ClassWrite构造方法中的参数是什么?visitMaxs方法的作用?

  • 对于ClassWrite中的参数其存在可选项,其中COMPUTE_FRAMES是最智能的,ASM会帮你自动计算最大栈深,与局部变量表大小.一般选择这个参数即可,其余的参数都不可避免的要进行手动的计算.但在一些特殊的情况,这两个值的计算往往是很复杂的.
  • visitMaxs方法对于每个Code表都是必不可少的,这个方法必须存在.即便你设置了COMPUTE_FRAMES,这个方法也必须调用,但ASM会自动将计算的值进行替换为正确的值,所以可以任意的给值.未开启的话必须计算正确,防止错误.

这里有一个很重要的点,不要陷入思维死结.以后面会提到Transformation做类比.如果说ClassReader是河流的源头,那么ClassWrite就是河流的结束.而一个ClassVisitor并不是一个类的抽象.就像一条大河它不可能一条分支都没有.而ClassVisitor就是如此,它是河流的分支.对于一个类,它可以被多个ClassVisitor访问.每个ClassVisitor只关心自己感兴趣的部分.但它们最终都要汇入主干直到结束.如果有支流没有汇入那就会是Transformation的另一种情况---指令删除.

下面再展示几种Generate的情况

Try-Catch

csharp 复制代码
/**
* try-catch目标类
**/
public class HelloWorld9 {
    public HelloWorld9() {
        super();
    }

    public void forTry() {
        try {
            System.out.println("Before Sleep");
            Thread.sleep(1000L);
            System.out.println("After Sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
java 复制代码
/**
 * ASM字节码研究
 * try-catch
 */
public class lab09_testDemo09 {

    static void main() {
        String relative_path = "fileDemo/HelloWorld9.class";
        File file = new File(relative_path);

        byte[] bytes = dump();

        new File(file.getParent()).mkdirs();
        try {
            Files.write(file.toPath(), bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] dump() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // 生成类
        cw.visit(
                69,
                ACC_PUBLIC + ACC_SUPER,
                "fileDemo/HelloWorld9",
                null,
                "java/lang/Object",
                null
        );

        {
            MethodVisitor mv1 = cw.visitMethod(
                    ACC_PUBLIC,
                    "<init>",
                    "()V",
                    null,
                    null
            );
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(
                    INVOKESPECIAL,
                    "java/lang/Object",
                    "<init>",
                    "()V",
                    false
            );
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            Label startLabel = new Label();
            Label endLabel = new Label();
            Label handleLabel = new Label();
            Label returnLabel = new Label();

            MethodVisitor mv = cw.visitMethod(
                    ACC_PUBLIC,
                    "forTry",
                    "()V",
                    null,
                    null
            );
            mv.visitCode();
            mv.visitTryCatchBlock(startLabel, endLabel, handleLabel, "java/lang/InterruptedException");
            // try部分
            mv.visitLabel(startLabel);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("Before Sleep");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitLdcInsn(new Long(1000L));
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("After Sleep");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitLabel(endLabel);

            // 未捕获异常,直接跳到结束处
            mv.visitJumpInsn(GOTO, returnLabel);

            // 捕获异常
            mv.visitLabel(handleLabel);
            mv.visitVarInsn(ASTORE, 1);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);

            mv.visitLabel(returnLabel);
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }

        cw.visitEnd();
        return cw.toByteArray();
    }
}

Switch

csharp 复制代码
/**
* switch目标类
**/
public class HelloWorld7 {
    public HelloWorld7() {
        super();
    }

    public void choose(int var1) {
        switch (var1) {
            case 1 -> System.out.println("val = 1");
            case 2 -> System.out.println("val = 2");
            default -> System.out.println("val is unknown");
        }

    }
}
ini 复制代码
/**
 * ASM字节码研究
 * switch语句跳转
 */
public class lab07_testDemo07 {

    static void main() {
        String relative_path = "fileDemo/HelloWorld7.class";
        File file = new File(relative_path);

        byte[] bytes = dump();

        new File(file.getParent()).mkdirs();
        try {
            Files.write(file.toPath(), bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] dump() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // 生成类
        cw.visit(
                69,
                ACC_PUBLIC + ACC_SUPER,
                "fileDemo/HelloWorld7",
                null,
                "java/lang/Object",
                null
        );

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            Label label1 = new Label();
            Label label2 = new Label();
            Label defaultLabel = new Label();
            Label returnLabel = new Label();
            MethodVisitor mv = cw.visitMethod(
                    ACC_PUBLIC,
                    "choose",
                    "(I)V",
                    null,
                    null
            );
            mv.visitCode();
            mv.visitVarInsn(ILOAD, 1);
            mv.visitTableSwitchInsn(
                    1,
                    2,
                    defaultLabel,
                    new Label[]{label1, label2}
            );
            mv.visitLabel(label1);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("val = 1");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitJumpInsn(GOTO, returnLabel);

            mv.visitLabel(label2);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("val = 2");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitJumpInsn(GOTO, returnLabel);

            mv.visitLabel(defaultLabel);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("val is unknown");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            mv.visitLabel(returnLabel);
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }
        cw.visitEnd();
        return cw.toByteArray();
    }
}

If

csharp 复制代码
/**
* if语句
**/
public class HelloWorld6 {
    public HelloWorld6() {
        super();
    }

    public void check(int var1) {
        if (var1 == 0) {
            System.out.println("value is 0");
        } else {
            System.out.println("value is not 0");
        }

    }
}
java 复制代码
/**
 * ASM字节码研究
 * if语句跳转
 */
public class lab06_testDemo06 {

    static void main() {
        String relative_path = "fileDemo/HelloWorld6.class";
        File file = new File(relative_path);

        byte[] bytes = dump();

        new File(file.getParent()).mkdirs();
        try {
            Files.write(file.toPath(), bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] dump() {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // 生成类
        cw.visit(
                69,
                ACC_PUBLIC + ACC_SUPER,
                "fileDemo/HelloWorld6",
                null,
                "java/lang/Object",
                null
        );

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            Label elseLabel = new Label();
            Label returnLabel = new Label();
            // 生成方法
            MethodVisitor mv = cw.visitMethod(
                    ACC_PUBLIC,
                    "check",
                    "(I)V",
                    null,
                    null
            );
            mv.visitCode();
            mv.visitVarInsn(ILOAD, 1);
            // 不等于0就进行跳转
            mv.visitJumpInsn(IFNE, elseLabel);
            mv.visitFieldInsn(
                    GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
            );
            mv.visitLdcInsn("value is 0");
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
            );
            // 执行完第一个分支
            mv.visitJumpInsn(GOTO, returnLabel);

            // 第二分支起始处
            mv.visitLabel(elseLabel);
            mv.visitFieldInsn(
                    GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
            );
            mv.visitLdcInsn("value is not 0");
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
            );
            // 第一分支执行完成跳转处
            mv.visitLabel(returnLabel);
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }
        cw.visitEnd();
        return cw.toByteArray();
    }
}

补充一个点,当没有显式的声明构造方法时,JVM会默认帮你添加一个无参的构造方法.但通过ASM的Generate生成的类必须显示的手动添加构造方法.JVM不会帮你生成.因为你用字节码的方式构造一个类,不会通过javac编译器,JVM也就无法得知你是否有构造方法.因为你直接生成了字节码,跳过了编译.

第二个就是在方法体中用{ }代码块的意义仅仅是限制命名,这样mv就可以一直使用,而不是mv1, mv2...此处可用可不用.

第三点,对于每个访问者,其本质上就是流.使用结束后务必记得使用visitEnd方法进行流的关闭.


Transformation

在进入Transformation前,我们应该先弄懂整个观察者模式的调用流程.其实只要掌握了调用栈,整个框架的层次,原理学起来都会事半功倍.

以下有几个类.

  • lab类---主类,连接ClassVisitor, ClassReader, ClassWrite的桥梁
  • LogVisitor---日志访问者增强类
  • TimeVisitor---方法耗时访问者增强类
  • Target---被增强目标类
ini 复制代码
/**
 * 对Target.class进行字节码增强,添加日志和时间记录功能
 */
public class lab {

    static void main() throws IOException {
        String relative_path = "fileDemo1/Target.class";
        File file = new File(relative_path);
        byte[] bytes = dump();

        new File(file.getParent()).mkdirs();
        try {
            Files.write(file.toPath(), bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 对Target.class进行字节码增强,添加日志和时间记录功能
     * @return 增强后的字节码
     * @throws IOException
     */
    public static byte[] dump() throws IOException {
        ClassReader cr = new ClassReader("ASM_Lab.Transformation.demo5.Target");
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        int api = Opcodes.ASM9;
        // 关联LogVisitor和TimeVisitor
        ClassVisitor lv = new LogVisitor(api, cw);
        TimeVisitor tv = new TimeVisitor(api, lv);

        int op = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(tv, op);

        return cw.toByteArray();
    }
}
java 复制代码
/**
 * 日志访问者
 */
public class LogVisitor extends ClassVisitor {

    protected LogVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        // 修改类名为 fileDemo1/Target
        super.visit(version, access, "fileDemo1/Target", signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                mv = new LogMethodVisitorAdapter(api, mv);
            }
        }
        return mv;
    }

    public static class LogMethodVisitorAdapter extends MethodVisitor {

        protected LogMethodVisitorAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitCode() {
            mv.visitFieldInsn(
                    GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
            );
            mv.visitLdcInsn("store log");
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
            );
            super.visitCode();
        }
    }
}
java 复制代码
/**
* TimeVisitor-方法计时访问者增强类
**/
public class TimeVisitor extends ClassVisitor {

    protected TimeVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                mv = new TimeMethodVisitorAdapter(api, mv, 0);
            }
        }
        return mv;
    }

    public static class TimeMethodVisitorAdapter extends MethodVisitor {
        private final int startTimeVarIndex;

        protected TimeMethodVisitorAdapter(int api, MethodVisitor methodVisitor, int startVarIndex) {
            super(api, methodVisitor);
            this.startTimeVarIndex = startVarIndex;
        }

        @Override
        public void visitCode() {
            // 记录开始时间
            mv.visitMethodInsn(
                    INVOKESTATIC,
                    "java/time/LocalDateTime",
                    "now",
                    "()Ljava/time/LocalDateTime;",
                    false
            );
            mv.visitVarInsn(ASTORE, startTimeVarIndex);
            super.visitCode();
        }

        @Override
        public void visitInsn(int opcode) {
            if (opcode == RETURN || opcode == ARETURN || opcode == IRETURN ||
                    opcode == LRETURN || opcode == FRETURN || opcode == DRETURN) {

                // 打印耗时
                mv.visitFieldInsn(
                        GETSTATIC,
                        "java/lang/System",
                        "out",
                        "Ljava/io/PrintStream;"
                );

                // 记录结束时间
                mv.visitMethodInsn(
                        INVOKESTATIC,
                        "java/time/LocalDateTime",
                        "now",
                        "()Ljava/time/LocalDateTime;",
                        false
                );

                // 加载开始时间
                mv.visitVarInsn(ALOAD, startTimeVarIndex);

                // 计算时间差
                mv.visitMethodInsn(
                        INVOKESTATIC,
                        "java/time/Duration",
                        "between",
                        "(Ljava/time/temporal/Temporal;Ljava/time/temporal/Temporal;)Ljava/time/Duration;",
                        false
                );

                // 转换为毫秒
                mv.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "java/time/Duration",
                        "toMillis",
                        "()J",
                        false
                );

                // 直接调用println方法,栈顺序为[PrintStream, long]
                mv.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "java/io/PrintStream",
                        "println",
                        "(J)V",
                        false
                );
            }
            super.visitInsn(opcode);
        }

    }
}
csharp 复制代码
/**
* Target增强目标类
**/
public class Target {

    public void test() {
        int a = 2;
        while (a < 100) {
            a *= 2;
        }
        System.out.println(a);
    }
}
csharp 复制代码
/**
* 增强后的Target类
**/
public class Target {
    public Target() {
        super();
    }

    public void test() {
        LocalDateTime var2 = LocalDateTime.now();
        System.out.println("store log");

        int var1;
        for(var1 = 2; var1 < 100; var1 *= 2) {
        }

        System.out.println(var1);
        System.out.println(Duration.between(LocalDateTime.now(), var2).toMillis());
    }
}

为了简洁(其实是懒),我们将统一使用缩写.

这段代码的逻辑很简单,所谓的添加日志也并不复杂仅仅只是打印了一句话.在lab类中我们主要关心其中的dump方法他返回一个字节数组,就是增强完的Target类.方法内部的逻辑就是Transaformation的常规流程.值得注意的就是当存在多个ClassVisitor时,我们通过构造方法进行关联,越往后靠就是越外层的ClassVisitor.最后设置ClassReader的读取属性,通过回调的accept方法开始整个Transformation流程.而链条则类似于责任链模式.

现在我们以accept方法为起点,探索一下观察者模式的调用流程.

这里我们要补充一个点,根据上方的源码我们我知道无论是ClassVisitor, 还是MethodVisitor他的内部都有一个对应的成员持有链中下一个访问者的引用.(指针放置处)

时间 动作 说明
t1 ClassReader调用tv.visitMethod()
t2 tv调用lv.visitMethod() 向下传递
t3 lv调用cw.visitMethod() 到达链尾
t4 cw返回MethodVisitor M1 开始返回
t5 lv包装M1,返回M2
t6 tv包装M2,返回M3
t7 ClassReader获得M3,开始调用其方法
t8 M3.visitCode()调用M2.visitCode() 向下传递
t9 M2.visitCode()调用M1.visitCode() 向下传递,到达链尾
t10 M1.visitCode()写入字节码,返回 ClassWrite写入字节码,开始依次返回
t11 控制权依次返回:M1→M2→M3→ClassReader
t12 下一条指令重复t8-t11流程
t13 依次按类似于上述的流程调用visitEnd()方法收尾

上述的表格已经极尽详细,所有的指令流都遵循上表的调用流程.

实际上,ASM中有两条并行但相关的链

  • ClassVisitor链(处理类结构)
  • MethodVisitor链(处理方法指令)

而已示例代码里的类,他们的方向大抵都是调用链:tv -> lv -> cw,以及返回链:cw -> lv -> tv,无论是cv链还是mv链亦是如此.

js 复制代码
// 此处ClassVisitor代表其对应的MethodVisitor
tv.visitCode()开始
├─ 执行 mv.visitMethodInsn()  // 指令1
│   ├─ 进入 lv.visitMethodInsn()
│   │   ├─ 进入 cw.visitMethodInsn()
│   │   │   └─ 写入字节码
│   │   └─ 返回
│   └─ 返回
├─ 执行 mv.visitVarInsn()     // 指令2
│   ├─ 进入 lv.visitVarInsn()
│   │   ├─ 进入 cw.visitVarInsn()
│   │   │   └─ 写入字节码
│   │   └─ 返回
│   └─ 返回
└─ 执行 super.visitCode()
    └─ ...
js 复制代码
执行栈深度图示:

第1步:开始执行tv.visitCode()
[栈深度1] TimeMethodVisitorAdapter.visitCode() 开始

第2步:执行 mv.visitMethodInsn(...)
[栈深度1] TimeMethodVisitorAdapter.visitCode() 调用 mv.visitMethodInsn(...)
[栈深度2] └→ LogMethodVisitorAdapter.visitMethodInsn(...) 被调用
[栈深度3]    └→ ClassWriter.visitMethodInsn(...) 被调用
[栈深度4]       └→ ClassWriter内部:将INVOKESTATIC指令写入字节数组 ✅
[栈深度3]    ← 返回 LogMethodVisitorAdapter.visitMethodInsn(...)
[栈深度2] ← 返回 TimeMethodVisitorAdapter.visitCode() 继续执行

第3步:执行 mv.visitVarInsn(ASTORE, 1)
[栈深度1] TimeMethodVisitorAdapter.visitCode() 调用 mv.visitVarInsn(...)
[栈深度2] └→ LogMethodVisitorAdapter.visitVarInsn(...) 被调用
[栈深度3]    └→ ClassWriter.visitVarInsn(...) 被调用
[栈深度4]       └→ ClassWriter内部:将ASTORE指令写入字节数组 ✅
[栈深度3]    ← 返回 LogMethodVisitorAdapter.visitVarInsn(...)
[栈深度2] ← 返回 TimeMethodVisitorAdapter.visitCode() 继续执行

第4步:执行 super.visitCode()
[栈深度1] TimeMethodVisitorAdapter.visitCode() 调用 super.visitCode()
[栈深度2] └→ MethodVisitor.visitCode() 被调用(父类)
[栈深度3]    └→ mv.visitCode() 被调用(即LogMethodVisitorAdapter.visitCode())
[栈深度4]       └→ LogMethodVisitorAdapter.visitCode() 开始执行...
           ...(类似的深度调用继续)

结合这两个近乎详细的指令流.理解整个双链的调用过程无疑探囊取物.

理解了大致的流程,现在我们再次回到Transformation,其实大致上就是读取->自定义->重新写入.无论是修改类的内容,添加新的逻辑,删除原有的逻辑.Transformation都能做到.而添加新的内容我们便不再赘述(如上参考代码).主要关注修改 ,与删除 还有状态机.


修改
相关推荐
开源之眼4 小时前
《github star 加星 Taimili.com 艾米莉 》为什么Java里面,Service 层不直接返回 Result 对象?
java·后端·github
Maori3165 小时前
放弃 SDKMAN!在 Garuda Linux + Fish 环境下的优雅 Java 管理指南
java
用户908324602735 小时前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
小王和八蛋5 小时前
DecimalFormat 与 BigDecimal
java·后端
beata6 小时前
Java基础-16:Java内置锁的四种状态及其转换机制详解-从无锁到重量级锁的进化与优化指南
java·后端
IT探险家6 小时前
你的第一个 Java 程序就翻车?HelloWorld 的 8 个隐藏陷阱
java
随风飘的云6 小时前
SpringBoot 的自动配置原理
java
SimonKing6 小时前
觅得又一款轻量级数据库管理工具:GoNavi
java·后端·程序员
Seven977 小时前
BIO详解:解锁阻塞IO的使用方式
java