深刻字節碼 -- ASM 關鍵接口 MethodVisitor

    本文是《深刻字節碼 -- 使用 ASM 實現 AOP》的後續博文。在上一篇文章中介紹瞭如何使用 ASM 動態安插代碼到類中,從而簡單實現 Aop。文章獲得了廣大朋友好評,我也但願能夠不負衆望繼續寫出能夠獲得你們承認的更多相關文章。本文主要講解 ASM 核心接口方法和其參數意義。另外本文也可用作參考手冊使用。 java

    在上一篇文章中着重介紹了 ClassVisitor 接口,在本文將重點介紹一下MethodVisitor接口。前文提到過Class的文件結構相似 shell

Class
    Annotation
        Annotation
            ...
    Field
        Annotation
            ...
    Method
        Annotation
            ...

    當ASM的ClassReader讀取到Method時就轉入MethodVisitor接口處理。方法的定義,以及方法中指令的定義都會經過MethodVisitor接口通知給程序。咱們假設有下面這樣的一個類: this

public class DemoClass {
	public static void main(String[] args) {
		System.out.println();
	}
}

經過Javap能夠獲得下面這樣的輸出: spa

$ javap -c classtest.DemoClass
Compiled from "DemoClass.java"
public class classtest.DemoClass extends java.lang.Object{
public classtest.DemoClass();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   invokevirtual   #22; //Method java/io/PrintStream.println:()V
   6:   return

}

    能夠看出其實Java編譯完這個類以後是產生了兩個方法。其中一個是第四行表示的「public classtest.DemoClass();」它是構造方法。和第十行表示的「main」方法。下面這段例子用來掃描這個類的這兩個方法,咱們的掃描邏輯很簡單就是當遇到一個定義的方法時輸出這個方法名。 .net

public class DemoClassTest {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader(DemoClass.class.getName());
        cr.accept(new DemoClassVisitor(), ClassReader.SKIP_DEBUG);
        System.out.println("---ALL END---");
    }
}
class DemoClassVisitor extends ClassVisitor {
    public DemoClassVisitor() {
        super(Opcodes.ASM4);
    }
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println("at Method " + name);
        //
        MethodVisitor superMV = super.visitMethod(access, name, desc, signature, exceptions);
        return new DemoMethodVisitor(superMV, name);
    }
}
class DemoMethodVisitor extends MethodVisitor {
    private String methodName;
    public DemoMethodVisitor(MethodVisitor mv, String methodName) {
        super(Opcodes.ASM4, mv);
        this.methodName = methodName;
    }
    public void visitCode() {
        System.out.println("at Method ‘" + methodName + "’ Begin...");
        super.visitCode();
    }
    public void visitEnd() {
        System.out.println("at Method ‘" + methodName + "’End.");
        super.visitEnd();
    }
}
  1. 上面這段程序咱們首先在第三行使用 ClassReader 去讀取 DemoClass 類的字節碼信息。
  2. 其次經過「cr.accept(new DemoClassVisitor(), ClassReader.SKIP_DEBUG);」方法開始Visitor掃描整個字節碼。
  3. SKIP_DEBUG選項的意義是在掃描過程當中掠過全部有關行號方面的內容。
  4. 在DemoClassVisitor類中咱們重寫了visitMethod方法,當遇到方法的時候打印出方法名。
  5. 隨後咱們返回DemoMethodVisitor對象,用以輸出方法的開始和結束。

    上面這段程序的輸出以下: code

at Method <init>
at Method ‘<init>’ Begin...
at Method ‘<init>’End.
at Method main
at Method ‘main’ Begin...
at Method ‘main’End.
---ALL END---

    下面是這個MethodVisitor接口的全部方法定義。本文只會介紹主要的方法,所以不會逐個對方法作依次介紹: xml

    雖然該接口的方法數量如此之多,甚至是ClassVisitor接口的3倍以上。可是值得咱們關心的接口只有下面這幾個,其他的都是和代碼有關係: 對象

MethodVisitor.visitCode();
MethodVisitor.visitMaxs(maxStack, maxLocals);
MethodVisitor.visitEnd();
  • 第一個方法:表示ASM開始掃描這個方法。
  • 第二個方法:該方法是visitEnd以前調用的方法,能夠反覆調用。用以肯定類方法在執行時候的堆棧大小。
  • 第三個方法:表示方法輸出完畢。

構造方法: blog

    關於方法名或許讀者注意到了在掃描這個類的時候,有一個特殊的方法被掃描到了「<init>」,這個方法是傳說中的構造方法。當Java在編譯的時候沒有發現類文件中有構造方法的定義會爲其建立一個默認的無參構造方法。這個「<init>」就是那個由系統添加的構造方法。如今咱們爲類填寫一個構造方法以下: 接口

public class DemoClass {
    public DemoClass(int a) {
        
    }
    public static void main(String[] args) {
        System.out.println();
    }
}

    再次掃描這個類,你會發現它的結果和剛纔是同樣的,這是因爲咱們編寫的構造方法替換了系統默認生成的那個。

靜態代碼塊:

    在Class咱們接觸過用「static {  }」包含的代碼,這個是咱們常說的靜態代碼塊。這個代碼快ASM在掃描字節碼的時候也會遇到它,你們可千萬別覺得這真的是一個什麼代碼塊。全部的靜態代碼快最後都會放到「<clinit>」方法中。

    靜態代碼快只有一個,現有下面這個的一個類。在編寫這個類的時候我有意的寫了兩個不一樣的靜態代碼塊的類:

public class DemoClass {
    static {
        int a;
        System.out.println(11);
    }
    public static void main(String[] args) {
        System.out.println();
    }
    static {
        int a;
        System.out.println(22);
    }
}

    ASM在掃描這個類的時候你會發現雖然類中存在多個靜態代碼快,可是最後類文件中只會出現了一個「<clinit>」方法。JVM在編譯Class的時候估計已經將多個靜態代碼塊合併到一塊兒了。

相關文章
相關標籤/搜索