在之前的java instrument學習中,使用到了ASM框架,這裏簡單介紹一下ASM的使用。
ASM是一個 Java 字節碼操控框架。它能夠以二進制形式修改已有類或者動態生成類。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行爲。ASM 從類文件中讀入信息後,能夠改變類行爲,分析類信息,甚至能夠根據用戶要求生成新類。
我們還是使用ASM Guide中的經典例子,
public class C {
public void m() throws Exception{
Thread.sleep(100);
}
}
即將以上代碼,修改如下。
public class C {
public static long timer;
public void m() throws Exception{
timer -= System.currentTimeMillis();
Thread.sleep(100);
timer += System.currentTimeMillis();
System.out.println(timer);
}
}
ASM中有幾個核心類:
ClassReader:該類用來解析編譯過的class字節碼文件。
ClassWriter:該類用來重新構建編譯後的類,比如說修改類名、屬性以及方法,甚至可以生成新的類的字節碼文件。
ClassAdapter:該類也實現了ClassVisitor接口,它將對它的方法調用委託給另一個ClassVisitor對象。
在此我們還是使用上一篇文章java instrument學習中的工程代碼,其中ASM使用的關鍵代碼如下。
byte[] data = null;
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter ca = new AddTimeClassAdapter(cw);
cr.accept(ca, ClassReader.SKIP_DEBUG);
data = cw.toByteArray();
其中,classfileBuffer是要修改的類的字節碼,AddTimeClassAdapter實現對字節碼的修改,代碼如下,最後返回類修改以後的字節碼。
package org.kylin.agent;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class AddTimeClassAdapter extends ClassAdapter {
private String owner;
private boolean isInterface;
public AddTimeClassAdapter(ClassVisitor cv) {
super(cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
owner = name;
isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (!name.equals("<init>") && !isInterface && mv != null) {
mv = new AddTimeMethodAdapter(mv);
}
return mv;
}
@Override
public void visitEnd() {
if (!isInterface) {
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "timer", "J", null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
class AddTimeMethodAdapter extends MethodAdapter {
public AddTimeMethodAdapter(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitInsn(Opcodes.LSUB);
mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitInsn(Opcodes.LADD);
mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
}
mv.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocal) {
mv.visitMaxs(maxStack + 4, maxLocal);
}
}
}
以上代碼中,
visitEnd()給類增加了一個public static的變量timer,
visitCode()在方法進入時添加timer -= System.currentTimeMillis();
visitInsn()在方法返回前加入timer += System.currentTimeMillis();System.out.println(timer);
最後,運行加了agent的測試代碼,
package org.kylin;
public class agentTest {
public static void main(String[] args) throws Exception {
System.out.println("Agent Test...");
Thread.sleep(100);
}
}
可以得到輸出結果:
Agent is called...
Agent Test...
100
這裏爲了使輸出比較清晰,註釋了agent中Transforming Class的打印語句。
更多的ASM使用方法可以參考ASM Guide。
另外,我們可以安裝一個Eclipse插件Bytecode Outline,這樣就不必擔憂不熟悉字節碼了。先把要生成的類或者要修改的部分用java代碼寫出來,然後用Bytecode查看其字節碼,這樣就能方便的使用ASM來生成和修改類的字節碼了。