ASM 字節碼增強框架詳解
1 概述
ASM是Java中比較流行的用來讀寫字節碼的類庫,用來基于字節碼層面對代碼進行分析和轉換。
ASM是一個Java字節碼操縱框架,它能被用來動態生成類或者增強既有類的功能。
ASM可以直接產生二進制class文件,也可在類被加載入虛擬機之前動態改變類行為, ASM從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能根據要求生成新類。目前許多框架如cglib、Hibernate、 Spring都直接或間接使用ASM操作字節碼。
在讀寫的過程中可以加入自定義的邏輯以增強或修改原來已編譯好的字節碼,比如CGLIB用它來實現動態代理。ASM被設計用于在運行時對Java類進行生成和轉換,當然也包括離線處理。ASM短小精悍、且速度很快,從而避免在運行時動態生成字節碼或轉換時對程序速度的影響,又因為它體積小巧,可以在很多內存受限的環境中使用。
ASM的主要優勢包括如下幾個方面:
它又一個很小,但設計良好并且模塊化的API,且易于使用。
它具有很好的文檔,并且還有eclipse插件。
它支持最新的Java版本。
它短小精悍、快速、健壯。
它又一個很大的用戶社區,可以給新用戶提供支持。
它的開源許可允許你幾乎以任何方式來使用它。
編程模型
Core API
提供了基于事件形式的編程模型。該模型不需要一次性將整個類的結構讀取到內存中,因此這種方式更快,需要更少的內存,但這種編程方式難度較大
Tree API
提供了基于樹形的編程模型。該模型需要一次性將一個類的完整結構全部讀取到內存當中,所以這種方法需要更多的內存,這種編程方式較簡單
ASM Core設計
主要有以下幾個類、接口(org.objectweb.asm包):
ClassReader類:字節碼的讀取與分析引擎。它采用類似SAX的事件讀取機制,每當有事件發生時,調用注冊的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相應的處理
ClassVisitor接口:該接口中的每個方法,對應了 class 文件中的每一項。定義在讀取Class字節碼時會觸發的事件,如類頭解析完成、注解解析、字段解析、方法解析等
解析器使ClassVisitor訪問 JVMS 中定義的Class文件結構。 此類解析ClassFile內容,并為遇到的每個字段,方法和字節碼指令調用給定ClassVisitor的適當訪問方法。
ClassAdapter
ClassAdapter是ClassVisitor的實現類,實現要變化的功能
AnnotationVisitor接口:定義在解析注解時會觸發的事件,如解析到一個基本值類型的注解、enum值類型的注解、Array值類型的注解、注解值類型的注解等
FieldVisitor接口:定義在解析字段時觸發的事件,如解析到字段上的注解、解析到字段相關的屬性等。
MethodVisitor接口:定義在解析方法時觸發的事件,如方法上的注解、屬性、代碼等。
ClassWriter類
它實現了ClassVisitor接口,用于拼接字節碼,輸出變化后的字節碼
AnnotationWriter類:它實現了AnnotationVisitor接口,用于拼接注解相關字節碼
FieldWriter類:它實現了FieldVisitor接口,用于拼接字段相關字節碼
MethodWriter類:它實現了MethodVisitor接口,用于拼接方法相關字節碼。
SignatureReader類:對類定義、字段定義、方法定義、本地變量定義的簽名的解析。Signature因范型引入,用于存儲范型定義時的元數據(因為這些元數據在運行時會被擦除)。
SignatureVisitor接口:定義在解析Signature時會觸發的事件,如正常的Type參數、類或接口的邊界等。
SignatureWriter類:它實現了SignatureVisitor接口,用于拼接范型相關字節碼。
Attribute類:字節碼中屬性的類抽象。
ByteVector類:字節碼二進制存儲的容器。
Opcodes接口:字節碼指令的一些常量定義。
Type類:類型相關的常量定義以及一些基于其上的操作。
類圖關系
ClassReader是ASM中最核心的實現,它用于讀取并解析Class字節碼
在構建ClassReader實例時,它首先保存字節碼二進制數組b,然后創建items數組,數組的長度在字節碼數組的第8、9個字節指定(最前面4個字節是魔數CAFEBABE,之后2個字節是次版本號,再后2個字節是主版本號),每個item表示常量池項在字節碼數組的偏移量加1(常量池中每個項由1個字節的type和緊跟的字節數組表示,常量池項有12種類型,其中CONSTANT_FieldRef_Info、CONSTANT_MethodRef_Info、CONSTANT_InterfaceMethodRef_Info、CONSTANT_NameAndType_Info包括其類型字節占用5個字節,另外4個字節每2個字節為字段、方法等所在的類、其名稱、描述符在當前常量池中CONSTANT_Utf8_Info類型的引用;CONSTANT_Integer_Info、CONSTANT_Float_Info包括其類型字節占用5個字節,另外四個字節為其對應的值;CONSTANT_Class_Info、CONSTANT_String_Info包括其類型字節占用3個字節,另外兩個字節為在當前常量池CONSTANT_Utf8_Info項的索引;CONSTANT_Utf8_Info類型第1個字節表示類型,第2、3個字節為該項所表示的字符串的長度);CONSTANT_Double_Info、CONSTANT_Long_Info加類型字節為9個字;maxStringLength表示最長的UTF8類型的常量池項的值,用于決定在解析CONSTANT_Utf8_Info類型項時最大需要的字符數組;header表示常量池之后的字節碼的第一個字節。
ASM 開發
IDEA使用插件 asm outline 查看 asm 如何生成該類。
類實現
對應的 asm 編程代碼
package asm.com.javaedge.asm; import java.util.*; import org.objectweb.asm.*; public class TestAsmDump implements Opcodes { public static byte[] dump() throws Exception { ClassWriter cw = new ClassWriter(0); FieldVisitor fv; MethodVisitor mv; AnnotationVisitor av0; cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/javaedge/asm/TestAsm", null, "java/lang/Object", null); cw.visitSource("TestAsm.java", null); { mv = cw.visitMethod(ACC_PUBLIC, "
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
看看最簡單的統計方法執行時間
... mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitVarInsn(LSTORE, 1); ... mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("Hello ASM"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitLabel(l0); mv.visitLineNumber(13, l0); mv.visitLdcInsn(new Long(100L)); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false); mv.visitLabel(l1); mv.visitLineNumber(16, l1); Label l5 = new Label(); mv.visitJumpInsn(GOTO, l5); mv.visitLabel(l2); mv.visitLineNumber(14, l2); mv.visitFrame(Opcodes.F_FULL, 2, new Object[]{"com/javaedge/asm/TestAsm", Opcodes.LONG}, 1, new Object[]{"java/lang/InterruptedException"}); mv.visitVarInsn(ASTORE, 3); Label l6 = new Label(); mv.visitLabel(l6); mv.visitLineNumber(15, l6); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false); mv.visitLabel(l5); mv.visitLineNumber(17, l5); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitVarInsn(LSTORE, 3); Label l7 = new Label(); mv.visitLabel(l7); mv.visitLineNumber(18, l7); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
這樣我們就知道之后用 ASM 編程該加哪些代碼了。
Java 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。