一、什么是ASM
首先看下官方中的說明 ASM a very small and fast Java bytecode manipulation framework。
ASM是一個JAVA字節碼分析、創建和修改的開源應用框架。它可以動態生成二進制格式的stub類或其他代理類,或者在類被JAVA虛擬機裝入內存之前,動態修改類。在ASM中提供了諸多的API用於對類的內容進行字節碼操作的方法。與傳統的BCEL和SERL不同,在ASM中提供了更為優雅和靈活的操作字節碼的方式。ASM相當小巧,並且它有更高的執行效率,是BCEL的7倍,SERP的11倍以上(摘自網絡,具體沒有測試)。目前ASM已被廣泛的開源應用架構所使用,例如:Spring、Hibernate等。
二、ASM能做什么
我們都知道,一般情況下,Class文件是通過javac編譯器產生的,然后通過類加載器加載到虛擬機內,再通過執行引擎去執行。現在我們可以通過ASM的API直接生成符合Java虛擬機規范的Class字節流,這樣,ASM做的事情一定程度上正是javac解釋器做的工作。
可以說ASM分析一個類、從字節碼角度創建一個類、修改一個已經被編譯過的類文件。
那么,我們就可以通過ASM來實現諸如代碼生成,代碼混淆,代碼轉換等等以字節碼為操作目標的工作
三、Java二進制(class)文件的格式
要想駕馭ASM,先要了解一下JAVA的CLASS文件格式。JAVA的CLASS文件通常是樹型結構。根節點包含以下元素:
- ConstantPool:符號表;
- FieldInfo:類中的成員變量信息;
- MethodInfo:類中的方法描述;
- Attribute:可選的附加節點。
FieldInfo節點包含成員變量的名稱,諸如public,private,static等的標志。
ConstantValue屬性用來存儲靜態的不變的成員變量的值。
Deprecated和Synthetic被用來標記一個成員變量是不被推薦的或由編譯器生成的。
MethodInfo節點包含方法的名稱,參數的類型和和它的返回值,方法是公有的,私有的或靜態的等標志。
MethodInfo包含可選的附加屬性,其中最重要的是Code屬性,它包含非抽象的方法的代碼。
Exceptions屬性包含方法將拋出的Exception的名稱。
Deprecated和Synthetic屬性的信息同上面的FieldInfo的定義一樣。
根節點的可選屬性有SourceFile,InnerClasses和Deprecated。
SourceFile用來存儲被編譯成字節碼的源代碼文件的原始名稱;
InnerClasses存儲內部類的信息。由於這些屬性的存在,java 的類格式是可以擴展的,也就是說可以在一個class中 附加一些非標准的屬性, java虛擬機會忽略這些不可識別的屬性,正常的加載這個class。
ConstantPool是一個由數字或字符串常量的索引組成的隊列,或由此類的樹的其他節點引用的,由其他對象創建的被引用常量的索引組成的隊列。這個表的目標是為了減少冗余。例如,FieldInfo節點不包含節點的名稱,只包含它在這一表中的索引。同樣的,GETFIELD和PUTFIELD不直接包含成員變量的名稱,只包含名稱的索引。
初試ASM(hello world):
package com.sunchao.asm; import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; /** * the hello world byte code generate * by asm. * @author Administrator * */ public class HelloWorld { public static void main(String args[]) throws Exception { ClassWriter classWriter = new ClassWriter(0); String className = "com/sunchao/asm/HelloWorld"; classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null); MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); initVisitor.visitCode(); initVisitor.visitVarInsn(Opcodes.ALOAD, 0); initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "V()"); initVisitor.visitInsn(Opcodes.RETURN); initVisitor.visitMaxs(1, 1); initVisitor.visitEnd(); MethodVisitor helloVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayHello", "()V;", null, null); helloVisitor.visitCode(); helloVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); helloVisitor.visitLdcInsn("hello world!"); helloVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); helloVisitor.visitInsn(Opcodes.RETURN); helloVisitor.visitMaxs(1, 1); helloVisitor.visitEnd(); classWriter.visitEnd(); byte[] code = classWriter.toByteArray(); File file = new File("D:\\HelloWorld.class"); FileOutputStream output = new FileOutputStream(file); output.write(code); output.close(); } }