5

我想使用ASM靜態最終字段添加到.class文件,源文件是如何使用ASM爲初始化程序添加靜態最終字段?

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文件,我該怎麼辦?

+0

這是Java的嗎?這個問題與manen-assembly-plugin有關嗎?然後標記它。 –

回答

17

這個答案將展示如何使用ASM的訪問者API來完成(見的ASM homepageASM 4.0一個Java字節碼工程庫的第2.2節),因爲它是我最熟悉的API。 ASM也有一個對象模型API(參見同一文檔中的第II部分)變體,在這種情況下可能通常更容易使用。由於它構造了整個類文件在內存中的一棵樹,因此對象模型有點慢,但如果只有少量類需要轉換,性能命中應該可以忽略不計。

當創建static final字段的值不是常量(如數字)時,它們的初始化實際上會轉到「static initializer block」。因此,你的第二個(轉化)代碼等同於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()方法被調用,所以如果到那時還沒有訪問過方法,我們知道我們需要創建一個新的方法。

假設我們有一個字節[]格式的一部開拓創新類,所請求轉換可以做這樣的:

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類的兩個實例,則上述示例按原樣運行。

+2

希望我能給這個答案10 upvotes。使用ASM添加/刪除方法非常簡單。這個答案顯示了修改它們的關鍵技術。 –