5

Voglio aggiungere campo finale statico in file .class utilizzando ASM, e il file di origine vieneCome aggiungere un campo finale statico con l'inizializzatore usando ASM?

public class Example { 

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

    public int getCode() { 
     return code; 
    } 

    private final int code; 

} 

e classe generata che viene decompilato dovrebbe essere simile a questo:

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; 

} 

E come conclusione , Voglio aggiungere le costanti FIRST e SECOND al file .class usando ASM, come posso fare?

+0

java? La domanda è relativa al plugin manen-assembly? Quindi taggalo come tale. –

risposta

17

Questa risposta dimostra come può essere fatto utilizzando l'API di ASM visitatore (vedere la sezione 2.2 della ASM 4.0 Una libreria Java bytecode ingegneria sul ASM homepage) perché è l'API più familiare per me. ASM ha anche una variante api del modello di oggetto (vedi Parte II nello stesso documento) che potrebbe essere generalmente più facile da usare in questo caso. Il modello di oggetti è presumibilmente un po 'più lento poiché costruisce un albero dell'intero file di classe in memoria, ma se c'è solo una piccola quantità di classi che devono essere trasformate, il colpo di performance dovrebbe essere trascurabile.

Quando si creano i campi static final i cui valori non sono costanti (come i numeri), la loro inizializzazione in realtà passa a "static initializer block". Così, il vostro secondo (trasformata) listato di codice è equivalente al seguente codice java:

public class Example { 

    public static final Example FIRST; 

    public static final Example SECOND; 

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

    ... 
} 

In un file Java si è permesso di avere più come statica {...} blocchi, mentre in classe file lì può solo Sii uno. Il compilatore java unisce automaticamente più blocchi statici in uno per soddisfare questo requisito. Quando si manipola bytecode significa che se prima non ci sono blocchi statici ne creiamo uno nuovo, mentre se esiste già un blocco statico dobbiamo anteporre il nostro codice all'inizio di quello esistente (anteporre è più semplice che aggiungere).

Con ASM, il blocco statico si presenta come un metodo statico con il nome speciale <clinit>, proprio come i costruttori sembrano metodi con il nome speciale <init>.

Quando si utilizza l'API visitatore, il modo per sapere se un metodo è stato definito prima è quello di ascoltare tutte le chiamate visitMethod() e controllare il nome del metodo in ogni chiamata. Dopo che tutti i metodi sono stati visitati, viene chiamato il metodo visitEnd(), quindi se nessun metodo è stato visitato da allora, sappiamo che è necessario creare un nuovo metodo.

Supponendo che abbiamo una classe orignal nel byte formato [], la trasformazione richiesta può essere fatto in questo modo:

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

Dal momento che la tua domanda non ha dato ulteriori indicazioni su che cosa esattamente il vostro obiettivo finale è, ho deciso di utilizzare un esempio di base codificato per funzionare per il tuo caso di classe Esempio. Se si desidera creare istanze della classe in corso di trasformazione, è necessario modificare tutte le stringhe contenenti "Esempio" sopra per utilizzare invece il nome completo della classe della classe effettivamente trasformata. Oppure, se vuoi specificamente due istanze della classe Example in ogni classe trasformata, l'esempio precedente funziona così com'è.

+2

Vorrei poter dare questa risposta 10 upvotes. Aggiungere/rimuovere metodi con ASM è facile. Questa risposta mostra la tecnica critica per modificarli. –

Problemi correlati