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

46 阅读31分钟

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

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

本文部分代码参考了我学的一位名叫"刘森"的博主的博客,写的非常好,欢迎大家去参考

Java ASM系列 | lsieun

ASM-core API

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.junjie</groupId>
    <artifactId>jvm-lab-jie</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>25</maven.compiler.source>
        <maven.compiler.target>25</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>9.8</version>
        </dependency>

        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-util</artifactId>
            <version>4.1</version>
        </dependency>

        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm-commons</artifactId>
            <version>7.2</version>
        </dependency>
    </dependencies>

</project>

ASM的介绍

ASM分为三个主要的功能

  • Generate(无中生有---生成新的)
  • 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循环的例子.

/**
* 生成的目标类
**/
public class HelloWorld8 {
    public HelloWorld8() {
        super();
    }

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

    }
}
/**
 * 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

/**
* 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();
        }

    }
}
/**
 * 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

/**
* 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");
        }

    }
}
/**
 * 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

/**
* 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");
        }

    }
}
/**
 * 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---被增强目标类
/**
 * 对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();
    }
}
/**
 * 日志访问者
 */
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();
        }
    }
}
/**
* 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);
        }

    }
}
/**
* Target增强目标类
**/
public class Target {

    public void test() {
        int a = 2;
        while (a < 100) {
            a *= 2;
        }
        System.out.println(a);
    }
}
/**
* 增强后的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方法为起点,探索一下观察者模式的调用流程.

image.png image.png

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

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

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

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

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

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

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

第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都能做到.而添加新的内容我们便不再赘述(如上参考代码).主要关注修改,替换删除还有状态机.


修改
/**
* 增强后目标类
**/
public class HelloWorld1 {
    public HelloWorld1() {
        super();
    }

    public void test() {
        System.out.println("ASM!!!!!!");
        System.out.println("hello world!");
        System.out.println("NB!!!!!!!");
    }
}
/**
 * 原始目标类
 */
public class HelloWorld {
    public void test() {
        System.out.println("hello world!");
    }
}
/**
 * ASM字节码研究
 * ClassReader - 修改已有的方法
 */
public class lab01 {
    public static void main(String[] args) throws IOException {
        String relative_path = "fileDemo1/HelloWorld1.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() throws IOException {
        ClassReader cr = new ClassReader("ASM_Lab.Transformation.demo1.HelloWorld");
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        int api = Opcodes.ASM9;
        ClassVisitor cv = new MyClassVisitor(api, cw);

        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        return cw.toByteArray();
    }
}
/**
 * 自定义的ClassVisitor,继承于ClassVisitor,旨在拓展特定的类的功能
 */
public class MyClassVisitor extends ClassVisitor {

    public MyClassVisitor(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)) {
            mv = new MethodAdapter(api, mv);
        }
        return mv;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, "fileDemo1/HelloWorld1", signature, superName, interfaces);
    }

    /**
     * 拓展的细节,具体的拓展的逻辑,重写原有的MethodVisitor中的方法
     */
    public static class MethodAdapter extends MethodVisitor {

        public MethodAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitCode() {
            // 增强原有方法
            super.visitFieldInsn(
                    GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
            );
            super.visitLdcInsn("ASM!!!!!!");
            super.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
            );
            super.visitCode();
        }

        @Override
        public void visitInsn(int opcode) {
            if (opcode == Opcodes.ATHROW || (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                this.visitFieldInsn(
                        GETSTATIC,
                        "java/lang/System",
                        "out",
                        "Ljava/io/PrintStream;"
                );
                this.visitLdcInsn("NB!!!!!!!");
                this.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "java/io/PrintStream",
                        "println",
                        "(Ljava/lang/String;)V",
                        false
                );
            }
            super.visitInsn(opcode);
        }
    }
}

Transformation的核心流程大致为

  1. 创建新的自定的XxxVisitor类,继承自ClassVisitor.
  2. 按需添加字段,重写visitMethod方法添加路由逻辑,确保目标逻辑能访问到自身增强的XxxVisitor类,原逻辑不受影响.
  3. 为自定的XxxVisitor类编写它对用的XxxMethodAdapter.选择合适的指令方法增强对应的逻辑.(此处需要注意一点,在目标增强的逻辑只能写在super.visitXxxInsn方法之前.特殊情况分开考虑.更多时候写在之后的或会是原方法先执行,如果原方法RETURN了,那么我们增强的逻辑就丢失了永远不会执行.)
  4. 通过ClassReader的accept方法关联三者

替换

有了核心流程后,对于替换我们也可以采用类似的方式.

/**
 * 原始目标类
 */
public class Update {
    public void test(int a, int b) {
        int c = Math.max(a, b);
        System.out.println(c);
    }
}
/**
 * 增强原始目标类
 */
public class Update {
    public Update() {
        super();
    }

    public void test(int var1, int var2) {
        int var3 = Math.min(var1, var2);  //将max方法替换为min方法
        System.out.println(var3);
    }
}
/**
 * 对Update.class进行字节码增强,将Math.max替换为Math.min
 */
public class lab03 {
    public static void main(String[] args) throws IOException {
        String relative_path = "fileDemo1/Update.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() throws IOException {
        ClassReader cr = new ClassReader("ASM_Lab.Transformation.Demo3.Update");
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        int api = Opcodes.ASM9;
        ClassVisitor cv = new ClassVisitorAdapter(api, cw,
                "java/lang/Math", "max", "(II)I",
                Opcodes.INVOKESTATIC, "java/lang/Math", "min", "(II)I");

        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        return cw.toByteArray();
    }
}
/**
 * 替换方法的调用
 */
public class ClassVisitorAdapter extends ClassVisitor {

    private final String oldOwner;
    private final String oldMethodName;
    private final String oldMethodDesc;

    private final int newOpcode;
    private final String newOwner;
    private final String newMethodName;
    private final String newMethodDesc;

    protected ClassVisitorAdapter(int api, ClassVisitor classVisitor,
                                  String oldOwner, String oldMethodName, String oldMethodDesc,
                                  int newOpcode, String newOwner, String newMethodName, String newMethodDesc) {
        super(api, classVisitor);
        this.oldOwner = oldOwner;
        this.oldMethodName = oldMethodName;
        this.oldMethodDesc = oldMethodDesc;

        this.newOpcode = newOpcode;
        this.newOwner = newOwner;
        this.newMethodName = newMethodName;
        this.newMethodDesc = newMethodDesc;
    }

    @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 MethodReplaceInvokeAdapter(api, mv);
            }
        }
        return mv;
    }

    private class MethodReplaceInvokeAdapter extends MethodVisitor {
        public MethodReplaceInvokeAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            if (oldOwner.equals(owner) && oldMethodName.equals(name) && oldMethodDesc.equals(descriptor)) {
                super.visitMethodInsn(newOpcode, newOwner, newMethodName, newMethodDesc, false);
            }
            else {
                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
            }
        }
    }
}

对于Transformation的替换,有两种

  • 等代价替换
  • 不等价替换

无论是哪种都必须保证栈平衡逻辑正确,上方展示的是等价替换.非等价等熟练后大家可以尝试.


删除

删除与替换类似,都需要保证前后一致的栈平衡,对于复杂的删除我们暂时按下不表.先介绍一个简单的删除.因为较为简单只摘取核心代码逻辑.

移除无操作数的指令NOP

/**
* 移除无操作数的指令NOP
**/
public class NopVisitor extends ClassVisitor {
    public NopVisitor(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 NopAdapter(api, mv);
            }

        }
        return mv;
    }

    private static class NopAdapter extends MethodVisitor {
        public NopAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitInsn(int opcode) {
        // 两种方式都可以实现,选取其一即可
        // 两种方式都是类似于阻碍指令传递到下一个mv.
            // if (opcode == NOP) {
            //     // do nothing
            // }
            // else {
            //     super.visitInsn(opcode);
            // }
            if (opcode != NOP) {
                super.visitInsn(opcode);
            }
        }
    }

状态机

正常我们在写代码的时候可能会遇到很多无效的代码,尽管很多时候JVM和自身的思考能避免到很多不必要的逻辑.但本着学习的目的我们也需要对其有足够的了解.

首先我们先了解什么是状态机,当然如果大家有尝试过欢乐斗地主就一定知道-记牌器.而我们所说的状态机就类似于记牌器.

这次我们先举一个小例子

/**
 * 原始目标类
 */
public class Stateful {
    public void test(int a, int b) {
        int c = a + b;
        int d = c + 0;
        System.out.println(d);
    }
}
/**
 * 增强原始目标类
 */
public class Stateful {
    public Stateful() {
        super();
    }

    public void test(int var1, int var2) {
        int var3 = var1 + var2;
        System.out.println(var3);
    }
}

/**
 * 组合复杂字节码变化
 */
public class lab04 {
     static void main() throws IOException {
        String relative_path = "fileDemo1/Stateful.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() throws IOException {
        ClassReader cr = new ClassReader("ASM_Lab.Transformation.Demo4.Stateful");
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        int api = Opcodes.ASM9;
        ClassVisitor cv = new StatefulClassVisitor(api, cw);

        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        return cw.toByteArray();
    }
}

/**
* 状态机类
**/
public abstract class MethodPatternAdapter extends MethodVisitor {
    protected final static int SEEN_NOTHING = 0;
    protected int state;

    public MethodPatternAdapter(int api, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
    }

    @Override
    public void visitInsn(int opcode) {
        visitInsn();
        super.visitInsn(opcode);
    }

    @Override
    public void visitIntInsn(int opcode, int operand) {
        visitInsn();
        super.visitIntInsn(opcode, operand);
    }

    @Override
    public void visitVarInsn(int opcode, int var) {
        visitInsn();
        super.visitVarInsn(opcode, var);
    }

    @Override
    public void visitTypeInsn(int opcode, String type) {
        visitInsn();
        super.visitTypeInsn(opcode, type);
    }

    @Override
    public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
        visitInsn();
        super.visitFieldInsn(opcode, owner, name, descriptor);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor) {
        visitInsn();
        super.visitMethodInsn(opcode, owner, name, descriptor);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        visitInsn();
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }

    @Override
    public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
        visitInsn();
        super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
    }

    @Override
    public void visitJumpInsn(int opcode, Label label) {
        visitInsn();
        super.visitJumpInsn(opcode, label);
    }

    @Override
    public void visitLdcInsn(Object value) {
        visitInsn();
        super.visitLdcInsn(value);
    }

    @Override
    public void visitIincInsn(int var, int increment) {
        visitInsn();
        super.visitIincInsn(var, increment);
    }

    @Override
    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
        visitInsn();
        super.visitTableSwitchInsn(min, max, dflt, labels);
    }

    @Override
    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
        visitInsn();
        super.visitLookupSwitchInsn(dflt, keys, labels);
    }

    @Override
    public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
        visitInsn();
        super.visitMultiANewArrayInsn(descriptor, numDimensions);
    }

    @Override
    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
        visitInsn();
        super.visitTryCatchBlock(start, end, handler, type);
    }

    @Override
    public void visitLabel(Label label) {
        visitInsn();
        super.visitLabel(label);
    }

    @Override
    public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
        visitInsn();
        super.visitFrame(type, numLocal, local, numStack, stack);
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        visitInsn();
        super.visitMaxs(maxStack, maxLocals);
    }

    protected abstract void visitInsn();
}
/**
* 状态机访问者
**/
public class StatefulClassVisitor extends ClassVisitor {

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

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = cv.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 MethodStatefulAdapter(api, mv);
            }
        }
        return mv;
    }

    /**
     * 继承,具体逻辑实现类
     */
    private static class MethodStatefulAdapter extends MethodPatternAdapter  {
        // 发现ICONST指令
        private static final int SEEN_ICONST = 1;

        public MethodStatefulAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitInsn(int opcode) {
            switch (state) {
                // 如果是iconst指令
                case SEEN_ICONST :
                    if (opcode == IADD) {
                        state = SEEN_NOTHING;
                        return;
                    }
                    else if (opcode == ICONST_0) {
                        mv.visitInsn(ICONST_0);
                        return;
                    }
                    break;
                case SEEN_NOTHING:
                    if (opcode == ICONST_0) {
                        state = SEEN_ICONST;
                        return;
                    }
                    break;
            }
            super.visitInsn(opcode);
        }

        @Override
        protected void visitInsn() {
            if (state == SEEN_ICONST) {
                mv.visitInsn(ICONST_0);
            }
            state = SEEN_NOTHING;
        }
    }
}

