Java-ASM框架学习-修改类的字节码

Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰

ClassReader

用于读取字节码,父类是Object

主要作用:

  1. 分析字节码里各部分内容,如版本、字段等等
  2. 配合其他Visitor使用

主要使用的方法

public void accept(ClassVisitor classVisitor, int parsingOptions) {
        this.accept(classVisitor, new Attribute[0], parsingOptions);
}

// 第一个参数是访问者,第二个参数用于跳过读取字节码的一些信息,常用ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES,跳过没必要的调试和帧信息以缩小大小

如何修改已存在的字节码

通过继承ClassVisitor,重写visitxxx方法,在方法中对访问到的数据进行操作,然后传给下一个ClassVisitor的子类处理(如果有的话)。经过一个或多个ClassVisitor的访问链处理后,传到ClassWriter手里,由ClassWriter生成最终的字节码结果

Java-ASM框架学习-修改类的字节码

ClassVisitor方法通过构造函数可以传递下一个Visitor进去,如同链表一样,一个个的形成访问链,最后链接到ClassWriter这个特殊的ClassVisitor里去形成结果

实例

被修改的类

package example;

public class TestClass01 {
    public int a;
    public int b;

    public TestClass01() {
        System.out.println("init!!");
    }
}

ASM代码

package example.modify;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.IOException;

import example.modify.clsvisitor.*;
import org.objectweb.asm.Opcodes;
import utils.FileUtils;

public class Test01 {

    public static void main(String[] args) throws IOException {
        String path = FileUtils.getFilePath("example/sample/TestClass01.class");
        FileUtils.writeBytes(path, modifyClass());
    }

    public static byte[] modifyClass() {
        ClassReader cr = null;
        try {
            cr = new ClassReader("example.TestClass01");
        } catch (IOException e) {
            e.printStackTrace();
        }
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        ClassVisitor cv = new TestClass01Visitor(Opcodes.ASM9, cw);
        cr.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
        return cw.toByteArray();
    }
}

package example.modify.clsvisitor;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;

public class TestClass01Visitor extends ClassVisitor {
    private String addFieldN = "content";
    private boolean flag1 = true;
    private String addStaticFieldN = "myID";
    private int addStaticFieldV = 100;
    private boolean flag2 = true;
    private String delFieldN = "b";

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

    /**
     * 把java版本改成1.6 全限定名改成sample包下,避免两个同样的类冲突
     * @param version
     * @param access
     * @param name
     * @param signature
     * @param superName
     * @param interfaces
     */
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(Opcodes.V1_6, access, "example/sample/TestClass01", signature, superName, interfaces);
    }

    /**
     * 添加两个成员删一个成员
     * @param access
     * @param name
     * @param descriptor
     * @param signature
     * @param value
     * @return
     */
    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        if (name.equals(delFieldN)) return null;
        if (name.equals(addFieldN)) flag1 = false;  // 存在就不添加
        if (name.equals(addStaticFieldN)) flag2 = false;
        return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
        super.visitField(
                Opcodes.ACC_PUBLIC,
                this.addFieldN,
                "Ljava/lang/String;",
                null,
                ""
        );
        super.visitField(
                Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
                this.addStaticFieldN,
                "I",
                null,
                this.addStaticFieldV
        );
        super.visitEnd();
    }
}

生成的字节码结果(idea反编译后)

Java-ASM框架学习-修改类的字节码

上一篇:ASM字节码插桩


下一篇:windows7电脑怎么永久关闭广告