Java位元組碼增強技術

阿凡盧發表於2021-07-29

簡單介紹下幾種java位元組碼增強技術。

ASM

ASM是一個Java位元組碼操控框架,它能被用來動態生成類或者增強既有類的功能。ASM可以直接產生class檔案,也可以在類被載入入Java虛擬機器之前動態改變類行為。ASM從類檔案中讀入資訊後,能夠改變類行為,分析類資訊,甚至能夠根據使用者要求生成新類。

主頁:https://asm.ow2.io/index.html

ASM框架中的核心類有以下幾個:

  ①  ClassReader:該類用來解析編譯過的class位元組碼檔案。

  ②  ClassWriter:該類用來重新構建編譯後的類,比如說修改類名、屬性以及方法,甚至可以生成新的類的位元組碼檔案。

  ③  ClassAdapter:該類也實現了ClassVisitor介面,它將對它的方法呼叫委託給另一個ClassVisitor物件。

參考程式碼:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class GeneratorClass {

    public static void main(String[] args) throws IOException {
        //生成一個類只需要ClassWriter元件即可
        ClassWriter cw = new ClassWriter(0);
        //通過visit方法確定類的頭部資訊
        cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT+Opcodes.ACC_INTERFACE,
                "com/asm3/Comparable", null, "java/lang/Object", new String[]{"com/asm3/Mesurable"});
        //定義類的屬性
        cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
                "LESS", "I", null, new Integer(-1)).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
                "EQUAL", "I", null, new Integer(0)).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
                "GREATER", "I", null, new Integer(1)).visitEnd();
        //定義類的方法
        cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT, "compareTo",
                "(Ljava/lang/Object;)I", null, null).visitEnd();
        cw.visitEnd(); //使cw類已經完成
        //將cw轉換成位元組陣列寫到檔案裡面去
        byte[] data = cw.toByteArray();
        File file = new File("D://Comparable.class");
        FileOutputStream fout = new FileOutputStream(file);
        fout.write(data);
        fout.close();
    }
}

Javassist

Javassist是一個開源的分析、編輯和建立Java位元組碼的類庫。

它已加入了開放原始碼JBoss應用伺服器專案,通過使用Javassist對位元組碼操作為JBoss實現動態"AOP"框架。

主頁:http://www.javassist.org/

利用Javassist實現位元組碼增強時,可以無須關注位元組碼刻板的結構,其優點就在於程式設計簡單。直接使用java編碼的形式,而不需要了解虛擬機器指令,就能動態改變類的結構或者動態生成類。其中最重要的是ClassPool、CtClass、CtMethod、CtField這四個類:

  • CtClass(compile-time class):編譯時類資訊,它是一個class檔案在程式碼中的抽象表現形式,可以通過一個類的全限定名來獲取一個CtClass物件,用來表示這個類檔案。
  • ClassPool:從開發視角來看,ClassPool是一張儲存CtClass資訊的HashTable,key為類名,value為類名對應的CtClass物件。當我們需要對某個類進行修改時,就是通過pool.getCtClass(“className”)方法從pool中獲取到相應的CtClass。
  • CtMethod、CtField:這兩個比較好理解,對應的是類中的方法和屬性。

參考程式碼:

import javassist.*;

public class CreatePerson {

    public static void createPseson() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        // 1. 建立一個空類
        CtClass cc = pool.makeClass("com.test.javassist.Person");

        // 2. 新增一個欄位 private String name;
        // 欄位名為name
        CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
        // 訪問級別是 private
        param.setModifiers(Modifier.PRIVATE);
        // 初始值是 "xiaoming"
        cc.addField(param, CtField.Initializer.constant("xiaoming"));

        // 3. 生成 getter、setter 方法
        cc.addMethod(CtNewMethod.setter("setName", param));
        cc.addMethod(CtNewMethod.getter("getName", param));

        // 4. 新增無參的建構函式
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
        cons.setBody("{name = \"xiaohong\";}");
        cc.addConstructor(cons);

        // 5. 新增有參的建構函式
        cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
        // $0=this / $1,$2,$3... 代表方法引數
        cons.setBody("{$0.name = $1;}");
        cc.addConstructor(cons);

        // 6. 建立一個名為printName方法,無引數,無返回值,輸出name值
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{System.out.println(name);}");
        cc.addMethod(ctMethod);

        //這裡會將這個建立的類物件編譯為.class檔案
        cc.writeFile("/Users/yangyue/workspace/springboot-learn/java-agent/src/main/java/");
    }

    public static void main(String[] args) {
        try {
            createPseson();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Byte Buddy

Byte Buddy是一個程式碼生成和操作庫,用於在Java應用程式執行時建立和修改Java類,而無需編譯器的幫助。
除了Java類庫附帶的程式碼生成實用程式外,Byte Buddy還允許建立任意類,並且不限於實現用於建立執行時代理的介面。
此外,Byte Buddy提供了一種方便的API,可以使用Java代理或在構建過程中手動更改類。

主頁:https://bytebuddy.net/#/

參考程式碼:

Class<?> dynamicType = new ByteBuddy()
  .subclass(Object.class)
  .method(ElementMatchers.named("toString"))
  .intercept(FixedValue.value("Hello World!"))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded();
 
assertThat(dynamicType.newInstance().toString(), is("Hello World!"));

JVM-SANDBOX

JVM沙箱容器,一種JVM的非侵入式執行期AOP解決方案:

  1. 動態增強類你所指定的類,獲取你想要的引數和行資訊甚至改變方法執行。
  2. 動態可插拔容器框架。

主頁:https://github.com/alibaba/jvm-sandbox

 

參考:

https://www.jianshu.com/p/b72f66da679f

https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html

 

相關文章