此次我们需要匹配的指令模式为MethodPatternAdapter即状态机类,它的职责就是记录某种状态,并进行方法路由.它重写了父类所有的方法并且在super.xisitXxx之前执行模版方法.保证在向下传递通过模版方法visitInsn()进入自定义增强逻辑之前还可以给自身的子类一个机会进行指令的额外处理,SEEN_NOTHING即代表当前状态机所处的状态---未发现指令.

在MethodPatternAdapter中,内部有State变量.它存在两个状态,警惕(SEEN_ICONST:1),空闲(SEEN_NOTHING:0).当它发现存在ICONST_0指令时就会进入警戒状态.

而在状态机增强类,具体逻辑大致如下:

  • 如果发现0,则警戒.看下一条指令是否为+.如是直接返回,这两条指令都不向下传递,清理状态机状态.如果不是将上一条指令复原向下传递,清理状态机状态.
  • 如果未警戒直接向下传递.

工具类

在 asm-util.jar 里,它提供的是通用性的功能,没有特别明确的应用场景;而在 asm-commons.jar 里,它提供的功能,都是为解决某一种特定场景中出现的问题而提出的解决思路.


ams-util
CheckClassAdapter

CheckClassAdapter 类,主要负责检查(Check)生成的 .class 文件内容是否正确. 主要有两种使用方式:

  • 在生成类或转换类的过程中进行检查
  • 在生成类或转换类的结束后进行检查

ASM命名规则,继承自ClassVisitor的命名为XxxVisitor.继承自MethodVisitor,命名为XxxAdapter.而CheckClassAdapter是唯一的例外,他的父类是ClassVisitor.

转换过程中检查

/**
 * 转换过程中检查
 */
public class lab01 {
    public static void main(String[] args) throws IOException {
        String relative_path = "fileDemo2/HelloWorld.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() throws IOException {
        ClassReader cr = new ClassReader("ASM_Lab.TestUtil.CheckClassAdapter.HelloWorld");
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        int api = Opcodes.ASM9;
        CheckClassAdapter cca = new CheckClassAdapter(cw);
        BeforeVisitor bv = new BeforeVisitor(api, cca);

        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(bv, parsingOptions);

        return cw.toByteArray();
    }
}
/**
 * 简易的前置增强
 */
public class BeforeVisitor extends ClassVisitor {

    protected BeforeVisitor(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)) {
            mv = new BeforeAdapter(api, mv);
        }
        return mv;
    }

    public static class BeforeAdapter extends MethodVisitor {

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

        @Override
        public void visitCode() {

            super.visitCode();

            // 此处往下的代码如果放在super.visitCode()之前,则会被CheckClassAdapter检查到异常
            // 因为CheckClassAdapter会检查是否有未被访问的指令
            // 而visitFieldInsn()和visitLdcInsn()都是访问指令,所以会被检查到异常
            // 框架级事件通知放在super.visitCode()之后
            // 调用指令级无需额外考虑,如visitMethodInsn()
            mv.visitFieldInsn(
                    Opcodes.GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
            );
            mv.visitLdcInsn("Before ...");
            mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
            );

        }
    }
}
/**
 * 原始目标类
 */
public class HelloWorld {

    public void test() {
        System.out.println("Hello World!");
    }
}
/**
 * 增强后原始目标类
 */
public class HelloWorld {
    public HelloWorld() {
        super();
    }

    public void test() {
        System.out.println("Before ...");
        System.out.println("Hello World!");
    }
}

转换结束后检查

/**
 * 转换结束后检查
 */
public class lab02 {
    public static void main(String[] args) throws IOException {
        String relative_path = "fileDemo2/HelloWorld2.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();
        }

        PrintWriter printWriter = new PrintWriter(System.out);
        CheckClassAdapter.verify(new ClassReader(bytes), true, printWriter);
    }

    public static byte[] dump() throws IOException {
        // (1) 创建 ClassWriter 对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用 visitXxx() 方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "fileDemo2/HelloWorld2",
                null, "java/lang/Object", null);

        {
            FieldVisitor fv = cw.visitField(ACC_PRIVATE, "intValue", "I", null, null);
            fv.visitEnd();
        }

        {
            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(1, 1);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("Hello World");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 1);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用 toByteArray() 方法
        return cw.toByteArray();
    }
}

TraceClassVisitor

TraceClassVisitor类,主要负责将 .class 文件的内容打印成文字输出。根据输出的文字信息,可以探索或追踪(Trace).class 文件的内部信息.它存在两个非常重要的字段.一个是 PrintWriter printWriter 用于打印;另一个是 Printer p 将 class 转换成文字信息.

