5

ASM を使用して static final フィールドを .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 を使用して .class ファイルに FIRST および SECOND 定数を追加したいのですが、どうすればよいですか?

4

1 に答える 1

23

この回答は、ASM の訪問者 API を使用してそれを行う方法を示しています ( ASM ホームページのASM 4.0 A Java バイトコード エンジニアリング ライブラリのセクション 2.2 を参照)。ASM にはオブジェクト モデル API (同じドキュメントのパート II を参照) バリアントもあり、この場合は一般的に使いやすいと思われます。オブジェクト モデルは、メモリ内にクラス ファイル全体のツリーを構築するため、おそらく少し遅くなりますが、変換が必要なクラスが少量しかない場合、パフォーマンスへの影響は無視できるはずです。

static final値が定数 (数値など) ではないフィールドを作成する場合、それらの初期化は実際には「静的初期化ブロック」に行われます。したがって、2 番目の (変換された) コード リストは、次の Java コードと同等です。

public class Example {

  public static final Example FIRST;

  public static final Example SECOND;

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

  ...
}

Java ファイルでは、そのような静的 { ... } ブロックを複数持つことができますが、クラス ファイルでは 1 つしか持てません。Java コンパイラは、この要件を満たすために、複数の静的ブロックを 1 つに自動的にマージします。バイトコードを操作する場合、以前の静的ブロックがない場合は新しいブロックを作成し、静的ブロックが既に存在する場合は、コードを既存のブロックの先頭に追加する必要があります (追加するよりも前に追加する方が簡単です)。

<clinit>ASM では、コンストラクターが特別な名前のメソッドのように見えるのと同様に、静的ブロックは特別な名前の静的メソッドのように見えます<init>

ビジター 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 クラスの 2 つのインスタンスが特に必要な場合は、上記の例がそのまま機能します。

于 2013-02-07T17:45:13.627 に答える