前些天看aop就看到了cglib,看cglib又看到了asm,模仿着做了个示例。利用asm修改字节码,能实现编译不通过执行通的过的效果,挺有意思。
一个简单的待修改类:
package com.asm.zjc; public class C { public void m() throws InterruptedException{ Thread.sleep(300); } } 对其进行修改的类: package com.asm.zjc; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassAdapter; public class Generator { public static void main(String[] arg){ try{ ClassReader cr = new ClassReader("com/asm/zjc/C"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddTimeClassAdapter(cw); //使给定的访问者访问Java类的ClassReader cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File(System.getProperty("user.dir") + "\\bin\\com\\asm\\zjc\\C.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); System.out.println("success!"); }catch(FileNotFoundException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } } } 其中用到了类适配器: package com.asm.zjc; import org.objectweb.asm.*; 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.visitInsn(opcode); } @Override public void visitMaxs(int maxStack, int maxLocal){ mv.visitMaxs(maxStack+4, maxLocal); } } } 最后写一个测试程序验证: package com.asm.zjc; public class Test { @SuppressWarnings("static-access") public static void main(String[] args)throws InterruptedException{ C c1=new C(); c1.m(); System.out.println(C.timer); } } 注意第8行是不能通过编译的,但可以执行成功。 其实我们用下面的命令行反编译class文件,也可以了解字节码的结构: D:\software\eclipse\workspace\study_asm\bin>javap -c -verbose com.asm.zjc.C >aaa.java