public final class TraceClassVisitor extends ClassVisitor {
    private final PrintWriter printWriter; // 打印类信息
    public final Printer p; // 信息采集器
}

构造方法如下(P-Printer, PW-PrintWriter, CV-ClassVisitor)

(PW)
(CV, PW)
(CV, P, PW)

使用TraceClassVisitor类

很重要的一点就是选择 Printer 类的具体实现,可以选择 ASMifier 类,也可以选择 Textifier 类(默认).

正常情况下跟CheckClassAdapter使用方式类似,只是需要根据其构造方法提供对应的感兴趣的输出形式.

public class TraceClassVisitorExample01Generate {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成 byte[] 内容
        byte[] bytes = dump();

        // (2) 保存 byte[] 到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建 ClassWriter 对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        PrintWriter printWriter = new PrintWriter(System.out);
        TraceClassVisitor cv = new TraceClassVisitor(cw, printWriter);

        // (2) 调用 visitXxx() 方法
        cv.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);

        {
            FieldVisitor fv1 = cv.visitField(ACC_PRIVATE, "intValue", "I", null, null);
            fv1.visitEnd();
        }

        {
            FieldVisitor fv2 = cv.visitField(ACC_PRIVATE, "strValue", "Ljava/lang/String;", null, null);
            fv2.visitEnd();
        }

        {
            MethodVisitor mv1 = cv.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();
        }

        {
            MethodVisitor mv2 = cv.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("Hello World");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 1);
            mv2.visitEnd();
        }

        cv.visitEnd();

        // (3) 调用 toByteArray() 方法
        return cw.toByteArray();
    }
}

且其使用方式也遵循即插即用的原则,需要使用时直接包装需增强的目标即可.

使用 TraceMethodVisitor 类

对于该类我们先不做过多赘述,等TreeAPI后就知道如何使用了,直接看一个例子.

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes = FileUtils.readBytes(filepath);

        //(1)构建 ClassReader
        ClassReader cr = new ClassReader(bytes);

        //(2)生成 ClassNode
        int api = Opcodes.ASM9;
        ClassNode cn = new ClassNode();

        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cn, parsingOptions);

        //(3)找到某个具体的方法
        List<MethodNode> methods = cn.methods;
        MethodNode mn = methods.get(1);

        //(4)打印输出
        Textifier printer = new Textifier();
        TraceMethodVisitor tmv = new TraceMethodVisitor(printer);

        InsnList instructions = mn.instructions;
        for (AbstractInsnNode node : instructions) {
            node.accept(tmv);
        }
        List<Object> list = printer.text;
        printList(list);
    }

    private static void printList(List<?> list) {
        PrintWriter writer = new PrintWriter(System.out);
        printList(writer, list);
        writer.flush();
    }

    // 下面这段代码来自 org.objectweb.asm.util.Printer.printList() 方法
    private static void printList(final PrintWriter printWriter, final List<?> list) {
        for (Object o : list) {
            if (o instanceof List) {
                printList(printWriter, (List<?>) o);
            }
            else {
                printWriter.print(o);
            }
        }
    }
}

由于篇幅问题,对于asm-commons此处就不过多展示.


ASM-Tree API

它分为两个核心包asm-tree.jarasm-analysis.jar

 asm-tree.jar

asm-tree.jar(9.0)当中,一共包含了36个类,我们会提及其中的20个类,这20个类就构成asm-tree.jar主体部分.

他们之间的关系如下

┌─── FieldNode
             │
             │
             │                                                                     ┌─── FieldInsnNode
             │                                                                     │
             │                                                                     ├─── IincInsnNode
             │                                                                     │
             │                                                                     ├─── InsnNode
             │                                                                     │
             │                                                                     ├─── IntInsnNode
             │                                                                     │
             │                                                                     ├─── InvokeDynamicInsnNode
ClassNode ───┤                                                                     │
             │                                                                     ├─── JumpInsnNode
             │                                                                     │
             │                                                                     ├─── LabelNode
             │                  ┌─── InsnList ────────────┼─── AbstractInsnNode ───┤
             │                  │                                                  ├─── LdcInsnNode
             │                  │                                                  │
             │                  │                                                  ├─── LookupSwitchInsnNode
             │                  │                                                  │
             │                  │                                                  ├─── MethodInsnNode
             │                  │                                                  │
             │                  │                                                  ├─── MultiANewArrayInsnNode
             └─── MethodNode ───┤                                                  │
                                │                                                  ├─── TableSwitchInsnNode
                                │                                                  │
                                │                                                  ├─── TypeInsnNode
                                │                                                  │
                                │                                                  └─── VarInsnNode
                                │
                                │
                                └─── TryCatchBlockNode

