Java-ASM框架學習-修改類的位元組碼
阿新 • • 發佈:2021-11-14
Tips: ASM使用訪問者模式,學會訪問者模式再看ASM更加清晰
ClassReader
用於讀取位元組碼,父類是Object
主要作用:
- 分析位元組碼裡各部分內容,如版本、欄位等等
- 配合其他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反編譯後)