字節碼生成-ASM

目錄

 

1 ASM介紹

2 JVM

2.1 動態生成/修改字節碼理論基礎

2.2 class文件規範

3 ASM編程模型

3.1 編程模型

3.2 demo

0 參考資料


1 ASM介紹

ASM是一個Java字節碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM可以直接產生二進制class文件,也可以在類被加載入Java虛擬機之前動態改變類行爲。

Java class被存儲在嚴格格式定義的.class文件裏,這些類文件擁有足夠的元數據來解析類中的所有元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM從類文件中讀入信息後,能夠改變類行爲,分析類信息,甚至能夠根據用戶要求生成新類。

目前許多框架如cglib、Hibernate、Spring都直接或間接地使用ASM操作字節碼,有些語言如Jython、JRuby、Groovy也是如此。而類ASM字節碼工具還有:

  1. BCEL:Byte Code Engineering Library (BCEL),這是Apache Software Foundation 的Jakarta 項目的一部分。BCEL是 Java classworking 最廣泛使用的一種框架,它可以讓您深入 JVM 彙編語言進行類操作的細節。BCEL與Javassist 有不同的處理字節碼方法,BCEL在實際的JVM 指令層次上進行操作(BCEL擁有豐富的JVM 指令級支持)而Javassist 所強調的源代碼級別的工作。

  2. JBET:通過JBET(Java Binary Enhancement Tool )的API可對Class文件進行分解,重新組合,或被編輯。JBET也可以創建新的Class文件。JBET用一種結構化的方式來展現Javabinary (.class)文件的內容,並且可以很容易的進行修改。

  3. Javassist:Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。是由東京技術學院的數學和計算機科學系的 Shigeru Chiba 所創建的。它已加入了開放源代碼JBoss 應用服務器項目,通過使用Javassist對字節碼操作爲JBoss實現動態AOP框架。

  4. cglib:是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口,cglib封裝了asm,可以在運行期動態生成新的 class,Hibernate和Spring都用到過它。cglib用於AOP,jdk中的proxy必須基於接口,cglib卻沒有這個限制。

而ASM與cglib、serp和BCEL相比,ASM有以下的優點 :

  • ASM 具有簡單、設計良好的 API,這些 API 易於使用;

  • ASM 有非常良好的開發文檔,以及可以幫助簡化開發的 Eclipse 插件;

  • ASM 支持 Java 6(ASM3)、Java7(ASM4)、Java(ASM5);

  • ASM 很小、很快、很健壯;

  • ASM 有很大的用戶羣,可以幫助新手解決開發過程中遇到的問題;

  • ASM 的開源許可可以讓你幾乎以任何方式使用它;

 

2 JVM

2.1 動態生成/修改字節碼理論基礎

 

 

從過程上來說,只要在提供給classLoader合法的class字節碼,就完全可以。

 

2.2 class文件規範

從上圖中可以看到,一個 Java 字節碼文件大致可以歸爲 10 個項:

 

  • Magic:該項存放了一個 Java 字節碼文件的魔數(magic number)和版本信息。一個 Java 字節碼文件的前 4 個字節被稱爲它的魔數。每個正確的 Java 字節碼文件都是以 0xCAFEBABE 開頭的,這樣保證了 Java 虛擬機能很輕鬆的分辨出 Java 文件和非 Java 文件。

  • Version:該項存放了 Java 字節碼文件的版本信息,它對於一個 Java 文件具有重要的意義。因爲 Java 技術一直在發展,所以字節碼文件的格式也處在不斷變化之中。字節碼文件的版本信息讓虛擬機知道如何去讀取並處理該字節碼文件。

  • Constant Pool:該項存放了類中各種文字字符串、類名、方法名和接口名稱、final 變量以及對外部類的引用信息等常量。虛擬機必須爲每一個被裝載的類維護一個常量池,常量池中存儲了相應類型所用到的所有類型、字段和方法的符號引用,因此它在 Java 的動態鏈接中起到了核心的作用。常量池的大小平均佔到了整個類大小的 60% 左右。

  • Access_flag:該項指明瞭該文件中定義的是類還是接口(一個 class 文件中只能有一個類或接口),同時還指名了類或接口的訪問標誌,如 public,private, abstract 等信息。

  • This Class:指向表示該類全限定名稱的字符串常量的指針。

  • Super Class:指向表示父類全限定名稱的字符串常量的指針。

  • Interfaces:一個指針數組,存放了該類或父類實現的所有接口名稱的字符串常量的指針。以上三項所指向的常量,特別是前兩項,在我們用 ASM 從已有類派生新類時一般需要修改:將類名稱改爲子類名稱;將父類改爲派生前的類名稱;如果有必要,增加新的實現接口。

  • Fields:該項對類或接口中聲明的字段進行了細緻的描述。需要注意的是,fields 列表中僅列出了本類或接口中的字段,並不包括從超類和父接口繼承而來的字段。

  • Methods:該項對類或接口中聲明的方法進行了細緻的描述。例如方法的名稱、參數和返回值類型等。需要注意的是,methods 列表裏僅存放了本類或本接口中的方法,並不包括從超類和父接口繼承而來的方法。使用 ASM 進行 AOP 編程,通常是通過調整 Method 中的指令來實現的。

  • Class attributes:該項存放了在該文件中類或接口所定義的屬性的基本信息。

