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

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

    ASM 4.0 核心包中包含幾個關鍵類,這些類在ASM 3.0 時期是以接口形式提供。本文針對 ASM 4.0 編寫,故不討論 ASM 3.0環境。首先簡單的將 「.class」 文件的內容看做是以下樹形結構: 數組

Class
    Annotation
        Annotation
            ...
    Field
        Annotation
            ...
    Method
        Annotation
            ...
    ASM 在讀取 「.class」 文件內容時也會按照遞此順序進行調用。每拜訪一個結構中的成員都會使用相應的接口,具體關係以下:
樹形關係 使用的接口
Class ClassVisitor
Field FieldVisitor
Method MethodVisitor
Annotation
AnnotationVisitor


    ClassVisitor,在 ASM3.0 中是一個接口,到了 ASM4.0 與 ClassAdapter 抽象類合併。主要負責 「拜訪」 類成員信息。其中包括(標記在類上的註解,類的構造方法,類的字段,類的方法,靜態代碼塊),它的完整接口以下:
spa

我將重點介紹其中幾個關鍵方法: .net

1.visit(int , int , String , String , String , String[])
    該方法是當掃描類時第一個拜訪的方法,主要用於類聲明使用。下面是對方法中各個參數的示意:visit( 類版本 , 修飾符 , 類名 , 泛型信息 , 繼承的父類 , 實現的接口)。例如: 代理

public class TestBean {

等價於:
visit(V1_6, ACC_PUBLIC | ACC_SUPER , "org/more/test/asm/simple/TestBean",
      null, "java/lang/Object", null)

    第一個參數:表示類版本:V1_6,表示 「.class」 文件的版本是 JDK 1.6。可用的其餘版本有:V1_1(JRE_1.1)、V1_2(J2SE_1.2)V1_3J2SE_1.3V1_4J2SE_1.4V1_5J2SE_1.5V1_6(JavaSE_1.6)V1_7JavaSE_1.7。咱們所指的 JDK 6 或 JDK 7 實際上就是隻 JDK 1.6 或 JDK 1.7。 code

    第二個參數:表示類的修飾符:修飾符在 ASM 中是以 「ACC_」 開頭的常量進行定義。能夠做用到類級別上的修飾符有:ACC_PUBLIC(public)、ACC_PRIVATE(private)、ACC_PROTECTED(protected)、ACC_FINAL(final)、ACC_SUPER(extends)、ACC_INTERFACE(接口)、ACC_ABSTRACT(抽象類)、ACC_ANNOTATION(註解類型)、ACC_ENUM(枚舉類型)、ACC_DEPRECATED(標記了@Deprecated註解的類)、ACC_SYNTHETIC blog

    第三個參數:表示類的名稱:一般咱們的類完整類名使用 「org.test.mypackage.MyClass」 來表示,可是到了字節碼中會以路徑形式表示它們 「org/test/mypackage/MyClass」 值得注意的是雖然是路徑表示法可是不須要寫明類的 「.class」 擴展名。 繼承

    第四個參數:表示泛型信息,若是類並未定義任何泛型該參數爲空。Java 字節碼中表示泛型時分別對接口和類採起不一樣的定義。該參數的內容格式以下: 接口

<泛型名:基於的類型....>Ljava/lang/Object;

<泛型名::基於的接口....>Ljava/lang/Object;
其中 「泛型名:基於的類型」 內容能夠無限的寫下去,例如:
public class TestBean<T,V,Z> {

泛型參數爲:<T:Ljava/lang/Object;V:Ljava/lang/Object;Z:Ljava/lang/Object;>Ljava/lang/Object;
分析結構以下:
  <
   T:Ljava/lang/Object;
   V:Ljava/lang/Object;
   Z:Ljava/lang/Object;
  >
   Ljava/lang/Object;

再或者: ci

public class TestBean<T extends Date, V extends ArrayList> {

泛型參數爲:<T:Ljava/util/Date;V:Ljava/util/ArrayList;>Ljava/lang/Object;
分析結構以下:
  <
   T:Ljava/util/Date;
   V:Ljava/util/ArrayList;
  >
   Ljava/lang/Object;

    以上內容只是針對泛型內容是基於某個具體類型的狀況,若是泛型是基於接口而非類型則定義方式會有所不一樣,這一點須要注意。例如:

public class TestBean<T extends Serializable, V> {

泛型參數爲:<T::Ljava/io/Serializable;V:Ljava/lang/Object;>Ljava/lang/Object;
分析結構以下:
  <
   T::Ljava/io/Serializable; //比類型多出一個「:」
   V:Ljava/lang/Object;
  >
   Ljava/lang/Object;

    第五個參數:表示所繼承的父類。因爲 Java 的類是單根結構,即全部類都繼承自 java.lang.Object 所以能夠簡單的理解爲任何類都會具備一個父類。雖然在編寫 Java 程序時咱們沒有去寫 extends 關鍵字去明確繼承的父類,可是 JDK在編譯時 總會爲咱們加上 「 extends Object」。因此假若某一天你看到這樣一份代碼也不要過於緊張。

    第六個參數:表示類實現的接口,在 Java 中類是能夠實現多個不一樣的接口所以此處是一個數組例如:

public class TestBean implements Serializable , List {

該參數會以 「[java/io/Serializable, java/util/List]」 形式出現。

    這裏須要補充一些內容,若是類型其自己就是接口類型。對於該方法而言,接口的父類類型是 「java/lang/Object」,接口所繼承的全部接口都會出如今第六個參數中。例如:

public inteface TestBean implements Serializable , List {

最後兩個參數對應爲:
    "java/lang/Object", ["java/io/Serializable","java/util/List"]

2.visitAnnotation(String , boolean)
    該方法是當掃描器掃描到類註解聲明時進行調用。下面是對方法中各個參數的示意:visitAnnotation(註解類型 , 註解是否能夠在 JVM 中可見)。例如:

@Bean({ "" })
public class TestBean {

@Bean等價於:
    visitAnnotation("Lnet/hasor/core/gift/bean/Bean;", true);
下面是 @Bean 的源代碼:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Bean {
    /** Bean名稱。*/
    public String[] value();
}

   第一個參數:表示的是,註解的類型。它使用的是(「L」 + 「類型路徑」 + 「;」)形式表述。

   第二個參數:表示的是,該註解是否在 JVM 中可見。這個參數的具體含義能夠理解爲:若是爲 true 表示虛擬機可見,咱們能夠經過下面這樣的代碼獲取到註解類型:

testBeanType.getAnnotation(TestAnno.class);

    談到這裏就須要額外說明一下在聲明註解時常見的 「@Retention(RetentionPolicy.RUNTIME)」 標記。RetentionPolicy 是一個枚舉它具有三個枚舉元素其每一個含義能夠理解爲:

    1.RetentionPolicy.SOURCE:聲明註解只保留在 Java 源程序中,在編譯 Java 類時註解信息不會被寫入到 Class。若是使用的是這個配置 ASM 也將沒法探測到這個註解。
    
2.RetentionPolicy.CLASS:聲明註解僅保留在 Class 文件中,JVM 運行時並不會處理它,這意味着 ASM 能夠在 visitAnnotation 時候探測到它,可是經過Class 反射沒法獲取到註解信息。
    
3.RetentionPolicy.RUNTIME:這是最經常使用的一種聲明,ASM 能夠探測到這個註解,同時 Java 反射也能夠取得註解的信息。全部用到反射獲取的註解都會用到這個配置,就是這個緣由。

3.visitField(int , String , String , String , Object)
    該方法是當掃描器掃描到類中字段時進行調用。下面是對方法中各個參數的示意:visitField(修飾符 , 字段名 , 字段類型 , 泛型描述 , 默認值)。例如:

public class TestBean {
    private String stringData;

stringData字段等價於:
    visitField(ACC_PRIVATE, "stringData", "Ljava/lang/String;", null, null)

    第一個參數:表示字段的修飾符,修飾符在 ASM 中是以 「ACC_」 開頭的常量進行定義。能夠做用到字段級別上的修飾符有:ACC_PUBLIC(public)、ACC_PRIVATE(private)、ACC_PROTECTED(protected)、ACC_STATIC(static)、ACC_FINAL(final)、ACC_VOLATILE(volatile)、ACC_TRANSIENT(transient)、ACC_ENUM(枚舉)、ACC_DEPRECATED(標記了@Deprecated註解的字段)、ACC_SYNTHETIC

    第二個參數:表示字段的名稱。

    第三個參數:表示字段的類型,其格式爲:(「L」 + 類型路徑 + 「;」)。

      第四個參數:表示泛型信息, 泛型類型描述是使用(「T」 + 泛型名 + 「;」)加以說明。例如:「private T data;」 字段的泛型描述將會是 「 TT; 」, private V data; 」 字段的泛型描述將會是 「 TV; 」。泛型名稱將會在 「visit(int , int , String , String , String , String[])」 方法中第五個參數中加以表述。例如:
public class TestBean<T, V> {
    private T data;
    private V value;

等價於:

visit(V1_6, ACC_PUBLIC | ACC_SUPER , "org/more/test/asm/simple/TestBean",
      "<T:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;", //定義了兩個泛型類型 T 和 V
      "java/lang/Object", null)

visitField(ACC_PRIVATE, "data", "Ljava/lang/Object;", "TT;", null)  //data 泛型名稱爲 T
visitField(ACC_PRIVATE, "value", "Ljava/lang/Object;", "TV;", null) // value 泛型名稱爲 V
還有一種狀況,假若類在定義泛型時候已經基於某個類型那麼生成的代碼將會是以下形式:
public class TestBean<T extends Serializable, V> {
    private T data;
    private V value;

等價於:

visit(V1_6, ACC_PUBLIC | ACC_SUPER , "org/more/test/asm/simple/TestBean",
      "<T::Ljava/io/Serializable;V:Ljava/lang/Object;>Ljava/lang/Object;", //定義了兩個泛型類型 T 和 V
      "java/lang/Object", null)

visitField(ACC_PRIVATE, "data", "Ljava/io/Serializable;", "TT;", null)  //data 泛型名稱爲 T
visitField(ACC_PRIVATE, "value", "Ljava/lang/Object;", "TV;", null)     // value 泛型名稱爲 V
    第五個參數:表示的是默認值, 因爲默認值是 Object 類型你們可能覺得能夠是任何類型。這裏要澄清一下,默認值中只能用來表述 Java 基本類型這其中包括了(byte、sort、int、long、float、double、boolean、String)其餘全部類型都不不能夠進行表述。而且只有標有 「final」 修飾符的字段而且該字段賦有初值時這個參數纔會有值。例如類:
public class TestBean {
    private final String data;
    public TestBean() {
        data = "aa";
    }
....

在執行 「visitField」 方法時候,這個參數的就是 null 值,下面這種代碼也會是 null 值:

public class TestBean {
    private final Date data;
    public TestBean() {
        data =new Date();
    }
....

此外若是字段使用的是基本類型的包裝類型,諸如:Integer、Long...也會爲空值:

public class TestBean {
    private final Integer intData = 12;
...

可以正確獲得默認值的代碼應該是這個樣子的:

public class TestBean {
    private final String data    = "ABC";
    private final int    intData = 12;
...

4.visitMethod(int , String , String , String , String[])
    該方法是當掃描器掃描到類的方法時進行調用。下面是對方法中各個參數的示意:visitMethod(修飾符 , 方法名 , 方法簽名 , 泛型信息 , 拋出的異常)。例如:

public class TestBean {
    public int halloAop(String param) throws Throwable {

等價於:

visit(V1_6, ACC_PUBLIC | ACC_SUPER , "org/more/test/asm/simple/TestBean",
      null, "java/lang/Object", null)

visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
visitMethod(ACC_PUBLIC, "halloAop", "(Ljava/lang/String;)I", null, [java/lang/Throwable])

    第一個參數:表示方法的修飾符,修飾符在 ASM 中是以 「ACC_」 開頭的常量進行定義。能夠做用到方法級別上的修飾符有:ACC_PUBLIC(public)、ACC_PRIVATE(private)、ACC_PROTECTED(protected)、ACC_STATIC(static)、ACC_FINAL(final)、ACC_SYNCHRONIZED(同步的)、ACC_VARARGS(不定參數個數的方法)、ACC_NATIVE(native類型方法)、ACC_ABSTRACT(抽象的)、ACC_DEPRECATED(標記了@Deprecated註解的方法)、ACC_STRICTACC_SYNTHETIC

    第二個參數:表示方法名,在 ASM 中 「visitMethod」 方法會處理(構造方法、靜態代碼塊、私有方法、受保護的方法、共有方法、native類型方法)。在這些範疇中構造方法的方法名爲 「<init>」,靜態代碼塊的方法名爲 「<clinit>」。列如:

public class TestBean {
    public int halloAop(String param) throws Throwable {
        return 0;
    }
    static {
        System.out.println();
    }
...

等價於:

visit(V1_6, ACC_PUBLIC | ACC_SUPER , "org/more/test/asm/simple/TestBean",
      null, "java/lang/Object", null)

visitMethod(ACC_PUBLIC, "<clinit>", "()V", null, null)
visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
visitMethod(ACC_PUBLIC, "halloAop", "(Ljava/lang/String;)I", null, [java/lang/Throwable])

    第三個參數:表示方法簽名,方法簽名的格式以下:「(參數列表)返回值類型」。在字節碼中不一樣的類型都有其對應的代碼,以下所示:

"I"        = int
"B"        = byte
"C"        = char
"D"        = double
"F"        = float
"J"        = long
"S"        = short
"Z"        = boolean
"V"        = void
"[...;"    = 數組
"[[...;"   = 二維數組
"[[[...;"  = 三維數組
"L....;"   = 引用類型

    下面是一些方法簽名對應的方法參數列表。

String[]
[Ljava/lang/String;
String[][]
[[Ljava/lang/String;
int, String, String, String, String[]
ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;
int, boolean, long, String[], double
IZJ[Ljava/lang/String;D
Class<?>, String, Object... paramType
Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Object;
int[]
[I

    第四個參數:凡是具備泛型信息的方法,該參數都會有值。而且該值的內容信息基本等於第三個參數的拷貝,只不過不一樣的是泛型參數被特殊標記出來。例如:

public class TestBean<T, V extends List> {
    public T halloAop(V abc, int aaa) throws Throwable {

方法簽名:(Ljava/util/List;I)Ljava/lang/Object;
泛型簽名:(TV;I)TT;
public class TestBean<T, V extends List> {
    public String halloAop(V abc, int aaa) throws Throwable {

方法簽名:(Ljava/util/List;I)Ljava/lang/String;
泛型簽名:(TV;I)Ljava/lang/String;
    能夠看出泛型信息中用於標識泛型類型的結構是(「T」 + 泛型名 + 「;」),還有一種狀況就是。泛型是聲明在方法上。例如:
public class TestBean {
    public <T extends List> String halloAop(T abc, int aaa) throws Throwable {

方法簽名:(Ljava/util/List;I)Ljava/lang/String;
泛型簽名:<T::Ljava/util/List;>(TT;I)Ljava/lang/String; //泛型類型基於接口
public class TestBean {
    public <T> String halloAop(T abc, int aaa) throws Throwable {

方法簽名:(Ljava/lang/Object;I)Ljava/lang/String;
泛型簽名:<T:Ljava/lang/Object;>(TT;I)Ljava/lang/String; //泛型類型基於類型
    第五個參數:用來表示將會拋出的異常,若是方法不會拋出異常。則該參數爲空。這個參數的表述形式比較簡單,舉一個例子:
public class TestBean {
    public <T> String halloAop(T abc, int aaa) throws Throwable,Exception {

異常參數爲:[java/lang/Throwable, java/lang/Exception]

5.visitEnd()
    該方法是當掃描器完成類掃描時纔會調用,若是想在類中追加某些方法。能夠在該方法中實現。在後續文章中咱們會用到這個方法。

    提示:ACC_SYNTHETICACC_STRICT這兩個修飾符我也不清楚具體功能含義是什麼,若是有知道的朋友還望補充說明。

    下一篇文章將講解,使用 ASM 如何實一個 Aop 現代理類,屆時將不在詳細講解 ClassVisitor 類中各個方法的做用。

相關文章
相關標籤/搜索