ASM框架使用(三)--方法修改以及建立
在jdk 1.6以後編譯的類,除了位元組碼指令以外,還多了一些棧對映楨(stack map frames),用來提高虛擬機器校驗位元組碼的速度的。
stack map frames反映了位元組碼執行過程中,棧幀的變化。
stack map frames中有一種特殊型別Uninitialized(label),它先分配記憶體,但是不初始化,它只有初始化方法可以被呼叫。一旦被初始化,則發生在這個型別上的所有事件都會被替換為真實的型別。比如IllegalArgumentException。
stack map frames還有其它3種特殊型別:
UNINITIALIZED_THIS 是建構函式中區域性變量表第一個元素。
TOP 相當於一個未定義的值
NULL 等價於 null
為了節省空間,只在特殊指令後儲存stack map frames,比如
jump跳轉指令,exception處理指令,無條件跳轉指令。
為了節省更多空間,只儲存每一幀與上一幀不同的地方。初始楨不儲存,因為這可以很容易從方法引數型別中推匯出來。
ASM使用MethodVisitor產生和修改方法,MethodVisitor類的方法呼叫有順序要求:
ASM提供了三種基於MethodVisitor的核心元件,用來產生和修改方法:
- 通過ClassReader解析位元組碼,然後呼叫classVisitor返回的methodVisitor中相應的方法。這個classVitor是ClassReader.accept()的引數。
- ClassWriter 的visitMethod方法返回了一個methodVisitor的實現,可以直接產生二進位制的位元組碼。
- methodVisitor委託呼叫其它methodVisitor示例,這種可以看作過濾器
ClassWriter的引數:
- 0,你需要手動計算,最大運算元棧,區域性變量表,楨變化
- ClassWriter.COMPUTE_MAXS,自動計算區域性變量表和運算元棧,但是必須要呼叫visitMaxs,方法引數會被忽略。楨變化需要手動計算
- ClassWriter.COMPUTE_FRAMES,全自動計算,但是必須要呼叫visitMaxs,方法引數會被忽略。
但是有時間成本,ClassWriter.COMPUTE_MAXS比0慢10%,COMPUTE_FRAMES慢一倍。
在特定情況下的特定演算法可能比ASM提供的更快,因為ASM需要考慮所有情況。
下面是給目標類的所有方法新增計時的程式碼,使用一個區域性變數計時,然後列印時間,納秒級別的。
使用AnalyzerAdapter計算最大運算元棧,LocalVariablesSorter重新計算區域性變數的索引並自動更新位元組碼中的索引引用。
使用MethodVisitor修改位元組碼,還可以限定類名,只修改指定類名的類方法。
因為插入了新的區域性變數用於計時,所以需要重新定位區域性變數。
效果:
原始類:
public class Receiver {
public void do1(){
System.out.println("開工12");
}
}
修改之後的類:
public void do1() {
long var1 = System.nanoTime();
System.out.println("開工12");
var1 = System.nanoTime() - var1;
System.out.println(var1);
}
工具類:
package bytecode;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AnalyzerAdapter;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
public class TimeCountAdpter extends ClassVisitor implements Opcodes {
private String owner;
private boolean isInterface;
public TimeCountAdpter(ClassVisitor classVisitor) {
super(ASM6, classVisitor);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
owner = name;
isInterface = (access & ACC_INTERFACE) != 0;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv=cv.visitMethod(access, name, descriptor, signature, exceptions);
if (!isInterface && mv != null && !name.equals("<init>")) {
AddTimerMethodAdapter at = new AddTimerMethodAdapter(mv);
at.aa = new AnalyzerAdapter(owner, access, name, descriptor, at);
at.lvs = new LocalVariablesSorter(access, descriptor, at.aa);
return at.lvs;
}
return mv;
}
public void visitEnd() {
cv.visitEnd();
}
class AddTimerMethodAdapter extends MethodVisitor {
private int time;
private int maxStack;
public LocalVariablesSorter lvs;
public AnalyzerAdapter aa;
public AddTimerMethodAdapter(MethodVisitor methodVisitor) {
super(ASM6, methodVisitor);
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
time=lvs.newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, time);
maxStack=4;
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(LLOAD, time);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, time);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(LLOAD, time);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
maxStack=Math.max(aa.stack.size()+4,maxStack);
}
mv.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(Math.max(maxStack,this.maxStack), maxLocals);
}
}
public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, IOException {
ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS);
TraceClassVisitor tv=new TraceClassVisitor(cw,new PrintWriter(System.out));
TimeCountAdpter addFiled=new TimeCountAdpter(tv);
ClassReader classReader=new ClassReader("command.Receiver");
classReader.accept(addFiled,ClassReader.EXPAND_FRAMES);
File file=new File("target/classes/command/Receiver.class");
String parent=file.getParent();
File parent1=new File(parent);
parent1.mkdirs();
file.createNewFile();
FileOutputStream fileOutputStream=new FileOutputStream(file);
fileOutputStream.write(cw.toByteArray());
}
}