具体关系如下

  • 类(ClassNode)包含字段(FieldNode)和方法(MethodNode
  • 方法(MethodNode)包含有序的指令集合(InsnList)和异常处理(TryCatchBlockNode
  • 有序的指令集合(InsnList)由多个单条指令(AbstractInsnNode)组合而成

asm-analysis.jar

asm-analysis.jar(9.0版本)当中,一共包含了13个类,我们会涉及到其中的10个类.

他们的关系也如下所示

-   Analyzer
    --   Frame
    --   Interpreter + Value
        ---   BasicInterpreter + BasicValue
        ---   BasicVerifier + BasicValue
        ---   SimpleVerifier + BasicValue
        ---   SourceInterpreter + SourceValue
-   Tree API的优势:
    -   易用性:如果一个人在之前并没有接触过Core APITree API,那么Tree API更容易入手
    -   功能性:在实现比较复杂的功能时,Tree APICore API更容易实现

-   Core API的优势:
    -   执行效率:在实现相同功能的前提下,Core API要比Tree API执行效率高,花费时间少
    -   内存使用:Core APITree API占用的内存空间少

Generate

相较于ClassVisitor,ClassNode的类生成更加简单

ClassNode

它是ClassVisitor的子类,其中定义了一些类的相关信息字段. 值得一提的是其中的构造方法.其分成有参和无参的构造方法.

public class ClassNode extends ClassVisitor {
    public ClassNode() {
        this(Opcodes.ASM9);
        if (getClass() != ClassNode.class) {
            throw new IllegalStateException();
        }
    }

    public ClassNode(final int api) {
        super(api);
        this.interfaces = new ArrayList<>();
        this.fields = new ArrayList<>();
        this.methods = new ArrayList<>();
    }
}

无参方法主要用于Generate,因为其在内部检查了字节码是否为自身.

有参方法主要用于Transformation.需指定ASM的版本.

生成类的通式如下

  • 1,创建ClassNode类实例,为其类的字段内容进行赋值,这是收集数据的过程
    • 首先,设置类层面的信息,包括类名、父类、实现的接口等
    • 接着,设置字段层面的信息
    • 最后,设置方法层面的信息
  • 2,借助于ClassWriter类,将ClassNode对象实例转换成byte[],这是输出结果的过程
public static byte[] dump() throws Exception {
    // (1) 使用ClassNode类收集数据
    ClassNode cn = new ClassNode();
    cn.version = V1_8;
    cn.access = ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE;
    cn.name = "sample/HelloWorld";
    cn.superName = "java/lang/Object";
    // ...

    // (2) 使用ClassWriter类生成字节码
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    cn.accept(cw);
    return cw.toByteArray();
}
/**
* 目标接口
**/
public interface HelloWorld {
}
/**
* 生成目标接口
**/
public class HelloWorldGenerateTree {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 使用ClassNode类收集数据
        ClassNode cn = new ClassNode();
        cn.version = V1_8;
        cn.access = ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE;
        cn.name = "sample/HelloWorld";
        cn.superName = "java/lang/Object";

        // (2) 使用ClassWriter类生成字节码
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cn.accept(cw);
        return cw.toByteArray();
    }
}

像剩下的MethodNode,FileNode本质上与coreAPI对应的抽象没有区别,不再赘述. 其实两者共性很多,TreeAPI依旧可以使用coreAPI相同的visitXxx方法进行操作.剩下的各种if, switch, try-catch, for都只是被TreeAPI内化为了独立的方法.不同的是TreeAPI无需通过visitCode进行方法开始的标志.而是存在指令集(InsnList),其中的每一条指令都对应着一个抽象类的子类对象来代表各种虚拟机的操作(AbstractInsnNode).我们还是以单个例子来看,以下是if的情况.

/**
* 预期目标类
**/
public class HelloWorld {
    public void test(int val) {
        if (val == 0) {
            System.out.println("val is 0");
        }
        else {
            System.out.println("val is not 0");
        }
    }
}
/**
* 具体实现
**/
public class HelloWorldGenerateTree {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 使用ClassNode类收集数据
        ClassNode cn = new ClassNode();
        cn.version = V1_8;
        cn.access = ACC_PUBLIC | ACC_SUPER;
        cn.name = "sample/HelloWorld";
        cn.signature = null;
        cn.superName = "java/lang/Object";

        {
            MethodNode mn1 = new MethodNode(ACC_PUBLIC, "<init>", "()V", null, null);
            cn.methods.add(mn1);

            InsnList il = mn1.instructions;
            il.add(new VarInsnNode(ALOAD, 0));
            il.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false));
            il.add(new InsnNode(RETURN));

            mn1.maxStack = 1;
            mn1.maxLocals = 1;
        }

        {
            MethodNode mn2 = new MethodNode(ACC_PUBLIC, "test", "(I)V", null, null);
            cn.methods.add(mn2);

            LabelNode elseLabelNode = new LabelNode();
            LabelNode returnLabelNode = new LabelNode();

            // 第1段
            InsnList il = mn2.instructions;
                il.add(new VarInsnNode(ILOAD, 1));// 括号中的每个new的对象本质上都是AbstractInsnNode的子类,针对不同操作的抽象
            il.add(new JumpInsnNode(IFNE, elseLabelNode));

            // 第2段
            il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
            il.add(new LdcInsnNode("val is 0"));
            il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
            il.add(new JumpInsnNode(GOTO, returnLabelNode));

            // 第3段
            il.add(elseLabelNode);
            il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
            il.add(new LdcInsnNode("val is not 0"));
            il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));

            // 第4段
            il.add(returnLabelNode);
            il.add(new InsnNode(RETURN));

            mn2.maxStack = 2;
            mn2.maxLocals = 2;
        }

        // (2) 使用ClassWriter类生成字节码
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cn.accept(cw);
        return cw.toByteArray();
    }
}

Transformation

对于TreeAPI的Transformation,其实细节与coreAPI趋近大同.又有一个需要关注的就是连续的转换.如core -> Tree.亦或者Tree -> core.

1. 类层面:ClassVisitor和ClassNode

假如有HelloWorld类,内容如下:

public class HelloWorld {
    public int intValue;
}
Copy

我们的预期目标:使用Core API添加一个String strValue字段,使用Tree API添加一个Object objValue字段。

1.1. 先Core API后Tree API

思路:

ClassReader --> ClassVisitor(Core API,添加strValue字段) --> ClassNode(Tree API,添加objValue字段) --> ClassWriter
Copy

代码片段:

int api = Opcodes.ASM9;
ClassNode cn = new ClassAddFieldNode(api, cw, Opcodes.ACC_PUBLIC, "objValue", "Ljava/lang/Object;");
ClassVisitor cv = new ClassAddFieldVisitor(api, cn, Opcodes.ACC_PUBLIC, "strValue", "Ljava/lang/String;");

int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
Copy
1.2. 先Tree API后Core API

思路:

