關於Java字節代碼的操縱的一些事

在一般的Java應用開發過程中,開發人員使用Java的方式比較簡單。打開慣用的IDE,編寫Java源代碼,再利用IDE提供的功能直接運行Java 程序就可以了。這種開發模式背後的過程是:開發人員編寫的是Java源代碼文件(.java),IDE會負責調用Java的編譯器把Java源代碼編譯成平臺無關的字節代碼(byte code),以類文件的形式保存在磁盤上(.class)。Java虛擬機(JVM)會負責把Java字節代碼加載並執行。

關於Java字節代碼的操縱的一些事

從Java源碼對應到Java字節碼的例子

例子前半是Java代碼,後面的註釋是對應的Java字節碼,每行一條指令。每條指令後面我還加了註釋來表示執行完該指令後操作數棧的狀態,就像JVM規範的記法一樣,左邊是棧底右邊是棧頂,省略號表示不關心除棧頂附近幾個值之外操作數棧上的值。

讀取一個局部變量用load係指令。

local_var_0

// // ... ->

// iload_0 // ..., value0

是類型前綴,有

b: byte

s: short

c: char

i: int

l: long

f: float

d: double

a: 引用類型

load後面跟的參數是局部變量所在的位置(slot number)。其中對0到3的slot有特化的簡短指令,例如iload_0。4和以上就用通用的load指令,例如iload 4。

關於Java字節代碼的操縱的一些事

存儲一個局部變量用store係指令。

local_var_0 = ...

// // ..., value0 ->

// istore_0 // ...

合併起來:

local_var_1 = local_var_0;

// // ... ->

// iload_0 // ..., value0 ->

// istore_1 // ...

二元算術運算:

... + ...

// // ..., value1, value2 ->

// iadd // ..., sum

結合讀取局部變量:

local_var_0 + local_var_1

// // ... ->

// iload_0 // ..., value0 ->

// iload_1 // ..., value0, value1 ->

// iadd // ..., sum

結合保存到局部變量:

local_var_2 = local_var_0 + local_var_1;

// // ... ->

// iload_0 // ..., value0 ->

// iload_1 // ..., value0, value1 ->

// iadd // ..., sum ->

// istore_2 // ...

連續加兩次:

local_var_3 = local_var_0 + local_var_1 + local_var_2

關於Java字節代碼的操縱的一些事

// // ... ->

// iload_0 // ..., value0 ->

// iload_1 // ..., value0, value1 ->

// iadd // ..., sum1 ->

// iload_2 // ..., sum1, value2 ->

// iadd // ..., sum2 ->

// istore_3 // ...

返回結果:

return ...;

// // ..., value ->

// ireturn // ...

返回一個局部變量:

return local_var_0;

// // ... ->

// iload_0 // ..., value0 ->

// ireturn // ...

返回一個加法:

return local_var_0 + local_var_0

// // ... ->

// iload_0 // ..., value0 ->

// dup // ..., value0, value0 ->

// iadd // ..., sum ->

// ireturn // ...

const_、bipush、sipush、ldc這些指令都用於向操作數棧壓入常量。例如:

1 // iconst_1

true // iconst_1 // JVM的類型系統裡,整型比int窄的類型都統一帶符號擴展到int來表示

127 // bipush 127 // 能用一個字節表示的帶符號整數常量

1234 // sipush 1234 // 能用兩個字節表示的帶符號整數常量

12.5 // ldc 12.5 // 較大的整型常量、float、double、字符串常量用ldc

創建一個對象,用空參數的構造器:

new Object()

關於Java字節代碼的操縱的一些事

// // ... ->

// new java/lang/Object // ..., ref ->

// dup // ..., ref, ref ->

// invokespecial java/lang/Object.()V // ..., ref

在字節代碼中,Java方法體是由一系列的指令組成的。而要做的是生成調用System.out.println方法的指令,並把這些指令插入到指令集合的最前面。ASM對這些指令做了抽象,不過熟悉全部的指令比較困難。ASM提供了一個工具類ASMifierClassVisitor,可以打印出Java類的字節代碼的結構信息。當需要增強某個類的時候,可以先在源代碼上做出修改,再通過此工具類來比較修改前後的字節代碼的差異,從而確定該如何編寫增強的代碼。

源代碼生成之後,就已經成為了程序的一部分,開發人員需要去維護它:要麼手工修改生成出來的源代碼,要麼重新生成。而字節代碼的增強過程,對於開發人員是完全透明的。妥善使用Java字節代碼的操縱技術,可以更好的解決某一類開發問題。

關於Java字節代碼的操縱的一些事

尚學堂12大精英團隊+各類實戰項目,真正實現1+1>10的目標效果。幫助學員迅速成長,持久騰飛,成就學員“高富帥”人生;幫助企業技術和團隊成長,成就百年中華名企;助力中國持續成為世界強國而貢獻力量。尚學堂12大精英團隊,覆蓋IT行業十大領域,實戰團隊240人,服務學員累計超過10萬人,就業合作企業數量500+。


分享到:


相關文章: