ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为,ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。目前许多框架如cglib、Hibernate、Spring都直接或间接地使用ASM操作字节码。
Core API 中操纵字节码的功能基于 ClassVisitor 接口。这个接口中的每个方法对应了 class文件中的每一项。ASM 提供了三个基于 ClassVisitor 接口的类来实现 class 文件的生成和转换:
1:ClassReader:ClassReader 解析一个类的 class 字节码,该类的 accept 方法接受一个 ClassVisitor的对象,在 accept 方法中,会按上文描述的顺序逐个调用 ClassVisitor 对象的方法。
2:ClassAdapter:ClassAdapter 是 ClassVisitor 的实现类。它的构造方法中需要一个 ClassVisitor 对象,并保存为字段 protected ClassVisitor cv。在它的实现中,每个方法都是原封不动的直接调用cv 的对应方法,并传递同样的参数。
3:ClassWriter:ClassWriter 也是 ClassVisitor 的实现类。ClassWriter 可以用来以二进制的方式创建一个类的字节码。可以通过 toByteArray 方法获取生成的字节数组。
ASM给我们提供了ASMifer工具来帮助开发,可使用ASMifer工具生成ASM结构来对比,比如使用命令:
java -cp .;asm-all-5.2.jar org.objectweb.asm.util.ASMifier Test (其中Test是编译后的class文件名)
package com.yuy.jvm;
public class CC {
public void m() throws InterruptedException {
System.out.println("now in CC.m");
Thread.sleep(100L);
}
}
2.通过java -cp 查看其编译的class文件编码(java -cp .;asm-all-5.2.jar org.objectweb.asm.util.ASMifier CC)
package asm.com.yuy.jvm;
import java.util.*;
import org.objectweb.asm.*;
public class CCDump implements Opcodes {
public static byte[] dump () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "com/yuy/jvm/CC", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, new String[] { "java/lang/InterruptedException" });
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("now in CC.m");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitLdcInsn(new Long(100L));
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
3.修改CC.java类,(修改只是为了查看加上计时器的class编码,拷贝到编码之后需要将CC还原)如下:
package com.yuy.jvm;
public class CC {
public void m() throws InterruptedException {
long startTime = System.currentTimeMillis();
System.out.println("now in CC.m");
Thread.sleep(100L);
long endTime = System.currentTimeMillis() - startTime;
System.out.println("方法耗时为:"+endTime );
}
}
4.通过java -cp 查看修改之后的class文件编码
package asm.com.yuy.jvm;
import java.util.*;
import org.objectweb.asm.*;
public class CCDump implements Opcodes {
public static byte[] dump () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, "com/yuy/jvm/CC", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, new String[] { "java/lang/InterruptedException" });
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("now in CC.m");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitLdcInsn(new Long(100L));
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, 1);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, 3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);
mv.visitLdcInsn("\u65b9\u6cd5\u8017\u65f6\u4e3a\uff1a");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(4, 5);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
5.对比修改前后的class文件编码:
6.创建MyClassVisitor类:
package com.yuy.jvm.asm;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv){
super(Opcodes.ASM5,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);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access,name,desc,signature,exceptions);
//字节码中的init为构造方法
if(!name.equals("") && mv != null){
//为方法增加计时的功能
mv = new MyMethodVisitor(mv);
}
return mv;
}
class MyMethodVisitor extends MethodVisitor{
public MyMethodVisitor(MethodVisitor mv){
super(Opcodes.ASM5,mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, 1);
}
@Override
public void visitInsn(int opcode) {
if(opcode >= Opcodes.IRETURN && opcode <=Opcodes.RETURN || opcode ==Opcodes.ATHROW){
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(Opcodes.LLOAD, 1);
mv.visitInsn(Opcodes.LSUB);
mv.visitVarInsn(Opcodes.LSTORE, 3);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false);
mv.visitLdcInsn("\u65b9\u6cd5\u8017\u65f6\u4e3a\uff1a");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(Opcodes.LLOAD, 3);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
mv.visitInsn(opcode);
}
}
}
7.创建Generator
package com.yuy.jvm.asm;
import com.sun.org.apache.xpath.internal.SourceTree;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class Generator {
public static void main(String[] args) throws IOException {
ClassReader cr = new ClassReader("com/yuy/jvm/CC");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv,ClassReader.SKIP_DEBUG);
byte[]data = cw.toByteArray();
//输出
File f = new File("out/production/JVMTest/com/yuy/jvm/CC.class");
FileOutputStream fout = new FileOutputStream(f);
fout.write(data);
fout.close();
System.out.println("now generator cc success");
}
}
8.执行Genrator中的main方法,改写CC.calss
9.执行test测试
package com.yuy.jvm.asm;
import com.yuy.jvm.CC;
public class MyTest {
public static void main(String[] args) throws InterruptedException {
CC cc = new CC();
cc.m();
}
}
10.说明:a.修改字节码之前执行Mytest控制台输出。
now in CC.m
b.执行Generator .main方法修改class文件之后执行Mytest控制台输出
now in CC.m
方法耗时为:101
CC.java 里面是没有方法耗时的代码 的,但是CC.class 经过重新Generator重新改造之后将统计耗时方法的字节码 代 码添加到CC.class文件中,所以会有统计方法耗时的输入会打印出来。
c.为了验证是统计所有方法的执行耗时。所以在CC.java中复制方法m 改名a b执行调a方法或b方法均有耗时打印
说明是支持的CC类的所有方法的耗时统计。
package com.yuy.jvm;
public class CC {
public void m() throws InterruptedException {
System.out.println("now in CC.m");
Thread.sleep(100L);
}
public void a() throws InterruptedException {
System.out.println("now in CC.m");
Thread.sleep(100L);
}
public void b() throws InterruptedException {
System.out.println("now in CC.m");
Thread.sleep(100L);
}
}