Как добавить статическое конечное поле с инициализатором с помощью 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;

}

И в заключение, я хочу добавить константы FIRST и SECOND в файл .class с помощью ASM, как мне это сделать?


person Christian Gann    schedule 16.06.2012    source источник
comment
это джава? Вопрос связан с manen-assembly-plugin? Затем пометьте его как таковой.   -  person Jens Björnhager    schedule 17.06.2012


Ответы (1)


В этом ответе показано, как это можно сделать с помощью API посетителя ASM (см. раздел 2.2 ASM 4.0 A Java Engineering Library на домашняя страница ASM), потому что это наиболее знакомый мне API. ASM также имеет вариант API объектной модели (см. часть II в том же документе), который может быть проще в использовании в этом случае. Предположительно, объектная модель немного медленнее, поскольку она строит дерево всего файла класса в памяти, но если есть только небольшое количество классов, которые необходимо преобразовать, снижение производительности должно быть незначительным.

При создании полей static final, значения которых не являются константами (например, числами), их инициализация фактически переходит в "статический блок инициализации< /а>». Таким образом, ваш второй (преобразованный) листинг кода эквивалентен следующему коду Java:

public class Example {

  public static final Example FIRST;

  public static final Example SECOND;

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

  ...
}

В файле java вам разрешено иметь несколько таких блоков static { ... }, тогда как в файлах классов может быть только один. Компилятор Java автоматически объединяет несколько статических блоков в один, чтобы выполнить это требование. При манипулировании байт-кодом это означает, что если ранее не было статического блока, мы создаем новый, а если статический блок уже существует, нам нужно добавить наш код в начало существующего (добавление проще, чем добавление).

В ASM статический блок выглядит как статический метод со специальным именем <clinit>, точно так же, как конструкторы выглядят как методы со специальным именем <init>.

При использовании посетителя API способ узнать, был ли метод определен ранее, состоит в том, чтобы прослушивать все вызовы visitMethod() и проверять имя метода в каждом вызове. После того, как все методы были посещены, вызывается метод visitEnd(), поэтому, если к тому времени ни один метод не был посещен, мы знаем, что нам нужно создать новый метод.

Предполагая, что у нас есть исходный класс в формате byte[], запрошенное преобразование можно выполнить следующим образом:

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. Если вы хотите создать экземпляры трансформируемого класса, вам придется изменить все строки, содержащие «Пример» выше, чтобы вместо этого использовать полное имя класса, который фактически преобразуется. Или, если вам специально нужны два экземпляра класса Example в каждом преобразованном классе, приведенный выше пример работает как есть.

person Jonas Berlin    schedule 07.02.2013
comment
Хотел бы я дать этому ответу 10 голосов. Добавлять/удалять методы с помощью ASM очень просто. Этот ответ показывает критический метод их изменения. - person David Blevins; 18.07.2013