簡體   English   中英

如何使用 ASM 為初始值設定項添加靜態最終字段?

[英]How to add static final field with initializer using ASM?

我想使用ASM將靜態最終字段添加到.class文件中,源文件是

public class Example {

    public Example(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    private final int code;

}

反編譯生成的類應該是這樣的:

public class Example {

    public static final Example FIRST = new Example(1);

    public static final Example SECOND = new Example(2);

    public Example(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    private final int code;

}

最后,我想使用 ASM 將 FIRST 和 SECOND 常量添加到 .class 文件中,我該怎么做?

這個答案顯示了如何使用 ASM 的訪問者 api(請參閱ASM 主頁上的ASM 4.0 A Java 字節碼工程庫的第 2.2 節)來完成它,因為它是我最熟悉的 api。 ASM 也有一個對象模型 api(請參閱同一文檔中的第二部分)變體,在這種情況下通常更容易使用。 對象模型可能有點慢,因為它在內存中構建了整個類文件的樹,但如果只有少量類需要轉換,那么性能損失應該可以忽略不計。

當創建值不是常量(如數字)的static final字段時,它們的初始化實際上轉到“ 靜態初始化塊”。 因此,您的第二個(轉換后的)代碼清單等效於以下 java 代碼:

public class Example {

  public static final Example FIRST;

  public static final Example SECOND;

  static {
    FIRST = new Example(1);
    SECOND = new Example(2);
  }

  ...
}

在一個 java 文件中,你可以有多個這樣的靜態 {... } 塊,而在類文件中只能有一個。 java編譯器自動將多個靜態塊合並為一個來滿足這個需求。 當操作字節碼時,這意味着如果之前沒有靜態塊,那么我們創建一個新的,而如果已經存在一個靜態塊,我們需要將我們的代碼添加到現有代碼的開頭(添加比添加更容易)。

對於 ASM,靜態塊看起來像一個具有特殊名稱<clinit>的靜態方法,就像構造函數看起來像具有特殊名稱<init>的方法一樣。

在使用visitor api時,要知道一個方法是否已經從之前定義過,方法是監聽所有的visitMethod()調用,並檢查每個調用中的方法名。 訪問完所有方法后,將調用 visitEnd() 方法,因此如果到那時還沒有訪問過任何方法,我們就知道需要創建一個新方法。

假設我們有一個 byte[] 格式的原始類,請求的轉換可以這樣完成:

import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;

public static byte[] transform(byte[] origClassData) throws Exception {
  ClassReader cr = new ClassReader(origClassData);
  final ClassWriter cw = new ClassWriter(cr, Opcodes.ASM4);

  // add the static final fields 
  cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "FIRST", "LExample;", null, null).visitEnd();
  cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "SECOND", "LExample;", null, null).visitEnd();

  // wrap the ClassWriter with a ClassVisitor that adds the static block to
  // initialize the above fields
  ClassVisitor cv = new ClassVisitor(ASM4, cw) {
    boolean visitedStaticBlock = false;

    class StaticBlockMethodVisitor extends MethodVisitor {
      StaticBlockMethodVisitor(MethodVisitor mv) {
        super(ASM4, mv);
      }
      public void visitCode() {
        super.visitCode();

        // here we do what the static block in the java code
        // above does i.e. initialize the FIRST and SECOND
        // fields

        // create first instance
        super.visitTypeInsn(NEW, "Example");
        super.visitInsn(DUP);
        super.visitInsn(ICONST_1); // pass argument 1 to constructor
        super.visitMethodInsn(INVOKESPECIAL, "Example", "<init>", "(I)V");
        // store it in the field
        super.visitFieldInsn(PUTSTATIC, "Example", "FIRST", "LExample;");

        // create second instance
        super.visitTypeInsn(NEW, "Example");
        super.visitInsn(DUP);
        super.visitInsn(ICONST_2); // pass argument 2 to constructor
        super.visitMethodInsn(INVOKESPECIAL, "Example", "<init>", "(I)V");
        super.visitFieldInsn(PUTSTATIC, "Example", "SECOND", "LExample;");

        // NOTE: remember not to put a RETURN instruction
        // here, since execution should continue
      }

      public void visitMaxs(int maxStack, int maxLocals) {
        // The values 3 and 0 come from the fact that our instance
        // creation uses 3 stack slots to construct the instances
        // above and 0 local variables.
        final int ourMaxStack = 3;
        final int ourMaxLocals = 0;

        // now, instead of just passing original or our own
        // visitMaxs numbers to super, we instead calculate
        // the maximum values for both.
        super.visitMaxs(Math.max(ourMaxStack, maxStack), Math.max(ourMaxLocals, maxLocals));
      }
    }

    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
      if (cv == null) {
        return null;
      }
      MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
      if ("<clinit>".equals(name) && !visitedStaticBlock) {
        visitedStaticBlock = true;
        return new StaticBlockMethodVisitor(mv);
      } else {
        return mv;
      }
    }

    public void visitEnd() {
      // All methods visited. If static block was not
      // encountered, add a new one.
      if (!visitedStaticBlock) {
        // Create an empty static block and let our method
        // visitor modify it the same way it modifies an
        // existing static block
        MethodVisitor mv = super.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
        mv = new StaticBlockMethodVisitor(mv);
        mv.visitCode();
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
      }
      super.visitEnd();
    }
  };

  // feed the original class to the wrapped ClassVisitor
  cr.accept(cv, 0);

  // produce the modified class
  byte[] newClassData = cw.toByteArray();
  return newClassData;
}

由於您的問題沒有進一步說明您的最終目標到底是什么,因此我決定使用一個硬編碼的基本示例來處理您的 Example 類案例。 如果您想創建正在轉換的類的實例,則必須更改上面包含“Example”的所有字符串,以使用實際正在轉換的類的完整類名。 或者,如果您特別想要在每個轉換后的類中使用 Example 類的兩個實例,則上面的示例可以按原樣工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM