1. 程式人生 > 其它 >Java-ASM框架學習-修改類的位元組碼

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生成最終的位元組碼結果

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反編譯後)