ClassReader --> ClassNode(Tree API,添加objValue字段) --> ClassVisitor(Core API,添加strValue字段) --> ClassWriter
Copy

代码片段:

int api = Opcodes.ASM9;
ClassVisitor cv = new ClassAddFieldVisitor(api, cw, Opcodes.ACC_PUBLIC, "strValue", "Ljava/lang/String;");
ClassNode cn = new ClassAddFieldNode(api, cv, Opcodes.ACC_PUBLIC, "objValue", "Ljava/lang/Object;");

int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cn, parsingOptions);
Copy
2. 方法层面:MethodVisitor和MethodNode
2.1. 先Core API后Tree API

思路:

MethodVisitor(Core API) --> MethodNode(Tree API) --> MethodVisitor(Core API)
Copy

编码实现:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.*;

import static org.objectweb.asm.Opcodes.*;

public class MixCore2TreeVisitor extends ClassVisitor {
    public MixCore2TreeVisitor(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 MethodEnterNode(api, access, name, descriptor, signature, exceptions, mv);
            }
        }
        return mv;
    }

    private static class MethodEnterNode extends MethodNode {
        public MethodEnterNode(int api, int access, String name, String descriptor,
                               String signature, String[] exceptions,
                               MethodVisitor mv) {
            super(api, access, name, descriptor, signature, exceptions);
            this.mv = mv;
        }

        @Override
        public void visitEnd() {
            // 首先,处理自己的代码逻辑
            InsnList il = new InsnList();
            il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
            il.add(new LdcInsnNode("Method Enter"));
            il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
            instructions.insert(il);

            // 其次,调用父类的方法实现(根据实际情况,选择保留,或删除)
            super.visitEnd();

            // 最后,向后续MethodVisitor传递
            if (mv != null) {
                accept(mv);
            }
        }
    }
}
Copy

进行转换:

import lsieun.utils.FileUtils;
import org.objectweb.asm.*;

public class HelloWorldTransformCore {
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new MixCore2TreeVisitor(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}
Copy
2.2. 先Tree API后Core API

思路:

MethodNode(Tree API) --> MethodVisitor(Core API) --> MethodNode(Tree API)
Copy

编码实现:

import lsieun.asm.template.MethodEnteringAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;

public class MixTree2CoreNode extends ClassNode {
    public MixTree2CoreNode(int api, ClassVisitor cv) {
        super(api);
        this.cv = cv;
    }

    @Override
    public void visitEnd() {
        // 首先,处理自己的代码逻辑
        int size = methods.size();
        for (int i = 0; i < size; i++) {
            MethodNode mn = methods.get(i);
            if ("<init>".equals(mn.name) || "<clinit>".equals(mn.name)) {
                continue;
            }
            InsnList instructions = mn.instructions;
            if (instructions.size() == 0) {
                continue;
            }

            int api = Opcodes.ASM9;
            MethodNode newMethodNode = new MethodNode(api, mn.access, mn.name, mn.desc, mn.signature, mn.exceptions.toArray(new String[0]));
            MethodVisitor mv = new MethodEnteringAdapter(api, newMethodNode, mn.access, mn.name, mn.desc);
            mn.accept(mv);
            methods.set(i, newMethodNode);
        }

        // 其次,调用父类的方法实现(根据实际情况,选择保留,或删除)
        super.visitEnd();

        // 最后,向后续ClassVisitor传递
        if (cv != null) {
            accept(cv);
        }
    }
}
Copy

进行转换:

import lsieun.utils.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

public class HelloWorldTransformTree {
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);

        // (1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        // (2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (3)串连ClassNode
        int api = Opcodes.ASM9;
        ClassNode cn = new MixTree2CoreNode(api, cw);

        //(4)结合ClassReader和ClassNode
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cn, parsingOptions);

        // (5) 生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}

关于类的部分其实没有什么好讲的.但是在此处,需要注意一个点.对于ClassWrite而言.他是一个顽固的人.他只认识ClassVisitor.不认识ClassNode(尽管它是ClassVisitor的子类).这是由于TreeAPI与CoreAPI的差异造成.所以,在下方代码传递时,通过accept方法进行了转换.但是这种方式是局部的.存在局限性.但胜在完美契合了我们之前学习的coreAPI的流式思想.而accept方法就是将它们两者转换的桥梁.下方所示为内部转换.还有一种方法就是在外部转换.

 // 最后,向后续ClassVisitor传递,内部转换
        if (cv != null) {
            accept(cv);
        }
image.png

相较于内部转换,外部转换的扩展性更好.


Type类

此类为通用的范式类.类似于Object,是java中Type的子类.代表各种类型.

具体用法即通过Type.XXX方法获取他的Type类型.感兴趣可以看一下我推荐的博客.

最核心的用法就是,通过一个对象的Type对象.getOpcode(XXX)来动态获取执行该类型的对应的指令. 假设我想操作int类型的变量.但是以后程序会变化.可能以后需要操作对象类型的变量.以加载指令为例(LOAD)

我已获取到当前操作字段的Type对象type.我要进行的加载操作.加载int类型就是ILOAD,对象类型就是ALOAD.为了通配这个逻辑.我们就可以通过type.getOpcode(ILOAD)这种方式的调用,他会根据type的类型返回对应的指令.它返回指令的操作类型由调用者和方法参数同时决定.方法参数决定它是哪一类型的指令.调用者决定指令的类型.如我当前的字段是int类型,那么就返回ILOAD,我当前的字段是对象类型,那么他就返回ALOAD.


实战

实现lombok中的Get,Set,Data注解.主要采用CoreAPI.支持字段排除.

注解设计

package com.junjie.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Data {
    String[] exclude() default {};
}
package com.junjie.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Getter {
    String[] exclude() default {};
}
package com.junjie.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Setter {
    String[] exclude() default {};
}

