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来生成和修改类的字节码了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值