事實上,使用 ASM 動態生成類,不需要像早年的 class hacker 一樣,熟知 class 文件的每一段,以及它們的功能、長度、偏移量以及編碼方式。ASM 會給我們照顧好這一切的,我們只要告訴 ASM 要改動什麼就可以了 —— 當然,我們首先得知道要改什麼:對字節碼文件格式瞭解的越多,我們就能更好地使用 ASM 這個利器。

3 ASM編程模型

3.1 編程模型

ASM 提供了兩種編程模型:

  • Core API,提供了基於事件形式的編程模型。該模型不需要一次性將整個類的結構讀取到內存中,因此這種方式更快,需要更少的內存。但這種編程方式難度較大。

  • Tree API,提供了基於樹形的編程模型。該模型需要一次性將一個類的完整結構全部讀取到內存當中,所以這種方法需要更多的內存。這種編程方式較簡單。

Core API 中操縱字節碼的功能基於 ClassVisitor 接口。這個接口中的每個方法對應了 class 文件中的每一項。Class 文件中的簡單項的訪問使用一個單獨的方法,方法參數描述了這個項的內容。而那些具有任意長度和複雜度的項,使用另外一類方法,這類方法會返回一個輔助的 Visitor 接口,通過這些輔助接口的對象來完成具體內容的訪問。例如 visitField 方法和 visitMethod 方法,分別返回 FieldVisitor 和 MethodVisitor 接口的對象。

ASM 提供了三個基於 ClassVisitor 接口的類來實現 class 文件的生成和轉換:

  • ClassReader:ClassReader 解析一個類的 class 字節碼,該類的 accept 方法接受一個 ClassVisitor 的對象,在 accept 方法中,會按上文描述的順序逐個調用 ClassVisitor 對象的方法。它可以被看做事件的生產者。

  • ClassAdapter:ClassAdapter 是 ClassVisitor 的實現類。它的構造方法中需要一個 ClassVisitor 對象,並保存爲字段 protected ClassVisitor cv。在它的實現中,每個方法都是原封不動的直接調用 cv 的對應方法,並傳遞同樣的參數。可以通過繼承 ClassAdapter 並修改其中的部分方法達到過濾的作用。它可以看做是事件的過濾器。

  • ClassWriter:ClassWriter 也是 ClassVisitor 的實現類。ClassWriter 可以用來以二進制的方式創建一個類的字節碼。對於 ClassWriter 的每個方法的調用會創建類的相應部分。例如:調用 visit 方法就是創建一個類的聲明部分,每調用一次 visitMethod 方法就會在這個類中創建一個新的方法。在調用 visitEnd 方法後即表明該類的創建已經完成。它最終生成一個字節數組,這個字節數組中包含了一個類的 class 文件的完整字節碼內容 。可以通過 toByteArray 方法獲取生成的字節數組。ClassWriter 可以看做事件的消費者。

通常情況下,它們是組合起來使用的。

3.2 demo

class CustomVisitor extends ClassVisitor implements Opcodes {

    public CustomVisitor(int api, ClassVisitor cv) {
        super(api, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (name.equals("sayHello")) {
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
        }
        return mv;
    }
}

 

 

 

  1. java org.objectweb.asm.util.ASMifier xxx 用來輸出class文件的asm形式;因此我們可以用文件比對工具找出原始class文件和修改後的class文件的區別,一次來編寫asm visitor程序

 

0 參考資料

https://blog.csdn.net/saifeng/article/details/46238387

發佈了87 篇原創文章 · 獲贊 20 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章