本文是《深刻字節碼 -- 使用 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_3(J2SE_1.3)、V1_4(J2SE_1.4)、V1_5(J2SE_1.5)、V1_6(JavaSE_1.6)、V1_7(JavaSE_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」 + 類型路徑 + 「;」)。
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_STRICT、ACC_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_SYNTHETIC、ACC_STRICT:這兩個修飾符我也不清楚具體功能含義是什麼,若是有知道的朋友還望補充說明。
下一篇文章將講解,使用 ASM 如何實一個 Aop 現代理類,屆時將不在詳細講解 ClassVisitor 類中各個方法的做用。