核心逻辑

package com.junjie.impl.annotation.data;

import com.junjie.impl.dto.CollectField;
import org.objectweb.asm.*;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.junjie.util.Constant.*;

/**
 * 作用范围@Data注解,@Getter,@Setter注解
 * 抽象的Data注解访问者,用于给添加对应注解的类添加JavaBean相关方法
 * 对原始的方法进行增强
 */
public abstract class AbstractDataVisitor extends ClassVisitor {

    // 当前所处类的全限定名
    protected String currentClassName;

    // 是否存在@Getter注解
    protected boolean hasGetter;

    // 是否存在@Setter注解
    protected boolean hasSetter;

    // 是否检查注解内属性
    private boolean checkAnnotationAttribute;

    // 收集到的字段信息
    protected List<CollectField> fields = new ArrayList<>();

    // 待排除字段
    protected HashMap<String, List<String>> excludes = new HashMap<>();

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

    /**
     * 传递记录当前类的全限定名
     */
    @Override
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
        this.currentClassName = name;
        super.visit(version, access, name, signature, superName, interfaces);
    }

    /**
     * 收集非静态非final字段,存储在fields集合中
     */
    @Override
    public FieldVisitor visitField(int access, String name,
                                   String descriptor, String signature, Object value) {
        // 是否为静态字段
        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
        // 是否为final字段
        boolean isFinal = (access & Opcodes.ACC_FINAL) != 0;
        if (!isStatic && !isFinal) {
            CollectField cf = new CollectField(access, name,
                    descriptor, signature, value, currentClassName);
            fields.add(cf);
        }
        return super.visitField(access, name, descriptor, signature, value);
    }

    /**
     * 对不同的注解进行路由,并检查注解内属性
     */
    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {

        AnnotationVisitor av = super.visitAnnotation(descriptor, visible);

        switch (descriptor) {
            case GETTER_ANNOTATION_DESCRIPTOR:
                hasGetter = true;
                checkAnnotationAttribute = true;
                break;
            case SETTER_ANNOTATION_DESCRIPTOR:
                hasSetter = true;
                checkAnnotationAttribute = true;
                break;
            case DATA_ANNOTATION_DESCRIPTOR:
                hasGetter = true;
                hasSetter = true;
                checkAnnotationAttribute = true;
                break;
        }
        // 检查注解内属性
        if (checkAnnotationAttribute) {
            return new AnnotationAdapter(api, av, descriptor, excludes);
        }
        return av;
    }

    @Override
    public void visitEnd() {
        // 排除指定字段
        exclude();
        // 增强类逻辑,再调用父类方法传递
        enhance();
        // 状态重置
        clear();
        super.visitEnd();
    }

    /**
     * 排除字段
     */
    private void exclude() {
        List<String> dataExcludes = excludes
                .getOrDefault(DATA_ANNOTATION_DESCRIPTOR, Collections.emptyList());
        List<String> getterExcludes = excludes
                .getOrDefault(GETTER_ANNOTATION_DESCRIPTOR, Collections.emptyList());
        List<String> setterExcludes = excludes
                .getOrDefault(SETTER_ANNOTATION_DESCRIPTOR, Collections.emptyList());

        // 准备最终要使用的列表
        List<String> finalGetterExcludes = getterExcludes;
        List<String> finalSetterExcludes = setterExcludes;

        // 仅在 @Data 注解实际排除了字段时,才进行合并
        if (!dataExcludes.isEmpty()) {
            // 合并,并去重
            finalGetterExcludes = Stream
                    .concat(dataExcludes.stream(), getterExcludes.stream())
                    .distinct()
                    .collect(Collectors.toList());
            finalSetterExcludes = Stream
                    .concat(dataExcludes.stream(), setterExcludes.stream())
                    .distinct()
                    .collect(Collectors.toList());
        }

        // 将最终确定下来的列表放回映射,确保键一定存在
        // 即使内容没变(比如都是空列表),也明确地 put 回去,表明这是“确定状态”
        excludes.put(GETTER_ANNOTATION_DESCRIPTOR, finalGetterExcludes);
        excludes.put(SETTER_ANNOTATION_DESCRIPTOR, finalSetterExcludes);
    }

    /**
     * 清理状态,防止状态紊乱
     */
    private void clear() {
        hasGetter = false;
        hasSetter = false;
        checkAnnotationAttribute = false;
        if (fields != null) fields.clear();
        if (excludes != null) excludes.clear();
    }

    /**
     * 抽象方法,用于增强类逻辑
     */
    protected abstract void enhance();
}
package com.junjie.impl.annotation.data;

import org.objectweb.asm.AnnotationVisitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 注解适配器,用于检查注解内属性
 */
public class AnnotationAdapter extends AnnotationVisitor {

    private final String descriptor;
    private final List<String> exFields = new ArrayList<>();

    // 需排除的字段
    private final HashMap<String, List<String>> excludes;

    protected AnnotationAdapter(int api, AnnotationVisitor annotationVisitor,
                                String descriptor, HashMap<String, List<String>> excludes) {
        super(api, annotationVisitor);
        this.excludes = excludes;
        this.descriptor = descriptor;
    }

    /**
     * 访问注解中的数组属性
     */
    @Override
    public AnnotationVisitor visitArray(String name) {
        AnnotationVisitor av = super.visitArray(name);
        if (name.equals("exclude")) {
            return new AnnotationAdapter(api, av, descriptor, excludes) {
                /**
                 * 访问数组中的每个元素
                 */
                @Override
                public void visit(String name, Object value) {
                    exFields.add((String) value);
                    super.visit(name, value);
                }
            };
        }
        return av;
    }

    /**
     *
     */
    @Override
    public void visitEnd() {
        excludes.put(descriptor, exFields);
        super.visitEnd();
    }
}
package com.junjie.impl.annotation.data;

