ASM—JAVA代碼產生

來源:互聯網
上載者:User

這裡要說的ASM,並不是指組合語言,而是一個操作Java bytecode的架構。對於Java平台而言,bytecode便是它的“組合語言”,所以,ASM這個名字倒也算是實至名歸。ASM本身很強大,有不少軟體和架構選擇它作為底層的實現,比如cglib。在這篇blog中,主要來關注一下它在代碼產生方面的威力。

在起步階段,Hello World總是一個很好的選擇,也就是說,我們產生的目標代碼是這樣的:
public class AsmExample {
    public static void main(String[] args) {
        System.out.println("Hello, World");
    }
}

在Java中,代碼是以類的形式進行組織的,.class檔案便是bytecode的載體。對照上面這段代碼,首先,我們需要一個類。
public class AsmMain {
    public static void main(String[] args) {
        ClassWriter cw = new ClassWriter(true);
        cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "AsmExample", null, "java/lang/Object", null);

        ...

        cw.visitEnd();
    }
}

在上面這段代碼中,ClassWriter就是ASM中用來產生bytecode形式的類。在這裡,我們要為我們產生的類設定一些屬性,比如類名、存取層級和超類,以及在bytecode層次上需要的版本號碼等等。至此,對應的Java代碼如下:
public class AsmExample {
}

有了類,接下來就是對應的方法了,先來看看基本的結構:
        Method m = Method.getMethod("void main (String[])");
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, m, null, null, cw);
        ...
        mg.returnValue();
        mg.endMethod();
首先我們設定了一個方法的簽名,包括方法名,參數和傳回型別。我們要產生這個方法,還需要設定一些方法的屬性,比如存取層級等等,通過cw這個參數,方法同類關聯在了一起。到這裡,對應的Java代碼是這樣的:
public class AsmExample {
    public static void main(String args[]) {
    }
}

前面所做的都是搭建靜態結構的工作,接下來,我們要進入的才是讓程式動起來的部分。
    mg.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
    mg.push("Hello world!");
    mg.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println (String)"));

在這裡,我們面對的實際上是JVM的指令,所以,如果面對彙編一樣,所有的一切都一步一步說清楚。

首先是獲得System.out。我們通過getStatic這個方法實現,它表示從哪個類中取出哪個static欄位,其類型是什麼。而且實際上,這條指令執行的結果是將這個取出的欄位推到了堆棧上。隨後,我們在把“Hello world!”也推入堆棧之中,很顯然,這一切都是在為調用方法做準備。

對於參數(這裡的“Hello world!”)入棧,我們很容易接受,但為什麼要把System.out也送入堆棧呢?再次提醒一下,這裡我們是在JVM一級進行思考,在這裡,方法調用被打回了最原始的形態,在Java程式中被隱藏的this這時也要作為參數顯式傳遞,也就是說,方法調用變成了這樣:
    println(System.out, "Hello world!");

萬事俱備,調用方法。在Java中,方法調用需要區分類方法和執行個體方法,它們在虛擬機器中有著不同的指令,這裡我們要調用的是執行個體的方法,所以,這裡用的是invokeVirtual,指定了類型,指定了方法,方法就可以調用了。如果要調用類的方法,也就是static方法,那就需要讓invokeStatic上陣了。

對比一下invokeVirtual和invokeStatic的API定義,我們不難發現,它們之間實際上沒有什麼區別,之所以要弄出兩個來,與Java的設計不無關係,它把屬於類的東西看作了一種特殊的東西,沒有統一到對象體系之中。如果為Ruby設計虛擬機器,可以消除這樣的問題,因為在Ruby中,類的方法就是類對象的執行個體方法,這樣將類的東西統一到對象體系之中,不必額外區分。

到這裡,我們的目標便已完全實現:
public class AsmExample {
    public static void main(String args[]) {
        System.out.println("Hello world!");
    }
}

之後,我們可以把定義的類轉為位元組,至於是載入到虛擬機器中運行,還是儲存到檔案中,那就由自己的喜好了。
    byte[] code = cw.toByteArray();

和ASM打交道,需要我們放低自己姿態,站在指令一級進行思考。比如,在這個層次上,實現判斷語句,就需要設定label,然後進行相應的跳轉;這裡沒有迴圈語句,需要自己用判斷加跳轉打造迴圈結構。不過,總的來說,很容易同Java程式對應上,就像我們上面所做的那樣。《深入Java虛擬機器》可以讓我們更好的瞭解JVM,也可以讓協助我們更好的理解ASM的程式。

有幾個幫手可以讓我們更好進行bytecode產生這個遊戲。javap,JDK帶的一個工具,可以用來反組譯碼Java bytecode。在接觸ASM的最初,我們對指令不是很熟悉的時候,可以考慮先把自己的目標寫成Java程式,編譯之後用“javap -c”來查看,所有的指令便一覽無餘,我們就可以照方抓藥了。jad,它為我們提供了一個將Java class檔案反編譯為Java檔案,通過它,我們就可以知道產生的bytecode究竟是不是自己想要的,我所展示與產生過程對應的Java代碼便是藉助於jad的力量完成的。

ASM很強大,這裡只介紹了ASM中的代碼產生,實際上,就連代碼產生這一項工作介紹的都不那麼完整,ASM還提供了另外一種產生方式,不過,用起來不如這裡的GeneratorAdapter,需要更多的JVM指令的智慧,優勢在於速度稍快一些。

讀後感:這玩意沒怎麼把玩過,但我知道為了逃避開原始碼使用問題,為了要改開原始碼,使用ASM動態修改class檔案。自己寫的代碼根本就根本使用ASM那麼麻煩了,ASM畢竟犧牲效率為代價。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.