首頁  >  文章  >  Java  >  Java實作Class解析器的方法範例

Java實作Class解析器的方法範例

黄舟
黄舟原創
2017-09-15 10:16:001257瀏覽

這篇文章主要透過對class檔案的分析,介紹了Java Class 解析器實作方法範例,具有一定參考價值,需要的朋友可以了解下。

最近在寫一個私人項目,名字叫做ClassAnalyzer,ClassAnalyzer的目的是能讓我們對Java Class文件的設計與結構能夠有一個深入的理解。主體框架與基本功能已經完成,還有一些細節功能日後再增加。實際上JDK已經提供了命令列工具javap來反編譯Class文件,但本篇文章將闡明我實作解析器的思路。

Class檔案

作為類別或介面資訊的載體,每個Class檔案都完整的定義了一個類別。為了使Java程式可以“編寫一次,處處運行”,Java虛擬機規範對Class檔案進行了嚴格的規定。構成Class檔案的基本資料單位是字節,這些位元組之間不存在任何分隔符,這使得整個Class檔案中儲存的內容幾乎全部是程式運行的必要數據,單一位元組無法表示的資料由多個連續的位元組來表示。

根據Java虛擬機器規範,Class檔案採用一種類似C語言結構體的偽結構來儲存數據,這種偽結構中只有兩種資料類型:無符號數和表。 Java虛擬機規格定義了u1、u2、u4和u8來分別表示1個位元組、2個位元組、4個位元組和8個位元組的無符號數,無符號數可以用來描述數字、索引引用、數量值或者是字串。表是由多個無符號數或其它表作為數據項構成的複合數據類型,表用於描述有層次關係的複合結構的數據,因此整個Class文件本質上就是一張表。在ClassAnalyzer中,byte、short、int和long分別對應u1、u2、u4和u8資料類型,Class檔案被描述為如下Java類別。


public class ClassFile {
 public U4 magic;       // magic
 public U2 minorVersion;      // minor_version
 public U2 majorVersion;      // major_version
 public U2 constantPoolCount;    // constant_pool_count
 public ConstantPoolInfo[] cpInfo;   // cp_info
 public U2 accessFlags;      // access_flags
 public U2 thisClass;      // this_class
 public U2 superClass;      // super_class
 public U2 interfacesCount;     // interfaces_count
 public U2[] interfaces;      // interfaces
 public U2 fieldsCount;      // fields_count
 public FieldInfo[] fields;     // fields
 public U2 methodsCount;      // methods_count
 public MethodInfo[] methods;    // methods
 public U2 attributesCount;     // attributes_count
 public BasicAttributeInfo[] attributes;  // attributes
}

如何解析

#組成Class檔案的各個資料項目中,例如魔數、Class檔案的版本等資料項目、存取標誌、類別索引、父類別索引,它們在每個Class檔案中都佔用固定數量的字節,在解析時只需要讀取對應數量的位元組。除此之外,需要靈活處理的主要包括4部分:常數池、欄位表集合、方法表集合、屬性表集合。欄位和方法都可以具備自己的屬性,Class本身也有對應的屬性,因此,在解析欄位表集合和方法表集合的同時也包含了屬性表的解析。

常數池佔據了Class檔案很大一部分的數據,用於儲存所有的常數信息,包括數字和字串常數、類別名稱、介面名、欄位名稱和方法名稱等。 Java虛擬機器規格定義了多種常數類型,每種常數類型都有自己的結構。常量池本身就是一個表,在解析時有幾點要注意。

每個常數類型都透過一個u1類型的tag來標識。

表頭給出的常數池大小(constantPoolCount)比實際大1,例如,如果constantPoolCount等於47,那麼常數池中有46項常數。

常數池的索引範圍從1開始,例如,如果constantPoolCount等於47,那麼常數池的索引範圍為1~46。設計者將第0項空出來的目的是用來表達「不引用任何一個常數池項目」。

CONSTANT_Utf8_info型常數的結構中包含u1類型的tag、u2類型的length和由length個u1類型組成的bytes,這length位元組的連續資料是一個使用MUTF-8(Modified UTF-8)編碼的字串。 MUTF-8與UTF-8並不相容,主要差異有兩點:一是null字元會被編碼成2位元組(0xC0和0x80);二是補充字元是依照UTF-16拆分為代理對分別編碼的,相關細節可以看這裡(變種UTF-8)。

屬性表用於描述某些場景專有的信息,Class檔案、欄位表和方法表都有對應的屬性表集合。 Java虛擬機器規格定義了多種屬性,ClassAnalyzer目前實作了對常用屬性的解析。和常數類型的資料項目不同,屬性並沒有一個tag來識別屬性的類型,但是每個屬性都包含有一個u2類型的attribute_name_index,attribute_name_index指向常數池中的一個CONSTANT_Utf8_info類型的常數,該常數包含著屬性的名稱。在解析屬性時,ClassAnalyzer正是透過attribute_name_index所指向的常數對應的屬性名稱來得知屬性的類型。

欄位表格用於描述類別或介面中宣告的變數,欄位包括類別層級變數以及實例層級變數。欄位表的結構包含一個u2類型的access_flags、一個u2類型的name_index、一個u2類型的descriptor_index、一個u2類型的attributes_count和attributes_count個attribute_info類型的attributes。我們已經介紹了屬性表的解析,attributes的解析方式與屬性表的解析方式一致。

Class的文件方法表采用了和字段表相同的存储格式,只是access_flags对应的含义有所不同。方法表包含着一个重要的属性:Code属性。Code属性存储了Java代码编译成的字节码指令,在ClassAnalyzer中,Code对应的Java类如下所示(仅列出了类属性)


public class Code extends BasicAttributeInfo {
 private short maxStack;
 private short maxLocals;
 private long codeLength;
 private byte[] code;
 private short exceptionTableLength;
 private ExceptionInfo[] exceptionTable;
 private short attributesCount;
 private BasicAttributeInfo[] attributes;
 ...
 private class ExceptionInfo {
  public short startPc;
  public short endPc;
  public short handlerPc;
  public short catchType;
   ...
 }
}

在Code属性中,codeLength和code分别用于存储字节码长度和字节码指令,每条指令即一个字节(u1类型)。在虚拟机执行时,通过读取code中的一个个字节码,并将字节码翻译成相应的指令。另外,虽然codeLength是一个u4类型的值,但是实际上一个方法不允许超过65535条字节码指令。

代码实现

ClassAnalyzer的源码已放在了GitHub上。在ClassAnalyzer的README中,我以一个类的Class文件为例,对该Class文件的每个字节进行了分析,希望对大家的理解有所帮助。

以上是Java實作Class解析器的方法範例的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn