5

Ich möchte in .class-Datei mit ASM static final Feld hinzuzufügen, und die QuelldateiWie statische Endfeld mit Initialisierer mit ASM hinzufügen?

public class Example { 

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

    public int getCode() { 
     return code; 
    } 

    private final int code; 

} 

und generierte Klasse, die so sein dekompilierten wird, sollte:

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; 

} 

Und als Abschluss , Ich möchte FIRST und SECOND Konstanten zu .class-Datei mit ASM hinzufügen, wie kann ich tun?

+0

Ist das Java? Bezieht sich die Frage auf das manen-assembly-plugin? Dann markieren Sie es als solches. –

Antwort

17

Diese Antwort zeigt, wie es mit dem Besucher api von ASM erfolgen (siehe Abschnitt 2.2 der ASM 4.0 Ein Java-Bytecode-Engineering-Bibliothek auf den ASM homepage sehen), weil es die am meisten vertraut api für mich. ASM hat auch eine Objektmodell-API-Variante (siehe Teil II im selben Dokument), die in diesem Fall möglicherweise einfacher zu verwenden ist. Das Objektmodell ist vermutlich etwas langsamer, da es einen Baum der gesamten Klassendatei im Speicher erstellt, aber wenn es nur eine kleine Menge von Klassen gibt, die transformiert werden müssen, sollte der Leistungshit vernachlässigbar sein.

Bei der Erstellung von static final Feldern, deren Werte keine Konstanten sind (wie Zahlen), geht ihre Initialisierung tatsächlich auf "static initializer block". Somit ist Ihre zweite (transformiert) Codeliste entspricht dem folgenden Java-Code:

public class Example { 

    public static final Example FIRST; 

    public static final Example SECOND; 

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

    ... 
} 

in einer Java-Datei, die Sie mehrere solche statische haben {...} Blöcke erlaubt sind, während in der Klasse dort Dateien können nur eins sein. Der Java-Compiler führt automatisch mehrere statische Blöcke zu einem zusammen, um diese Anforderung zu erfüllen. Wenn man Bytecode manipuliert, bedeutet dies, dass wenn vorher keine statische Blockierung vorhanden ist, wir eine neue Blockade erstellen. Wenn bereits eine statische Blockade vorhanden ist, müssen wir unseren Code dem Anfang der bestehenden voranstellen (das Voranstellen ist einfacher als das Anfügen).

Mit ASM sieht der statische Block wie eine statische Methode mit dem speziellen Namen <clinit> aus, so wie Konstruktoren wie Methoden mit dem speziellen Namen <init> aussehen.

Wenn Sie die Besucher-API verwenden, müssen Sie wissen, ob eine Methode zuvor definiert wurde, indem Sie alle Aufrufe von visitMethod() abhören und den Methodennamen bei jedem Aufruf überprüfen. Nachdem alle Methoden aufgerufen wurden, wird die Methode visitEnd() aufgerufen. Wenn bis dahin noch keine Methode aufgerufen wurde, müssen wir eine neue Methode erstellen.

Angenommen wir die Orignal-Klasse in byte [] Format haben, kann die angeforderte Transformation wie folgt geschehen:

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

Da Ihre Frage nicht geben weitere Hinweise auf, was genau Ziel, Ihr Ende ist, ich Ich entschied mich dafür, mit einem einfachen Beispiel zu arbeiten, das so programmiert wurde, dass es für den Beispiel-Beispielfall funktioniert. Wenn Sie Instanzen der zu transformierenden Klasse erstellen möchten, müssen Sie alle Zeichenfolgen, die "Beispiel" enthalten, ändern, um stattdessen den vollständigen Klassennamen der Klasse zu verwenden, die gerade transformiert wird. Oder wenn Sie speziell zwei Instanzen der Example-Klasse in jeder transformierten Klasse möchten, funktioniert das obige Beispiel wie es ist.

+2

Ich wünschte, ich könnte diese Antwort 10 upvotes geben. Das Hinzufügen/Entfernen von Methoden mit ASM ist einfach. Diese Antwort zeigt die kritische Technik, um sie zu modifizieren. –