1. 程式人生 > >ASM框架使用(三)--方法修改以及建立

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的核心元件,用來產生和修改方法:

  1. 通過ClassReader解析位元組碼,然後呼叫classVisitor返回的methodVisitor中相應的方法。這個classVitor是ClassReader.accept()的引數。
  2. ClassWriter 的visitMethod方法返回了一個methodVisitor的實現,可以直接產生二進位制的位元組碼。
  3. 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());
    }
}