В этом ответе показано, как это можно сделать с помощью 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