本博客主要介紹通過 Javassist、ASM 操作 Java 位元組碼。 Class 文件是什麼 通常對於用 idea 的同學來說,class 文件是直接可以查看的,可以看到像 java 那樣的代碼。其實 class 文件是一種位元組碼文件,我們平時在 idea 所看到的,是 idea 自動反編譯後的 ...
本博客主要介紹通過 Javassist、ASM 操作 Java 位元組碼。
Class 文件是什麼
通常對於用 idea 的同學來說,class 文件是直接可以查看的,可以看到像 java 那樣的代碼。其實 class 文件是一種位元組碼文件,我們平時在 idea 所看到的,是 idea 自動反編譯後的結果。如果把 class 文件用 sublime 打開,就會看到許多位元組碼,而不是 Java 代碼了。像這樣:
cafe babe 0000 0034 0017 0100 1163 6e2f 6863 6873 7475 6469 6f2f 5573 6572 0700 0101 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0700 0301 0004 6e61 6d65 0100 ......
Class文件是一組以 8 位位元組為基礎單位的二進位流,各個數據項目嚴格按照順序緊湊排列在 Class 文件中,中間無任何分隔符。
我們這裡所說的操作 Java 位元組碼,就是操作修改 class 文件內容。
Why
同學們可能會有這樣一個疑問,為什麼要操作 Java 位元組碼,直接改 java 文件不是很好嗎?
很多情況下是無法操作的 java 文件的,或者使用修改位元組碼的方式更方便:
- 在第三方依賴中加入一些檢測數據
- AOP 操作,例如 Android 自動埋點統計
- Spring 框架的 AOP 操作使用 ASM 操作 Java 位元組碼
總的來說,可以更方便開發,也同時瞭解一些底層的原理。
Javassist
Javassist是一個開源的分析、編輯和創建Java位元組碼的類庫。是由東京工業大學的數學和電腦科學系的 Shigeru Chiba(千葉 滋)所創建的。它已加入了開放源代碼 JBoss 應用伺服器項目,通過使用Javassist對位元組碼操作為 JBoss 實現動態”AOP”框架。
導包
compile group: 'org.javassist', name: 'javassist', version: '3.23.1-GA'
類搜索路徑版本號可能不是最新的,想要最新的話查找 Maven 倉庫獲取最新的版本號即可。
通過 ClassPool.getDefault() 獲取的 ClassPool 使用 JVM 的類搜索路徑。如果程式運行在 JBoss 或者 Tomcat 等 Web 伺服器上,ClassPool 可能無法找到用戶的類,因為 Web 伺服器使用多個類載入器作為系統類載入器。在這種情況下,ClassPool 必須添加額外的類搜索路徑。
下麵的例子中,pool 代表一個 ClassPool 對象:
pool.insertClassPath(new ClassClassPath(this.getClass()));
上面的語句將 this 指向的類添加到 pool 的類載入路徑中。你可以使用任意 Class 對象來代替 this.getClass(),從而將 Class 對象添加到類載入路徑中。傳參支持 ClassPath、URLClassPath、ByteArrayClassPath 類型。
編輯
// 創建 User 類 CtClass ctClass = classPool.makeClass("cn.hchstudio.User"); // 獲取 String 類 CtClass CtString = classPool.get("java.lang.String");
通過 makeClass 和 get 方法可以分別創建、獲取 CtClass,進而操作類。
上面的語句是創建一個變數,new CtField 中分別傳入類型、名稱、ctClass。setModifiers 設置變數修飾符;addField 表示把變數加入到這個類中。
CtField name = new CtField(CtString, "name", ctClass); name.setModifiers(Modifier.PRIVATE); ctClass.addField(name);
對於已存在的方法,可以使用 insertBefore、insertAfter 方法插入到方法函數之後或之後。Javassist 有一個簡單除暴的新增方法方式,就是直接把要寫的 java 代碼變為字元串,之後 Javassist 便可自動完成代碼校驗,轉為位元組碼的過程。
ctMethod.insertBefore("System.out.println(\"lalala\");"); ctMethod.insertAfter("System.out.println(\"lalala\");");
一個慄子
舉一個慄子,這裡通過 Javassist 生成一個 User 類,其中包括 name、sex 屬性,並有其 set、get 方法。並且輸出到 ./out/production/classes 目錄下。
ClassPool classPool = ClassPool.getDefault(); try { CtClass ctClass = classPool.makeClass("cn.hchstudio.User"); CtClass CtString = classPool.get("java.lang.String"); CtField name = new CtField(CtString, "name", ctClass); name.setModifiers(Modifier.PRIVATE); ctClass.addField(name); CtField sex = new CtField(CtString, "sex", ctClass); sex.setModifiers(Modifier.PRIVATE); ctClass.addField(sex); CtMethod setName = new CtMethod(CtClass.voidType, "setName", new CtClass[]{CtString}, ctClass); setName.setModifiers(Modifier.PUBLIC); setName.setBody("name = $1;"); ctClass.addMethod(setName); CtMethod getName = new CtMethod(CtString, "getName", new CtClass[]{}, ctClass); getName.setModifiers(Modifier.PUBLIC); getName.setBody("return name;"); ctClass.addMethod(getName); CtMethod setSex = CtMethod.make("public void setSex(java.lang.String sex){" + "this.sex = sex;" + "}", ctClass); ctClass.addMethod(setSex); CtMethod getSex = new CtMethod(CtString, "getSex", new CtClass[]{}, ctClass); getSex.setModifiers(Modifier.PUBLIC); getSex.setBody("return sex;"); ctClass.addMethod(getSex); ctClass.writeFile("./out/production/classes"); } catch (Exception e) { System.out.println(e.toString()); e.printStackTrace(); }
ASM
ASM 也是一個操作 Java 位元組碼的框架,相比於 Javassist,它更加底層、輕量級、速度也快,不過在編寫代碼的時候可能容易出錯,它需要我們直接寫 Java 位元組碼。
Java jdk 自帶了 ASM 的依賴,在 rt.jar!/jdk/internal/org/objectweb/asm 下。
Android 環境下則需要自己導入依賴,因為 Android 去掉了 rt.jar!/jdk 包。
編輯
ASM 編輯代碼則比較複雜,需要對彙編有一定瞭解的同學才可以。
通常的方式是我們需要用 java 寫出一個想要自動生成的類,然後查看他的 class 位元組碼
mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, "cn/hchstudio/User", "name", "Ljava/lang/String;"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd();
這裡改出一段示例代碼,意思為新建一個 setName 方法,並給 name 屬性賦值。