import com.junjie.util.StringUtil;
import org.objectweb.asm.*;

import java.util.List;

import static com.junjie.util.Constant.GETTER_ANNOTATION_DESCRIPTOR;
import static org.objectweb.asm.Opcodes.*;

/**
 * 生成Getter方法的访问器
 */
public class GetterVisitor extends AbstractDataVisitor {

    public GetterVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    /**
     * 对字段进行遍历,生成Getter方法
     */
    @Override
    protected void enhance() {
        if (hasGetter) {
            fields.forEach(
                    f ->
                    {
                        List<String> gets = excludes.get(GETTER_ANNOTATION_DESCRIPTOR);
                        if (!gets.contains(f.getName())) {
                            // 获取get方法名
                            String methodName = StringUtil.getMethodName(f.getName());
                            // 获取字段的Type
                            Type type = Type.getType(f.getDesc());
                            int opcode = type.getOpcode(IRETURN);
                            MethodVisitor mv = visitMethod(
                                    ACC_PUBLIC,
                                    methodName,
                                    "()" + f.getDesc(),
                                    f.getSignature(),
                                    null
                            );
                            mv.visitCode();
                            mv.visitVarInsn(ALOAD, 0);
                            mv.visitFieldInsn(
                                    GETFIELD,
                                    currentClassName,
                                    f.getName(),
                                    f.getDesc()
                            );
                            mv.visitInsn(opcode);
                            mv.visitMaxs(1, 1);
                            mv.visitEnd();
                        }
                    });
        }
    }
}
package com.junjie.impl.annotation.data;

import com.junjie.util.StringUtil;
import org.objectweb.asm.*;

import java.util.List;

import static com.junjie.util.Constant.SETTER_ANNOTATION_DESCRIPTOR;
import static org.objectweb.asm.Opcodes.*;

/**
 * 生成Setter方法的访问器
*/
public class SetterVisitor extends AbstractDataVisitor {

    public SetterVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    /**
     * 遍历字段列表,生成Setter方法
     */
    @Override
    protected void enhance() {
        if (hasSetter) {
            fields.forEach(
                    f ->
                    {
                        List<String> sets = excludes.get(SETTER_ANNOTATION_DESCRIPTOR);
                        if (!sets.contains(f.getName())) {
                            // 获取Set方法名
                            String methodName = StringUtil.setMethodName(f.getName());
                            // 获取字段的Type
                            Type type = Type.getType(f.getDesc());
                            int opcode = type.getOpcode(ILOAD);
                            MethodVisitor mv = visitMethod(
                                    ACC_PUBLIC,
                                    methodName,
                                    "(" + f.getDesc() + ")" + "V",
                                    f.getSignature(),
                                    null
                            );
                            mv.visitCode();
                            mv.visitVarInsn(ALOAD,0);
                            mv.visitVarInsn(opcode,1);
                            mv.visitFieldInsn(
                                    PUTFIELD,
                                    currentClassName,
                                    f.getName(),
                                    f.getDesc()
                            );
                            mv.visitInsn(RETURN);
                            mv.visitMaxs(1, 1);
                            mv.visitEnd();
                        }
                    });
        }
    }
}

实体设计

package com.junjie.impl.dto;

public class CollectField {
    private int access;      // 访问标志,如 Opcodes.ACC_PUBLIC
    private String name;     // 字段名
    private String desc;     // 描述符,如 "I", "Ljava/lang/String;"
    private String signature; // 泛型签名,可能为null
    private Object value;    // 如果是常量,它的值
    private String ownerClass; // 所属类名

    public CollectField(int access, String name, String desc,
                        String signature, Object value, String ownerClass) {
        this.access = access;
        this.name = name;
        this.desc = desc;
        this.signature = signature;
        this.value = value;
        this.ownerClass = ownerClass;
    }

    public int getAccess() {
        return access;
    }

    public void setAccess(int access) {
        this.access = access;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getSignature() {
        return signature;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public String getOwnerClass() {
        return ownerClass;
    }

    public void setOwnerClass(String ownerClass) {
        this.ownerClass = ownerClass;
    }
}

工具类

package com.junjie.util;

public class Constant {

    public static final String GETTER_ANNOTATION_DESCRIPTOR = "Lcom/junjie/annotation/Getter;";
    public static final String SETTER_ANNOTATION_DESCRIPTOR = "Lcom/junjie/annotation/Setter;";
    public static final String DATA_ANNOTATION_DESCRIPTOR = "Lcom/junjie/annotation/Data;";
}
package com.junjie.util;

public class StringUtil {

    private StringUtil(){
    }

    /**
     * 获取字段对应的get方法名
     * @param fieldName 字段名
     * @return 对应的get方法名
     */
    public static String getMethodName(String fieldName) {
        return "get" + fieldName.substring(0,1).toUpperCase()
                + fieldName.substring(1);
    }

    /**
     * 获取字段对应的set方法名
     * @param fieldName 字段名
     * @return 对应的set方法名
     */
    public static String setMethodName(String fieldName) {
        return "set" + fieldName.substring(0,1).toUpperCase()
                + fieldName.substring(1);
    }
}

启动类

package com.junjie;

import com.junjie.impl.annotation.data.GetterVisitor;
import com.junjie.impl.annotation.data.SetterVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class Start {
    static void main() throws IOException {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassReader cr = new ClassReader("com.junjie.test.Girl");

        GetterVisitor gv = new GetterVisitor(Opcodes.ASM9, cw);
        SetterVisitor sv = new SetterVisitor(Opcodes.ASM9, gv);
        int po = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;

        cr.accept(sv, po);

        byte[] byteArray = cw.toByteArray();

        Files.write(Path.of("src/main/resources/class/annotation/Girl.class"), byteArray);
    }
}