前些天看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