5

Quiero añadir campo final estático en el archivo .class mediante ASM, y el archivo de origen es¿Cómo agregar campo final estático con inicializador usando ASM?

public class Example { 

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

    public int getCode() { 
     return code; 
    } 

    private final int code; 

} 

y clase generada que se decompiled debería ser así:

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; 

} 

Y como conclusión , Quiero agregar FIRST y SECOND constants al archivo .class utilizando ASM, ¿cómo puedo hacer?

+0

¿Esto es Java? ¿La pregunta está relacionada con manen-assembly-plugin? Luego marqúelo como tal. –

Respuesta

17

Esta respuesta muestra cómo se puede hacer uso de la API de visitantes de ASM (véase la sección 2.2 de la biblioteca de Java de código de bytes de ingeniería ASM 4.0 en el ASM homepage) porque es la API más familiar para mí. ASM también tiene una variante de modelo de objeto api (véase la Parte II en el mismo documento) que, en general, podría ser más fácil de usar en este caso. El modelo de objeto es probablemente un poco más lento ya que construye un árbol de todo el archivo de clase en la memoria, pero si solo hay una pequeña cantidad de clases que necesitan transformar el rendimiento, el impacto debería ser insignificante.

Al crear static final campos cuyos valores no son constantes (como números), su inicialización en realidad va a un "static initializer block". Por lo tanto, el segundo (transformado) listado de código es equivalente al siguiente código de Java:

public class Example { 

    public static final Example FIRST; 

    public static final Example SECOND; 

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

    ... 
} 

En un archivo Java que permite tener múltiples tales estáticas {...} bloques, mientras que en la clase de archivos no sólo puede ser uno. El compilador de java fusiona automáticamente múltiples bloques estáticos en uno para cumplir con este requisito. Al manipular bytecode, esto significa que si no hay un bloque estático de antes, entonces creamos uno nuevo, mientras que si ya existe un bloque estático, tenemos que anteponer nuestro código al comienzo del existente (es más fácil agregar que agregar).

Con ASM, el bloque estático se ve como un método estático con el nombre especial <clinit>, al igual que los constructores parecen métodos con el nombre especial <init>.

Al utilizar la API de visitante, la forma de saber si un método se ha definido antes es escuchar todas las llamadas a visitMethod() y verificar el nombre del método en cada llamada. Después de que se hayan visitado todos los métodos, se llama al método visitEnd(), por lo que si no se ha visitado ningún método hasta entonces, sabemos que debemos crear un nuevo método.

Suponiendo que tenemos una clase orignal en formato de byte [], la transformación solicitada se puede hacer así:

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

Desde su pregunta no dio más indicios de qué es exactamente lo que su objetivo final es, yo Decidió ir con un ejemplo básico codificado para trabajar en su caso de clase de ejemplo. Si desea crear instancias de la clase que se va a transformar, tendrá que cambiar todas las cadenas que contengan "Ejemplo" arriba para usar el nombre de clase completo de la clase que en realidad se está transformando. O si desea específicamente dos instancias de la clase Ejemplo en cada clase transformada, el ejemplo anterior funciona tal como está.

+2

Ojalá pudiera dar 10 votos positivos a esta respuesta. Agregar/eliminar métodos con ASM es fácil. Esta respuesta muestra la técnica crítica para modificarlos. –

Cuestiones relacionadas