ASM學習

在之前的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來生成和修改類的字節碼了。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章