5

私はASMを使用しての.classファイルに静的最終フィールドを追加したい、とソースファイルがASMを使用してイニシャライザでstatic finalフィールドを追加する方法はありますか?

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定数を追加したいのですが、どうすればいいですか?それは私にとって最も身近なAPIだから

+0

これはJavaですか? manen-assembly-pluginに関する質問ですか?その後、それをタグ付けします。 –

答えて

17

この答えは(ASM homepageASM 4.0 A Javaバイトコードエンジニアリングライブラリのセクション2.2を参照)、それはASMの訪問者APIを使用して行うことができる方法を示しています。 ASMには、一般にこの場合に使用しやすいオブジェクト・モデルapi(同じ文書のパートIIを参照)もあります。オブジェクトモデルはメモリ内にクラスファイル全体のツリーを構築するので、少し遅くなるかもしれませんが、変換を必要とするクラスがわずかであれば、パフォーマンスヒットは無視してください。

static finalフィールドの値が定数(数字など)でないフィールドを作成すると、その初期化は実際には "static initializer block"になります。このように、あなたの(変換)は、第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つにマージします。バイトコードを操作する場合、これは前からの静的ブロックが存在しない場合は新しいものを作成し、静的ブロックがすでに存在する場合は既存のものの先頭に追加する必要があることを意味します。

ASMでは、スタティック・ブロックは特別な名前の静的メソッドのように見えます。コンストラクタは特別な名前<init>のメソッドのようになります。

visitor apiを使用する場合、以前からメソッドが定義されているかどうかを確認するには、すべてのvisitMethod()呼び出しをリッスンし、各呼び出しでメソッド名を確認します。すべてのメソッドが訪問された後でvisitEnd()メソッドが呼び出されるため、それまでにメソッドが訪れていない場合は、新しいメソッドを作成する必要があることがわかります。

我々はバイト[]の形式でのorignalクラスを持っていると仮定すると、要求された変換は次のように行うことができます。

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; 
} 

あなたの質問は正確にあなたの最終目標が何であるかのさらなる兆候を与えていないので、Iあなたの例のクラスのケースで動作するようにハードコードされた基本的な例を使用することに決めました。変換するクラスのインスタンスを作成する場合は、実際に変換されるクラスの完全なクラス名を使用するには、上記の「例」を含むすべての文字列を変更する必要があります。または、変換されたすべてのクラスでExampleクラスのインスタンスを2つ具体的に使用する場合は、上記の例はそのまま動作します。

+2

欲しい私はこの答えを与えることができる10 upvotes。 ASMによるメソッドの追加/削除は簡単です。この回答は、それらを変更するための重要な技術を示しています。 –

関連する問題