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